From f4baa86b71e6c4c2efc8c2d091879112b03c13fb Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Wed, 13 Apr 2022 07:15:11 -0400 Subject: [PATCH 001/177] Devel version 5 --- Changes | 8 ++++++++ configure.ac | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 91b355795..aa24b39b0 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,14 @@ The changes in each Verilator version are described below. The contributors that suggested a given feature are shown in []. Thanks! +Verilator 5.001 devel +========================== + +**Major:** + +* This is a major new release, currently only in alpha testing. + + Verilator 4.221 devel ========================== diff --git a/configure.ac b/configure.ac index 0be207f17..3313a0437 100644 --- a/configure.ac +++ b/configure.ac @@ -7,7 +7,7 @@ #AC_INIT([Verilator],[#.### YYYY-MM-DD]) #AC_INIT([Verilator],[#.### devel]) -AC_INIT([Verilator],[4.221 devel], +AC_INIT([Verilator],[5.001 devel], [https://verilator.org], [verilator],[https://verilator.org]) # When releasing, also update header of Changes file From 92f64de115a7fe9ff52c4f7a2b00273487826e91 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Wed, 13 Apr 2022 22:18:59 -0400 Subject: [PATCH 002/177] Add FIXMEV5 comments --- test_regress/t/t_dist_fixme.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test_regress/t/t_dist_fixme.pl b/test_regress/t/t_dist_fixme.pl index f722de3fe..74c927883 100755 --- a/test_regress/t/t_dist_fixme.pl +++ b/test_regress/t/t_dist_fixme.pl @@ -38,7 +38,8 @@ if (!-r "$root/.git") { print "$grep\n"; foreach my $line (split /\n/, $grep) { print "L $line\n"; - $names{$1} = 1 if $line =~ /^([^:]+)/; + # FIXMEV5 for use in develop-v5 branch until merged to master + $names{$1} = 1 if $line =~ /^([^:]+)/ && $line !~ /FIXMEV5/; } } } From 599d23697dabe2da7dcd6e2cd863c1f9adec8e41 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 15 May 2022 16:03:32 +0100 Subject: [PATCH 003/177] 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 --- Changes | 3 + bin/verilator | 1 - docs/gen/ex_DIDNOTCONVERGE_msg.rst | 6 +- docs/gen/ex_DIDNOTCONVERGE_nodbg_msg.rst | 2 +- docs/guide/exe_verilator.rst | 26 +- docs/guide/extensions.rst | 8 +- docs/guide/languages.rst | 11 - docs/guide/warnings.rst | 92 +- docs/internals.rst | 321 +++ include/verilated_types.h | 69 + src/Makefile_obj.in | 6 +- src/V3Active.cpp | 313 +-- src/V3ActiveTop.cpp | 52 +- src/V3Ast.h | 130 +- src/V3AstNodes.cpp | 46 +- src/V3AstNodes.h | 120 +- src/V3CCtors.cpp | 4 +- src/V3Cast.cpp | 5 +- src/V3Cdc.cpp | 4 +- src/V3Changed.cpp | 255 -- src/V3Changed.h | 32 - src/V3Clock.cpp | 332 +-- src/V3Const.cpp | 56 +- src/V3Dead.cpp | 2 +- src/V3Delayed.cpp | 72 +- src/V3EmitCFunc.cpp | 77 - src/V3EmitCFunc.h | 11 - src/V3EmitCImp.cpp | 1 + src/V3EmitCModel.cpp | 109 +- src/V3EmitCSyms.cpp | 17 + src/V3EmitV.cpp | 10 + src/V3EmitXml.cpp | 1 - src/V3Error.h | 8 +- src/V3FileLine.cpp | 2 - src/V3GenClk.cpp | 224 -- src/V3GenClk.h | 32 - src/V3Global.h | 3 + src/V3LifePost.cpp | 36 +- src/V3LinkLValue.cpp | 7 + src/V3LinkParse.cpp | 2 +- src/V3LinkResolve.cpp | 39 +- src/V3Options.cpp | 4 +- src/V3Options.h | 2 - src/V3Order.cpp | 1352 +++------ src/V3Order.h | 27 +- src/V3OrderGraph.h | 94 +- src/V3Partition.cpp | 20 +- src/V3Sched.cpp | 1047 +++++++ src/V3Sched.h | 126 + src/V3SchedAcyclic.cpp | 421 +++ src/V3SchedPartition.cpp | 400 +++ src/V3SchedReplicate.cpp | 268 ++ src/V3SenTree.h | 27 +- src/V3Task.cpp | 11 +- src/V3Width.cpp | 15 +- src/Verilator.cpp | 15 +- src/verilog.y | 17 +- test_regress/t/t_alw_dly.pl | 1 - test_regress/t/t_case_huge.pl | 2 +- test_regress/t/t_clk_condflop_nord.v | 127 - test_regress/t/t_clk_dpulse.v | 2 - test_regress/t/t_clk_dsp.v | 2 - test_regress/t/t_clk_first.v | 4 - test_regress/t/t_clk_gen.v | 1 - test_regress/t/t_clk_latch.pl | 5 +- test_regress/t/t_clk_latch_edgestyle.pl | 6 +- test_regress/t/t_clk_latchgate.v | 3 +- test_regress/t/t_clk_powerdn.v | 2 - test_regress/t/t_clk_scope_bad.out | 7 - test_regress/t/t_clk_scope_bad.pl | 5 +- test_regress/t/t_clocker.pl | 2 +- test_regress/t/t_clocker.v | 5 - test_regress/t/t_clocker_bad.out | 12 - test_regress/t/t_comb_input_0.cpp | 40 + test_regress/t/t_comb_input_0.pl | 24 + test_regress/t/t_comb_input_0.v | 34 + test_regress/t/t_comb_input_1.cpp | 40 + test_regress/t/t_comb_input_1.pl | 24 + test_regress/t/t_comb_input_1.v | 39 + test_regress/t/t_comb_input_2.cpp | 40 + test_regress/t/t_comb_input_2.pl | 24 + test_regress/t/t_comb_input_2.v | 48 + test_regress/t/t_debug_emitv.out | 2 +- test_regress/t/t_dedupe_clk_gate.pl | 4 +- test_regress/t/t_dedupe_clk_gate.v | 2 +- test_regress/t/t_dedupe_clk_gate.vlt | 3 - test_regress/t/t_detectarray_1.pl | 2 +- test_regress/t/t_detectarray_2.pl | 2 +- test_regress/t/t_event.v | 69 +- test_regress/t/t_flag_comp_limit_parens.pl | 3 +- test_regress/t/t_flag_csplit_eval.pl | 4 +- test_regress/t/t_flag_xinitial_unique.pl | 3 +- test_regress/t/t_func_check.v | 2 + test_regress/t/t_fuzz_always_bad.out | 4 - test_regress/t/t_gen_forif.pl | 3 + test_regress/t/t_gen_intdot.pl | 3 + test_regress/t/t_gen_upscope.out | 2 +- test_regress/t/t_hier_block.v | 4 +- test_regress/t/t_inst_ccall.v | 1 - test_regress/t/t_inst_tree.v | 4 +- test_regress/t/t_inst_tree_inl0_pub1.pl | 2 +- test_regress/t/t_lib_prot.v | 2 +- test_regress/t/t_lib_prot_secret.v | 2 +- test_regress/t/t_lint_didnotconverge_bad.out | 7 +- test_regress/t/t_lint_didnotconverge_bad.pl | 2 +- test_regress/t/t_lint_didnotconverge_bad.v | 2 +- .../t/t_lint_didnotconverge_nodbg_bad.out | 2 +- test_regress/t/t_lint_latch_1.out | 9 - test_regress/t/t_lint_latch_1.pl | 2 - test_regress/t/t_lint_latch_5.pl | 2 - test_regress/t/t_lint_latch_5.v | 4 +- test_regress/t/t_lint_latch_bad_2.out | 14 +- test_regress/t/t_lint_latch_bad_2.v | 4 +- test_regress/t/t_lint_latch_bad_3.out | 30 +- test_regress/t/t_lint_latch_bad_3.v | 12 +- test_regress/t/t_lint_nolatch_bad.out | 12 +- test_regress/t/t_lint_nolatch_bad.v | 6 +- test_regress/t/t_lint_unsup_mixed.v | 2 + test_regress/t/t_math_shift.pl | 1 - test_regress/t/t_math_shift_noexpand.pl | 2 +- test_regress/t/t_optm_if_cond.pl | 2 +- test_regress/t/t_order_clkinst.out | 2 +- test_regress/t/t_order_clkinst.v | 7 +- test_regress/t/t_order_clkinst_bad.out | 24 - test_regress/t/t_order_doubleloop.pl | 5 +- test_regress/t/t_order_loop_bad.pl | 13 +- test_regress/t/t_order_wireloop.pl | 2 +- test_regress/t/t_protect_ids_key.out | 22 +- ...clk_condflop_nord.pl => t_scheduling_0.pl} | 7 +- test_regress/t/t_scheduling_0.v | 66 + ...order_clkinst_bad.pl => t_scheduling_1.pl} | 13 +- test_regress/t/t_scheduling_1.v | 47 + .../t/{t_clocker_bad.pl => t_scheduling_2.pl} | 14 +- test_regress/t/t_scheduling_2.v | 47 + test_regress/t/t_scheduling_3.pl | 21 + test_regress/t/t_scheduling_3.v | 47 + test_regress/t/t_scheduling_4.pl | 21 + test_regress/t/t_scheduling_4.v | 49 + test_regress/t/t_scheduling_5.pl | 22 + test_regress/t/t_scheduling_5.v | 44 + test_regress/t/t_scheduling_6.v | 39 + test_regress/t/t_sys_rand_concat.pl | 4 +- test_regress/t/t_trace_complex.out | 282 +- test_regress/t/t_trace_complex_params.out | 282 +- test_regress/t/t_unopt_combo.vlt | 2 +- test_regress/t/t_unopt_combo_bad.out | 8 +- .../t/t_unopt_converge_initial_run_bad.out | 6 +- test_regress/t/t_unopt_converge_ndbg_bad.out | 2 +- test_regress/t/t_unopt_converge_print_bad.out | 6 +- test_regress/t/t_unopt_converge_run_bad.out | 6 +- test_regress/t/t_unopt_converge_unopt_bad.out | 12 +- test_regress/t/t_unoptflat_simple_2_bad.out | 10 +- test_regress/t/t_var_pinsizes.v | 1 - test_regress/t/t_verilated_debug.out | 28 +- test_regress/t/t_wrapper_context_top0.out | 4 +- test_regress/t/t_wrapper_context_top1.out | 4 +- test_regress/t/t_xml_debugcheck.out | 2482 +++++++++-------- 157 files changed, 6386 insertions(+), 4395 deletions(-) delete mode 100644 src/V3Changed.cpp delete mode 100644 src/V3Changed.h delete mode 100644 src/V3GenClk.cpp delete mode 100644 src/V3GenClk.h create mode 100644 src/V3Sched.cpp create mode 100644 src/V3Sched.h create mode 100644 src/V3SchedAcyclic.cpp create mode 100644 src/V3SchedPartition.cpp create mode 100644 src/V3SchedReplicate.cpp delete mode 100644 test_regress/t/t_clk_condflop_nord.v delete mode 100644 test_regress/t/t_clk_scope_bad.out delete mode 100644 test_regress/t/t_clocker_bad.out create mode 100644 test_regress/t/t_comb_input_0.cpp create mode 100755 test_regress/t/t_comb_input_0.pl create mode 100644 test_regress/t/t_comb_input_0.v create mode 100644 test_regress/t/t_comb_input_1.cpp create mode 100755 test_regress/t/t_comb_input_1.pl create mode 100644 test_regress/t/t_comb_input_1.v create mode 100644 test_regress/t/t_comb_input_2.cpp create mode 100755 test_regress/t/t_comb_input_2.pl create mode 100644 test_regress/t/t_comb_input_2.v delete mode 100644 test_regress/t/t_dedupe_clk_gate.vlt delete mode 100644 test_regress/t/t_lint_latch_1.out delete mode 100644 test_regress/t/t_order_clkinst_bad.out rename test_regress/t/{t_clk_condflop_nord.pl => t_scheduling_0.pl} (63%) create mode 100644 test_regress/t/t_scheduling_0.v rename test_regress/t/{t_order_clkinst_bad.pl => t_scheduling_1.pl} (56%) create mode 100644 test_regress/t/t_scheduling_1.v rename test_regress/t/{t_clocker_bad.pl => t_scheduling_2.pl} (55%) create mode 100644 test_regress/t/t_scheduling_2.v create mode 100755 test_regress/t/t_scheduling_3.pl create mode 100644 test_regress/t/t_scheduling_3.v create mode 100755 test_regress/t/t_scheduling_4.pl create mode 100644 test_regress/t/t_scheduling_4.v create mode 100755 test_regress/t/t_scheduling_5.pl create mode 100644 test_regress/t/t_scheduling_5.v create mode 100644 test_regress/t/t_scheduling_6.v diff --git a/Changes b/Changes index 75a7516bd..3332a28c5 100644 --- a/Changes +++ b/Changes @@ -14,6 +14,9 @@ Verilator 5.001 devel **Major:** * This is a major new release, currently only in alpha testing. +* Fully support the Active and NBA scheduling regions as defined by the + SystemVerilog standard (IEEE 1800-2017 chapter 4). This means all generated + clocks are now simulated correctly (#3278, #3384). [Geza Lore, Shunyao CAD] Verilator 4.223 devel diff --git a/bin/verilator b/bin/verilator index b1ee97e73..d936ebf56 100755 --- a/bin/verilator +++ b/bin/verilator @@ -355,7 +355,6 @@ detailed descriptions of these arguments. -O3 High performance optimizations -O Selectable optimizations -o Name of final executable - --no-order-clock-delay Disable ordering clock enable assignments --no-verilate Skip verilation and just compile previously Verilated code. --output-split Split .cpp files into pieces --output-split-cfuncs Split model functions diff --git a/docs/gen/ex_DIDNOTCONVERGE_msg.rst b/docs/gen/ex_DIDNOTCONVERGE_msg.rst index c15d71bb4..bd055184b 100644 --- a/docs/gen/ex_DIDNOTCONVERGE_msg.rst +++ b/docs/gen/ex_DIDNOTCONVERGE_msg.rst @@ -1,7 +1,5 @@ .. comment: generated by t_lint_didnotconverge_bad .. code-block:: - -V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request - -V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request_1 - -V{t#,#} CHANGE: t/t_lint_didnotconverge_bad.v:14: a - %Error: t/t_lint_didnotconverge_bad.v:7: Verilated model didn't converge + -V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] b) + %Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge. diff --git a/docs/gen/ex_DIDNOTCONVERGE_nodbg_msg.rst b/docs/gen/ex_DIDNOTCONVERGE_nodbg_msg.rst index b6de737df..925494ec9 100644 --- a/docs/gen/ex_DIDNOTCONVERGE_nodbg_msg.rst +++ b/docs/gen/ex_DIDNOTCONVERGE_nodbg_msg.rst @@ -1,4 +1,4 @@ .. comment: generated by t_lint_didnotconverge_nodbg_bad .. code-block:: - %Error: t/t_lint_didnotconverge_bad.v:7: Verilated model didn't converge + %Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge. diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 00e101ed0..7a8e791f5 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -158,10 +158,7 @@ Summary: .. option:: --clk - With :vlopt:`--clk`, the specified signal-name is taken as a root clock - into the model; Verilator will mark the signal as clocker and - propagate the clocker attribute automatically to other signals downstream in - that clock tree. + With :vlopt:`--clk`, the specified signal is marked as a clock signal. The provided signal-name is specified using a RTL hierarchy path. For example, v.foo.bar. If the signal is the input to top-module, then @@ -173,11 +170,11 @@ Summary: individual bits, Verilator will attempt to decompose the vector and connect the single-bit clock signals. - The clocker attribute is useful in cases where Verilator does not - properly distinguish clock signals from other data signals. Using - clocker will cause the signal indicated to be considered a clock, and - remove it from the combinatorial logic reevaluation checking code. This - may greatly improve performance. + In versions prior to 5.002, the clocker attribute is useful in cases where + Verilator does not properly distinguish clock signals from other data + signals. Using clocker will cause the signal indicated to be considered a + clock, and remove it from the combinatorial logic reevaluation checking + code. This may greatly improve performance. .. option:: --compiler @@ -714,6 +711,10 @@ Summary: .. option:: --no-order-clock-delay + Deprecated and has no effect (ignored). + + In versions prior to 5.002: + Rarely needed. Disables a bug fix for ordering of clock enables with delayed assignments. This option should only be used when suggested by the developers. @@ -1255,8 +1256,7 @@ Summary: Enable all code style warnings, including code style warnings that are normally disabled by default. Equivalent to :vlopt:`-Wwarn-lint` - :vlopt:`-Wwarn-style`. Excludes some specialty warnings, - i.e. IMPERFECTSCH. + :vlopt:`-Wwarn-style`. Excludes some specialty warnings. .. option:: -Werror- @@ -1509,6 +1509,10 @@ The grammar of configuration commands is as follows: .. option:: clock_enable -module "" -var "" + Deprecated and has no effect (ignored). + + In versions prior to 5.002: + Indicate the signal is used to gate a clock, and the user takes responsibility for insuring there are no races related to it. diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index bd5171c52..230bbac69 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -160,6 +160,10 @@ or "`ifdef`"'s may break other tools. .. option:: /*verilator&32;clock_enable*/ + Deprecated and has no effect (ignored). + + In versions prior to 5.002: + Used after a signal declaration to indicate the signal is used to gate a clock, and the user takes responsibility for insuring there are no races related to it. (Typically by adding a latch, and running static timing @@ -184,9 +188,7 @@ or "`ifdef`"'s may break other tools. .. option:: /*verilator&32;no_clocker*/ - Specifies that the signal is used as clock or not. This information is - used by Verilator to mark the signal and any derived signals as - clocker. See :vlopt:`--clk`. + Specifies that the signal is used as clock or not. See :vlopt:`--clk`. Same as :option:`clocker` and :option:`no_clocker` in configuration files. diff --git a/docs/guide/languages.rst b/docs/guide/languages.rst index 074f91d31..4ad58d9da 100644 --- a/docs/guide/languages.rst +++ b/docs/guide/languages.rst @@ -276,17 +276,6 @@ probably expect, what C does. The default behavior of Verilog is different.) -Generated Clocks ----------------- - -Verilator attempts to deal with generated and gated clocks correctly, -however some cases cause problems in the scheduling algorithm which is -optimized for performance. The safest option is to have all clocks as -primary inputs to the model, or wires directly attached to primary inputs. -For proper behavior clock enables may also need the -:option:`/*verilator&32;clock_enable*/` metacomment. - - Gate Primitives --------------- diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 55206295c..1b7026d4f 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -5,6 +5,7 @@ Errors and Warnings ******************* +.. _Disabling Warnings: Disabling Warnings ================== @@ -313,20 +314,6 @@ List Of Warnings potential for reset glitches. -.. option:: CLKDATA - - .. TODO better example - - Warns that clock signal is mixed used with/as data signal. The checking - for this warning is enabled only if user has explicitly marked some - signal as clocker using command line option or in-source meta comment - (see :vlopt:`--clk`). - - The warning can be disabled without affecting the simulation result. But - it is recommended to check the warning as this may degrade the - performance of the Verilated model. - - .. option:: CMPCONST .. TODO better example @@ -492,7 +479,7 @@ List Of Warnings passing. Thus to prevent an infinite loop, the Verilated executable gives the DIDNOTCONVERGE error. - To debug this, first review any UNOPT or UNOPTFLAT warnings that were + To debug this, first review any UNOPTFLAT warnings that were ignored. Though typically it is safe to ignore UNOPTFLAT (at a performance cost), at the time of issuing a UNOPTFLAT Verilator did not know if the logic would eventually converge and assumed it would. @@ -587,13 +574,6 @@ List Of Warnings with a newline." -.. option:: GENCLK - - Deprecated and no longer used as a warning. Used to indicate that the - specified signal was is generated inside the model, and also being used - as a clock. - - .. option:: HIERBLOCK Warns that the top module is marked as a hierarchy block by the @@ -651,16 +631,6 @@ List Of Warnings simulate correctly. -.. option:: IMPERFECTSCH - - .. TODO better example - - Warns that the scheduling of the model is not absolutely perfect, and - some manual code edits may result in faster performance. This warning - defaults to off, is not part of -Wall, and must be turned on explicitly - before the top module statement is processed. - - .. option:: IMPLICIT .. TODO better example @@ -1353,27 +1323,6 @@ List Of Warnings undriven (...) and will be removed". -.. option:: UNOPT - - .. TODO better example - - Warns that due to some construct, optimization of the specified signal - or block is disabled. The construct should be cleaned up to improve - simulation performance. - - A less obvious case of this is when a module instantiates two - submodules. Inside submodule A, signal I is input and signal O is - output. Likewise in submodule B, signal O is an input and I is an - output. A loop exists and a UNOPT warning will result if AI & AO both - come from and go to combinatorial blocks in both submodules, even if - they are unrelated always blocks. This affects performance because - Verilator would have to evaluate each submodule multiple times to - stabilize the signals crossing between the modules. - - Ignoring this warning will only slow simulations, it will simulate - correctly. - - .. option:: UNOPTFLAT .. TODO better example @@ -1385,10 +1334,6 @@ List Of Warnings performance; two times better performance may be possible by fixing these warnings. - Unlike the ``UNOPT`` warning, this occurs after flattening the netlist, - and indicates a more basic problem, as the less obvious case described - under ``UNOPT`` does not apply. - Often UNOPTFLAT is caused by logic that isn't truly circular as viewed by synthesis which analyzes interconnection per-bit, but is circular to simulation which analyzes per-bus. @@ -1441,10 +1386,6 @@ List Of Warnings the conflict. If you run with `--report-unoptflat` Verilator will suggest possible candidates for :option:`/*verilator&32;split_var*/`. - The UNOPTFLAT warning may also be due to clock enables, identified from - the reported path going through a clock gating instance. To fix these, - use the clock_enable meta comment described above. - The UNOPTFLAT warning may also occur where outputs from a block of logic are independent, but occur in the same always block. To fix this, use the :option:`/*verilator&32;isolate_assignments*/` metacomment described @@ -1702,3 +1643,32 @@ List Of Warnings The correct fix is to either size the 1 (:code:`32'h1`), or add the width to the parameter definition (:code:`parameter [31:0]`), or add the width to the parameter usage (:code:`{PAR[31:0], PAR[31:0]}`). + + +Historical Warnings +=================== + +The following list of warnings used to be issued by some earlier versions of +Verilator. The current version never issues these warnings. For compatibility, +these warning codes are still accepted by the message control mechanisms (see +:ref:`Disabling Warnings`), but have no other effect. + + +.. option:: CLKDATA + + Historical, never issued by current version of Verilator. + + +.. option:: GENCLK + + Historical, never issued by current version of Verilator. + + +.. option:: IMPERFECTSCH + + Historical, never issued by current version of Verilator. + + +.. option:: UNOPT + + Historical, never issued by current version of Verilator. diff --git a/docs/internals.rst b/docs/internals.rst index 62358f1d7..cf6b05d1e 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -183,6 +183,327 @@ A number of predefined derived algorithm classes and access methods are provided and documented in ``V3GraphAlg.cpp``. + +Scheduling +---------- + +Verilator implements the Active and NBA regions of the SystemVerilog scheduling +model as described in IEEE 1800-2017 chapter 4, and in particular sections +4.5 and Figure 4.1. The static (verilation time) scheduling of SystemVerilog +processes is performed by code in the ``V3Sched`` namespace. The single +entry-point to the scheduling algorithm is ``V3Sched::schedule``. Some +preparatory transformations important for scheduling are also performed in +``V3Active`` and ``V3ActiveTop``. High level evaluation functions are +constructed by ``V3Order``, which ``V3Sched`` invokes on subsets of the logic +in the design. + +Scheduling deals with the problem of evaluating 'logic' in the correct order +and the correct number of times in order to compute the correct state of the +SystemVerilog program. Throughout this section, we use the term 'logic' to +refer to all SystemVerilog constructs that describe the evolution of the state +of the program. In particular, all SystemVerilog processes and continuous +assignments are considered 'logic', but not for example variable definitions +without initialization or other miscellaneous constructs. + + +Classes of logic +^^^^^^^^^^^^^^^^ + +The first step in the scheduling algorithm is to gather all the logic present +in the design, and classify it based on the conditions under which the logic +needs to be evaluated. + +The classes of logic we distinguish between are: + +- SystemVerilog ``initial`` processes, that need to be executed once at + startup. + +- Static variable initializers. These are a separate class as they need to be + executed before ``initial`` processes. + +- SystemVerilog ``final`` processes. + +- Combinational logic. Any process or construct that has an implicit + sensitivity list with no explicit sensitivities is considered 'combinational' + logic. This includes among other things, ``always @*`` and ``always_comb`` + processes, and continuous assignments. Verilator also converts some other + ``always`` processes to combinational logic in ``V3Active`` as described + below. + +- Clocked logic. Any process or construct that has an explicit sensitivity + list, with no implicit sensitivities is considered 'clocked' (or + 'sequential') logic. This includes among other things ``always`` and + ``always_ff`` processes with an explicit sensitivity list. + +Note that the distinction between clocked logic and combinational logic is only +important for the scheduling algorithm within Verilator as we handle the two +classes differently. It is possible to convert clocked logic into combinational +logic if the explicit sensitivity list of the clocked logic is the same as the +implicit sensitivity list of the equivalent combinational logic would be. The +canonical examples are: ``always @(a) x = a;``, which is considered to be +clocked logic by Verilator, and the equivalent ``assign x = a;``, which is +considered to be combinational logic. ``V3Active`` in fact converts all clocked +logic to combinational logic whenever possible, as this provides advantages for +scheduling as described below. + +There is also a 'hybrid' logic class, which has both explicit and implicit +sensitivities. This kind of logic does not arise from a SystemVerilog +construct, but is created during scheduling to break combinational cycles. +Details of this process and the hybrid logic class are described below. + + +Scheduling of simple classes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +SystemVerilog ``initial`` and ``final`` blocks can be scheduled (executed) in an +arbitrary order. + +Static variable initializers need to be executed in source code order in case +there is a dependency between initializers, but the ordering of static variable +initialization is otherwise not defined by the SystemVerilog standard +(particularly, in the presence of hierarchical references in static variable +initializers). + +The scheduling algorithm handles all three of these classes the same way and +schedules the logic in these classes in source code order. This step yields the +``_eval_static``, ``_eval_initial`` and ``_eval_final`` functions which execute +the corresponding logic constructs. + + +Scheduling of clocked and combinational logic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +For performance, clocked and combinational logic needs to be ordered. +Conceptually this minimizes the iterations through the evaluation loop +presented in the reference algorithm in the SystemVerilog standard (IEEE +1800-2017 section 4.5), by evaluating logic constructs in data-flow order. +Without going into a lot of detail here, accept that well thought out ordering +is crucial to good simulation performance, and also enables further +optimizations later on. + +At the highest level, ordering is performed by ``V3Order::order``, which is +invoked by ``V3Sched::schedule`` on various subsets of the combinational and +clocked logic as described below. The important thing to highlight now is that +``V3Order::order`` operates by assuming that the state of all variables driven +by combinational logic are consistent with that combinational logic. While this +might seem subtle, it is very important, so here is an example: + +:: + always_comb d = q + 2; + always @(posedge clock) q <= d; + + +During ordering, ``V3Order`` will assume that ``d`` equals ``q + 2`` at the +beginning of an evaluation step. As a result it will order the clocked logic +first, and all downstream combinational logic (like the assignment to ``d``) +will execute after the clocked logic that drives inputs to the combinational +logic, in data-flow (or dependency) order. At the end of the evaluation step, +this ordering restores the invariant that variables driven by combinational +logic are consistent with that combinational logic (i.e.: the circuit is in a +settled/steady state). + +One of the most important optimizations for performance is to only evaluate +combinational logic, if its inputs might have changed. For example, there is no +point in evaluating the above assignment to ``d`` on a negative edge of the +clock signal. Verilator does this by pushing the combinational logic into the +same (possibly multiple) event domains as the logic driving the inputs to that +combinational logic, and only evaluating the combinational logic if at least +one driving domains have been triggered. The impact of this activity gating is +very high (observed 100x slowdown on large designs when turning it off), it is +the reason we prefer to convert clocked logic to combinational logic in +``V3Active`` whenever possible. + +The ordering procedure described above works straight forward unless there are +combinational logic constructs that are circularly dependent (a.k.a.: the +UNOPTFLAT warning). Combinational scheduling loops can arise in sound +(realizable) circuits as Verilator considers each SystemVerilog process as a +unit of scheduling (albeit we do try to split processes into smaller ones to +avoid this circularity problem whenever possible, this is not always possible). + + +Breaking combinational loops +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Combinational loops are broken by the introduction of instances of the 'hybrid' +logic class. As described in the previous section, combinational loops require +iteration until the logic is settled, in order to restore the invariant that +combinationally driven signals are consistent with the combinational logic. + +To achieve this, ``V3Sched::schedule`` calls ``V3Sched::breakCycles``, which +builds a dependency graph of all combinational logic in the design, and then +breaks all combinational cycles by converting all combinational logic that +consumes a variable driven via a 'back-edge' into hybrid logic. Here +'back-edge' just means a graph edge that points from a higher rank vertex to a +lower rank vertex in some consistent ranking of the directed graph. Variables +driven via a back-edge in the dependency graph are marked, and all +combinational logic that depends on such variables is converted into hybrid +logic, with the back-edge driven variables listed as explicit 'changed' +sensitivities. + +Hybrid logic is handled by ``V3Order`` mostly in the same way as combinational +logic, with two exceptions: + +- Explicit sensitivities of hybrid logic are ignored for the purposes of + data-flow ordering with respect to other combinational or hybrid logic. I.e.: + an explicit sensitivity suppresses the implicit sensitivity on the same + variable. This cold also be interpreted as ordering the hybrid logic as if + all variables listed as explicit sensitivities were substituted as constants + with their current values. + +- The explicit sensitivities are included as an additional driving domain of + the logic, and also cause evaluation when triggered. + +This means that hybrid logic is evaluated when either any of its implicit +sensitivities might have been updated (the same way as combinational logic, by +pushing it into the domains that write those variables), or if any of its +explicit sensitivities are triggered. + +The effect of this transformation is that ``V3Order`` can proceed as if there +are no combinational cycles (or alternatively, under the assumption that the +back-edge driven variables don't change during one evaluation pass). The +evaluation loop invoking the ordered code, will then re-invoke it on a follow +on iteration, if any of the explicit sensitivities of hybrid logic have +actually changed due to the previous invocation, iterating until all the +combinational (including hybrid) logic have settled. + +One might wonder if there can be a race condition between clocked logic +triggered due to a combinational signal change from the previous evaluation +pass, and a combinational loop settling due to hybrid logic, if the clocked +logic reads the not yet settled combinationally driven signal. Such a race is +indeed possible, but our evaluation is consistent with the SystemVerilog +scheduling semantics (IEEE 1800-2017 chapter 4), and therefore any program that +exhibits such a race has non-deterministic behaviour according to the +SystemVerilog semantics, so we accept this. + + +Settling combinational logic after initialization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +At the beginning of simulation, once static initializer and ``initial`` blocks +have been executed, we need to evaluate all combinational logic, in order to +restore the invariant utilized by ``V3Order`` that the state of all +combinationally driven variables are consistent with the combinational logic. + +To achieve this, we invoke ``V3Order::order`` on all of the combinational and +hybrid logic, and iterate the resulting evaluation function until no more +hybrid logic is triggered. This yields the `_eval_settle` function which is +invoked at the beginning of simulation, after the `_eval_initial`. + + +Partitioning logic for correct NBA updates +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``V3Order`` can order logic corresponding to non-blocking assignments (NBAs) to +yield correct simulation results, as long as all the sensitivity expressions of +clocked logic triggered in the Active scheduling region of the current time +step are known up front. I.e.: the ordering of NBA updates is only correct if +derived clocks that are computed in an Active region update (that is, via a +blocking or continuous assignment) are known up front. + +We can ensure this by partitioning the logic into two regions. Note these +regions are a concept of the Verilator scheduling algorithm and they do not +directly correspond to the similarly named SystemVerilog scheduling regions +as defined in the standard: + +- All logic (clocked, combinational and hybrid) that transitively feeds into, + or drives, via a non-blocking or continuous assignments (or via any update + that SystemVerilog executes in the Active scheduling region), a variable that + is used in the explicit sensitivity list of some clocked or hybrid logic, is + assigned to the 'act' region. + +- All other logic is assigned to the 'nba' region. + +For completeness, note that a subset of the 'act' region logic, specifically, +the logic related to the pre-assignments of NBA updates (i.e.: AstAssignPre +nodes), is handled separately, but is executed as part of the 'act' region. + +Also note that all logic representing the committing of an NBA (i.e.: Ast*Post) +nodes) will be in the 'nba' region. This means that the evaluation of the 'act' +region logic will not commit any NBA updates. As a result, the 'act' region +logic can be iterated to compute all derived clock signals up front. + +The correspondence between the SystemVerilog Active and NBA scheduling regions, +and the internal 'act' and 'nba' regions, is that 'act' contains all Active +region logic that can compute a clock signal, while 'nba' contains all other +Active and NBA region logic. For example, if the only clocks in the design are +top level inputs, then 'act' will be empty, and 'nba' will contain the whole of +the design. + +The partitioning described above is performed by ``V3Sched::partition``. + + +Replication of combinational logic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We will separately invoke ``V3Order::order`` on the 'act' and 'nba' region +logic. + +Combinational logic that reads variables driven from both 'act' and 'nba' +region logic has the problem of needing to be re-evaluated even if only one of +the regions updates an input variable. We could pass additional trigger +expressions between the regions to make sure combinational logic is always +re-evaluated, or we can replicate combinational logic that is driven from +multiple regions, by copying it into each region that drives it. Experiments +show this simple replication works well performance-wise (and notably +``V3Combine`` is good at combining the replicated code), so this is what we do +in ``V3Sched::replicateLogic``. + +In ``V3Sched::replicateLogic``, in addition to replicating logic into the 'act' +and 'nba' regions, we also replicate combinational (and hybrid) logic that +depends on top level inputs. These become a separate 'ico' region (Input +Combinational logic), which we will always evaluate at the beginning of a +time-step to ensure the combinational invariant holds even if input signals +have changed. Note that this eliminates the need of changing data and clock +signals on separate evaluations, as was necessary with earlier versions of +Verilator). + + +Constructing the top level `_eval` function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To construct the top level `_eval` function, which updates the state of the +circuit to the end of the current time step, we invoke ``V3Order::order`` +separately on the 'ico', 'act' and 'nba' logic, which yields the `_eval_ico`, +`_eval_act`, and `_eval_nba` functions. We then put these all together with the +corresponding functions that compute the respective trigger expressions into +the top level `_eval` function, which on the high level has the form: + +:: + void _eval() { + // Update combinational logic dependent on top level inptus ('ico' region) + while (true) { + _eval__triggers__ico(); + // If no 'ico' region trigger is active + if (!ico_triggers.any()) break; + _eval_ico(); + } + + + // Iterate 'act' and 'nba' regions together + while (true) { + + // Iterate 'act' region, this computes all derived clocks updaed in the + // Active scheduling region, but does not commit any NBAs that executed + // in 'act' region logic. + while (true) { + _eval__triggers__act(); + // If no 'act' region trigger is active + if (!act_triggers.any()) break; + // Remember what 'act' triggers were active, 'nba' uses the same + latch_act_triggers_for_nba(); + _eval_act(); + } + + + // If no 'nba' region trigger is active + if (!nba_triggers.any()) break; + + // Evaluate all other Active region logic, and commti NBAs + _eval_nba(); + } + } + + Multithreaded Mode ------------------ diff --git a/include/verilated_types.h b/include/verilated_types.h index 8e8da1941..614c6b4cc 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -29,6 +29,7 @@ #endif #include +#include #include #include #include @@ -70,6 +71,67 @@ extern std::string VL_TO_STRING_W(int words, const WDataInP obj); #define VL_OUT(name, msb, lsb) IData name ///< Declare output signal, 17-32 bits #define VL_OUTW(name, msb, lsb, words) VlWide name ///< Declare output signal, 65+ bits +//=================================================================== +// Activity trigger vector + +template // +class VlTriggerVec final { +private: + // MEMBERS + std::array 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& 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& a, const VlTriggerVec& b) { + for (size_t i = 0; i < T_size; ++i) m_flags[i] = a.m_flags[i] & !b.m_flags[i]; + } +}; + +//=================================================================== +// SystemVerilog event type + +class VlEvent final { + // MEMBERS + bool m_fired = false; // Fired on this scheduling iteration + bool m_triggered = false; // Triggered state of event persisting until next time step + +public: + // CONSTRUCTOR + VlEvent() = default; + ~VlEvent() = default; + + // METHODS + void fire() { m_fired = m_triggered = true; } + bool isFired() const { return m_fired; } + bool isTriggered() const { return m_triggered; } + void clearFired() { m_fired = false; } + void clearTriggered() { m_triggered = false; } +}; + //=================================================================== // Shuffle RNG @@ -860,6 +922,13 @@ template struct VlUnpacked final { T_Value& operator[](size_t index) { return m_storage[index]; } const T_Value& operator[](size_t index) const { return m_storage[index]; } + bool operator!=(const VlUnpacked& that) const { + for (int i = 0; i < T_Depth; ++i) { + if (m_storage[i] != that.m_storage[i]) return true; + } + return false; + } + // Dumping. Verilog: str = $sformatf("%p", assoc) std::string to_string() const { std::string out = "'{"; diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 140be6086..97acb8090 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -166,7 +166,6 @@ RAW_OBJS = \ V3Case.o \ V3Cast.o \ V3Cdc.o \ - V3Changed.o \ V3Class.o \ V3Clean.o \ V3Clock.o \ @@ -201,7 +200,6 @@ RAW_OBJS = \ V3FileLine.o \ V3Force.o \ V3Gate.o \ - V3GenClk.o \ V3Global.o \ V3Graph.o \ V3GraphAlg.o \ @@ -239,6 +237,10 @@ RAW_OBJS = \ V3ProtectLib.o \ V3Randomize.o \ V3Reloop.o \ + V3Sched.o \ + V3SchedAcyclic.o \ + V3SchedPartition.o \ + V3SchedReplicate.o \ V3Scope.o \ V3Scoreboard.o \ V3Slice.o \ diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 1b9e9534c..45f7433f9 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -206,8 +206,10 @@ class ActiveNamer final : public ActiveBaseVisitor { private: // STATE AstScope* m_scopep = nullptr; // Current scope to add statement to - AstActive* m_iActivep = nullptr; // For current scope, the IActive we're building - AstActive* m_cActivep = nullptr; // For current scope, the SActive(combo) we're building + AstActive* m_sActivep = nullptr; // For current scope, the Static active we're building + AstActive* m_iActivep = nullptr; // For current scope, the Initial active we're building + AstActive* m_fActivep = nullptr; // For current scope, the Final active we're building + AstActive* m_cActivep = nullptr; // For current scope, the Combo active we're building // Map from AstSenTree (equivalence) to the corresponding AstActive created. std::unordered_map, AstActive*> m_activeMap; @@ -217,10 +219,13 @@ private: UASSERT_OBJ(m_scopep, nodep, "nullptr scope"); m_scopep->addActivep(nodep); } + // VISITORS virtual void visit(AstScope* nodep) override { m_scopep = nodep; + m_sActivep = nullptr; m_iActivep = nullptr; + m_fActivep = nullptr; m_cActivep = nullptr; m_activeMap.clear(); iterateChildren(nodep); @@ -234,30 +239,28 @@ private: virtual void visit(AstNodeStmt*) override {} // Accelerate virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } + // Specialized below for the special sensitivity classes + template AstActive*& getSpecialActive(); + public: // METHODS AstScope* scopep() { return m_scopep; } - AstActive* getCActive(FileLine* fl) { - if (!m_cActivep) { - m_cActivep = new AstActive( - fl, "combo", new AstSenTree(fl, new AstSenItem(fl, AstSenItem::Combo()))); - m_cActivep->sensesStorep(m_cActivep->sensesp()); - addActive(m_cActivep); + + // Return an AstActive sensitive to the given special sensitivity class + template AstActive* getSpecialActive(FileLine* fl) { + AstActive*& cachep = getSpecialActive(); + if (!cachep) { + AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, SenItemKind{}}}; + cachep = new AstActive{fl, "", senTreep}; + cachep->sensesStorep(cachep->sensesp()); + addActive(cachep); } - return m_cActivep; - } - AstActive* getIActive(FileLine* fl) { - if (!m_iActivep) { - m_iActivep = new AstActive( - fl, "initial", new AstSenTree(fl, new AstSenItem(fl, AstSenItem::Initial()))); - m_iActivep->sensesStorep(m_iActivep->sensesp()); - addActive(m_iActivep); - } - return m_iActivep; + return cachep; } // Return an AstActive that is sensitive to a SenTree equivalent to the given sentreep. AstActive* getActive(FileLine* fl, AstSenTree* sensesp) { + UASSERT(sensesp, "Must be non-null"); auto it = m_activeMap.find(*sensesp); // If found matching AstActive, return it @@ -278,6 +281,11 @@ public: void main(AstScope* nodep) { iterate(nodep); } }; +template <> AstActive*& ActiveNamer::getSpecialActive() { return m_sActivep; } +template <> AstActive*& ActiveNamer::getSpecialActive() { return m_iActivep; } +template <> AstActive*& ActiveNamer::getSpecialActive() { return m_fActivep; } +template <> AstActive*& ActiveNamer::getSpecialActive() { return m_cActivep; } + //###################################################################### // Latch checking visitor @@ -312,10 +320,10 @@ private: public: // CONSTRUCTORS - ActiveLatchCheckVisitor(AstNode* nodep, VAlwaysKwd kwd) { + ActiveLatchCheckVisitor(AstNode* nodep, bool expectLatch) { m_graph.begin(); iterate(nodep); - m_graph.latchCheck(nodep, kwd == VAlwaysKwd::ALWAYS_LATCH); + m_graph.latchCheck(nodep, expectLatch); } virtual ~ActiveLatchCheckVisitor() = default; }; @@ -397,87 +405,24 @@ class ActiveVisitor final : public ActiveBaseVisitor { private: // NODE STATE // Each call to V3Const::constify + // AstVarScope::user1() bool: This VarScope is referenced in the sensitivity list + // AstVarScope::user2() bool: This VarScope is written in the current process // AstNode::user4() Used by V3Const::constify, called below // STATE ActiveNamer m_namer; // Tracking of active names - AstCFunc* m_scopeFinalp = nullptr; // Final function for this scope - bool m_itemCombo = false; // Found a SenItem combo - bool m_itemSequent = false; // Found a SenItem sequential - - // VISITORS - virtual void visit(AstScope* nodep) override { - // Create required actives and add to scope - UINFO(4, " SCOPE " << nodep << endl); - // Clear last scope's names, and collect this scope's existing names - m_namer.main(nodep); - m_scopeFinalp = nullptr; - iterateChildren(nodep); - } - virtual void visit(AstActive* nodep) override { - // Actives are being formed, so we can ignore any already made - } - virtual void visit(AstInitialStatic* nodep) override { - // Relink to IACTIVE, unless already under it - UINFO(4, " INITIAL " << nodep << endl); - const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL}; - AstActive* const wantactivep = m_namer.getIActive(nodep->fileline()); - nodep->unlinkFrBack(); - wantactivep->addStmtsp(nodep); - } - virtual void visit(AstInitial* nodep) override { - // Relink to IACTIVE, unless already under it - UINFO(4, " INITIAL " << nodep << endl); - const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL}; - AstActive* const wantactivep = m_namer.getIActive(nodep->fileline()); - nodep->unlinkFrBack(); - wantactivep->addStmtsp(nodep); - } - virtual void visit(AstAssignAlias* nodep) override { - // Relink to CACTIVE, unless already under it - UINFO(4, " ASSIGNW " << nodep << endl); - AstActive* const wantactivep = m_namer.getCActive(nodep->fileline()); - nodep->unlinkFrBack(); - wantactivep->addStmtsp(nodep); - } - virtual void visit(AstAssignW* nodep) override { - // Relink to CACTIVE, unless already under it - UINFO(4, " ASSIGNW " << nodep << endl); - AstActive* const wantactivep = m_namer.getCActive(nodep->fileline()); - nodep->unlinkFrBack(); - wantactivep->addStmtsp(nodep); - } - virtual void visit(AstCoverToggle* nodep) override { - // Relink to CACTIVE, unless already under it - UINFO(4, " COVERTOGGLE " << nodep << endl); - AstActive* const wantactivep = m_namer.getCActive(nodep->fileline()); - nodep->unlinkFrBack(); - wantactivep->addStmtsp(nodep); - } - virtual void visit(AstFinal* nodep) override { - // Relink to CFUNC for the final - UINFO(4, " FINAL " << nodep << endl); - if (!nodep->bodysp()) { // Empty, Kill it. - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - return; - } - const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL}; - if (!m_scopeFinalp) { - m_scopeFinalp = new AstCFunc( - nodep->fileline(), "_final_" + m_namer.scopep()->nameDotless(), m_namer.scopep()); - m_scopeFinalp->dontCombine(true); - m_scopeFinalp->isFinal(true); - m_scopeFinalp->isStatic(false); - m_scopeFinalp->isLoose(true); - m_scopeFinalp->slow(true); - m_namer.scopep()->addActivep(m_scopeFinalp); - } - nodep->unlinkFrBack(); - m_scopeFinalp->addStmtsp(nodep->bodysp()->unlinkFrBackWithNext()); - VL_DO_DANGLING(nodep->deleteTree(), nodep); - } + bool m_clockedProcess = false; // Whether current process is a clocked process + bool m_allChanged = false; // Whether all SenItem in the SenTree are ET_CHANGED + bool m_walkingBody = false; // Walking body of a process + bool m_canBeComb = false; // Whether current clocked process can be turned into a comb process // METHODS + template void moveUnderSpecial(AstNode* nodep) { + AstActive* const wantactivep = m_namer.getSpecialActive(nodep->fileline()); + nodep->unlinkFrBack(); + wantactivep->addStmtsp(nodep); + } + void visitAlways(AstNode* nodep, AstSenTree* oldsensesp, VAlwaysKwd kwd) { // Move always to appropriate ACTIVE based on its sense list if (oldsensesp && oldsensesp->sensesp() && oldsensesp->sensesp()->isNever()) { @@ -488,112 +433,152 @@ private: return; } - // Read sensitivities - m_itemCombo = false; - m_itemSequent = false; - iterateAndNextNull(oldsensesp); - bool combo = m_itemCombo; - bool sequent = m_itemSequent; + { + const VNUser1InUse user1InUse; - if (!combo && !sequent) combo = true; // If no list, Verilog 2000: always @ (*) - if (combo && sequent) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: Mixed edge (pos/negedge) and activity " - "(no edge) sensitive activity list"); - sequent = false; - } - - AstActive* wantactivep = nullptr; - if (combo && !sequent) { - // Combo: Relink to ACTIVE(combo) - wantactivep = m_namer.getCActive(nodep->fileline()); - } else { - // Sequential: Build a ACTIVE(name) - // OPTIMIZE: We could substitute a constant for things in the sense list, for example - // always (posedge RESET) { if (RESET).... } we know RESET is true. - // Summarize a long list of combo inputs as just "combo" -#ifndef __COVERITY__ // Else dead code on next line. - if (combo) { - oldsensesp->addSensesp(new AstSenItem(nodep->fileline(), AstSenItem::Combo())); + // Walk sensitivity list + m_clockedProcess = false; + m_allChanged = true; + if (oldsensesp) { + oldsensesp->unlinkFrBack(); + iterateChildrenConst(oldsensesp); + } + + // If all SenItems are ET_CHANGE, then walk the body to determine if this process + // could be turned into a combinational process instead. + if (m_allChanged) { + const VNUser2InUse user2InUse; + m_walkingBody = true; + m_canBeComb = true; + iterateChildrenConst(nodep); + m_walkingBody = false; + if (m_canBeComb) m_clockedProcess = false; } -#endif - wantactivep = m_namer.getActive(nodep->fileline(), oldsensesp); } + AstActive* const wantactivep + = m_clockedProcess ? m_namer.getActive(nodep->fileline(), oldsensesp) + : m_namer.getSpecialActive(nodep->fileline()); + // Delete sensitivity list - if (oldsensesp) { - VL_DO_DANGLING(oldsensesp->unlinkFrBackWithNext()->deleteTree(), oldsensesp); - } + if (oldsensesp) VL_DO_DANGLING(oldsensesp->deleteTree(), oldsensesp); // Move node to new active nodep->unlinkFrBack(); wantactivep->addStmtsp(nodep); - // Warn and/or convert any delayed assignments - if (combo && !sequent) { - ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_COMB}; - const ActiveLatchCheckVisitor latchvisitor{nodep, kwd}; - } else if (!combo && sequent) { - ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_SEQ}; + // Warn and convert any delayed assignments + ActiveDlyVisitor{nodep, + m_clockedProcess ? ActiveDlyVisitor::CT_SEQ : ActiveDlyVisitor::CT_COMB}; + + // check combinational processes for latches + if (!m_clockedProcess || kwd == VAlwaysKwd::ALWAYS_LATCH) { + const ActiveLatchCheckVisitor latchvisitor{nodep, kwd == VAlwaysKwd::ALWAYS_LATCH}; } } - virtual void visit(AstAlways* nodep) override { - // Move always to appropriate ACTIVE based on its sense list - UINFO(4, " ALW " << nodep << endl); - // if (debug() >= 9) nodep->dumpTree(cout, " Alw: "); - if (!nodep->bodysp()) { - // Empty always. Kill it. + // VISITORS + virtual void visit(AstScope* nodep) override { + m_namer.main(nodep); // Clear last scope's names, and collect this scope's existing names + iterateChildren(nodep); + } + virtual void visit(AstActive* nodep) override { + // Actives are being formed, so we can ignore any already made + } + + virtual void visit(AstInitialStatic* nodep) override { + moveUnderSpecial(nodep); + } + virtual void visit(AstInitial* nodep) override { + const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL}; + moveUnderSpecial(nodep); + } + virtual void visit(AstFinal* nodep) override { + const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL}; + moveUnderSpecial(nodep); + } + virtual void visit(AstAssignAlias* nodep) override { + moveUnderSpecial(nodep); + } + virtual void visit(AstCoverToggle* nodep) override { + moveUnderSpecial(nodep); + } + virtual void visit(AstAssignW* nodep) override { + visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS_COMB); + } + virtual void visit(AstAlways* nodep) override { + if (!nodep->bodysp()) { // Empty always. Remove it now. VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); return; } visitAlways(nodep, nodep->sensesp(), nodep->keyword()); } virtual void visit(AstAlwaysPostponed* nodep) override { - UINFO(4, " ALW " << nodep << endl); - if (!nodep->bodysp()) { + if (!nodep->bodysp()) { // Empty always. Remove it now. VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); return; } visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS); } virtual void visit(AstAlwaysPublic* nodep) override { - // Move always to appropriate ACTIVE based on its sense list - UINFO(4, " ALWPub " << nodep << endl); - // if (debug() >= 9) nodep->dumpTree(cout, " Alw: "); visitAlways(nodep, nodep->sensesp(), VAlwaysKwd::ALWAYS); } + virtual void visit(AstSenItem* nodep) override { + UASSERT_OBJ(!m_walkingBody, nodep, "Should not reach here when walking body"); + if (!nodep->sensp()) return; // Ignore sequential items (e.g.: initial, comb, etc.) + + m_clockedProcess = true; + if (nodep->edgeType() != VEdgeType::ET_CHANGED) m_allChanged = false; + if (nodep->varrefp()) { if (const AstBasicDType* const basicp = nodep->varrefp()->dtypep()->basicp()) { - if (basicp->isEventValue()) { - // Events need to be treated as active high so we only activate on event being - // 1 - UINFO(8, "Demote event to HIGHEDGE " << nodep << endl); - nodep->edgeType(VEdgeType::ET_HIGHEDGE); - } + if (basicp->isEvent()) nodep->edgeType(VEdgeType::ET_EVENT); } } - if (nodep->edgeType() == VEdgeType::ET_ANYEDGE) { - m_itemCombo = true; - // Delete the sensitivity - // We'll add it as a generic COMBO SenItem in a moment. - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } else if (nodep->varrefp()) { - // V3LinkResolve should have cleaned most of these up - if (!nodep->varrefp()->width1()) { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: Non-single bit wide signal pos/negedge sensitivity: " - << nodep->varrefp()->prettyNameQ()); - } - m_itemSequent = true; - nodep->varrefp()->varp()->usedClock(true); + + nodep->sensp()->foreach([](const AstVarRef* refp) { + refp->varp()->usedClock(true); + refp->varScopep()->user1(true); + }); + } + + virtual void visit(AstVarRef* nodep) override { + AstVarScope* const vscp = nodep->varScopep(); + if (nodep->access().isWriteOnly()) { + vscp->user2(true); + } else { + // If the variable is read before it is written, and is not in the sensitivity list, + // then this cannot be optimized into a combinational process + // TODO: live variable analysis would be more precise + if (!vscp->user2() && !vscp->user1()) m_canBeComb = false; } } + virtual void visit(AstAssignDly* nodep) override { + m_canBeComb = false; + iterateChildrenConst(nodep); + } + virtual void visit(AstFireEvent* nodep) override { + m_canBeComb = false; + iterateChildrenConst(nodep); + } + virtual void visit(AstAssignForce* nodep) override { + m_canBeComb = false; + iterateChildrenConst(nodep); + } + virtual void visit(AstRelease* nodep) override { + m_canBeComb = false; + iterateChildrenConst(nodep); + } //-------------------- - virtual void visit(AstNodeMath*) override {} // Accelerate + virtual void visit(AstVar*) override {} // Accelerate virtual void visit(AstVarScope*) override {} // Accelerate - virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } + virtual void visit(AstNode* nodep) override { + if (m_walkingBody && !m_canBeComb) return; // Accelerate + if (!nodep->isPure()) m_canBeComb = false; + iterateChildren(nodep); + } public: // CONSTRUCTORS diff --git a/src/V3ActiveTop.cpp b/src/V3ActiveTop.cpp index f4f9782b1..2d255f12e 100644 --- a/src/V3ActiveTop.cpp +++ b/src/V3ActiveTop.cpp @@ -36,20 +36,28 @@ // Active class functions class ActiveTopVisitor final : public VNVisitor { -private: - // NODE STATE - // Entire netlist - // AstNode::user() bool. True if processed - // Each call to V3Const::constify - // AstNode::user4() Used by V3Const::constify, called below - const VNUser1InUse m_inuser1; - // STATE SenTreeFinder m_finder; // Find global sentree's / add them under the AstTopScope // METHODS VL_DEBUG_FUNC; // Declare debug() + static bool isInitial(AstNode* nodep) { + const VNUser1InUse user1InUse; + // Return true if no variables that read. + return nodep->forall([&](const AstVarRef* refp) -> bool { + AstVarScope* const vscp = refp->varScopep(); + // Note: Use same heuristic as ordering does to ignore written variables + // TODO: Use live variable analysis. + if (refp->access().isWriteOnly()) { + vscp->user1(true); + return true; + } + // Read or ReadWrite: OK if written before + return vscp->user1(); + }); + } + // VISITORS virtual void visit(AstNodeModule* nodep) override { // Create required actives and add to module @@ -70,15 +78,6 @@ private: VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); return; } - // Copy combo tree to settlement tree with duplicated statements - if (sensesp->hasCombo()) { - AstSenTree* const newsentreep = new AstSenTree( - nodep->fileline(), new AstSenItem(nodep->fileline(), AstSenItem::Settle())); - AstActive* const newp = new AstActive(nodep->fileline(), "settle", newsentreep); - newp->sensesStorep(newsentreep); - if (nodep->stmtsp()) newp->addStmtsp(nodep->stmtsp()->cloneTree(true)); - nodep->addNextHere(newp); - } // Move the SENTREE for each active up to the global level. // This way we'll easily see what clock domains are identical AstSenTree* const wantp = m_finder.getSenTree(sensesp); @@ -97,8 +96,23 @@ private: } nodep->sensesp(wantp); } - // No need to do statements under it, they're already moved. - // iterateChildren(nodep); + + // If this is combinational logic that does not read any variables, then it really is an + // initial block in disguise, so move such logic under an Initial AstActive, V3Order would + // prune these otherwise. + // TODO: we should warn for these if they were 'always @*' as some (including strictly + // compliant) simulators will never execute these. + if (nodep->sensesp()->hasCombo()) { + FileLine* const flp = nodep->fileline(); + AstActive* initialp = nullptr; + for (AstNode *logicp = nodep->stmtsp(), *nextp; logicp; logicp = nextp) { + nextp = logicp->nextp(); + if (!isInitial(logicp)) continue; + if (!initialp) initialp = new AstActive{flp, "", m_finder.getInitial()}; + initialp->addStmtsp(logicp->unlinkFrBack()); + } + if (initialp) nodep->addHereThisAsNext(initialp); + } } virtual void visit(AstNodeProcedure* nodep) override { // LCOV_EXCL_LINE nodep->v3fatalSrc("Node should have been under ACTIVE"); diff --git a/src/V3Ast.h b/src/V3Ast.h index ca00b8040..547bc8308 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -261,66 +261,87 @@ public: // in V3Const::visit AstSenTree ET_ILLEGAL, // Involving a variable - ET_ANYEDGE, // Default for sensitivities; rip them out - ET_BOTHEDGE, // POSEDGE | NEGEDGE + ET_CHANGED, // Value changed + ET_BOTHEDGE, // POSEDGE | NEGEDGE (i.e.: 'edge' in Verilog) ET_POSEDGE, ET_NEGEDGE, - ET_HIGHEDGE, // Is high now (latches) - ET_LOWEDGE, // Is low now (latches) - // Not involving anything + ET_EVENT, // VlEvent::isFired + ET_DPIEXPORT, // Used exclusively to check the AstNetlist::dpiExportTriggerp() + // Involving an expression + ET_TRUE, + // ET_COMBO, // Sensitive to all combo inputs to this block - ET_INITIAL, // User initial statements - ET_SETTLE, // Like combo but for initial wire resolutions after initial statement + ET_HYBRID, // This is like ET_COMB, but with explicit sensitivity to an expression + ET_STATIC, // static variable initializers (runs before 'initial') + ET_INITIAL, // 'initial' statements + ET_FINAL, // 'final' statements ET_NEVER // Never occurs (optimized away) }; enum en m_e; bool clockedStmt() const { - static const bool clocked[] - = {false, false, true, true, true, true, true, false, false, false}; + static const bool clocked[] = { + false, // ET_ILLEGAL + + true, // ET_CHANGED + true, // ET_BOTHEDGE + true, // ET_POSEDGE + true, // ET_NEGEDGE + true, // ET_EVENT + true, // ET_DPIEXPORT + true, // ET_TRUE + + false, // ET_COMBO + false, // ET_HYBRID + false, // ET_STATIC + false, // ET_INITIAL + false, // ET_FINAL + false, // ET_NEVER + }; return clocked[m_e]; } VEdgeType invert() const { switch (m_e) { - case ET_ANYEDGE: return ET_ANYEDGE; + case ET_CHANGED: return ET_CHANGED; case ET_BOTHEDGE: return ET_BOTHEDGE; case ET_POSEDGE: return ET_NEGEDGE; case ET_NEGEDGE: return ET_POSEDGE; - case ET_HIGHEDGE: return ET_LOWEDGE; - case ET_LOWEDGE: return ET_HIGHEDGE; default: UASSERT_STATIC(0, "Inverting bad edgeType()"); } return VEdgeType::ET_ILLEGAL; } const char* ascii() const { - static const char* const names[] - = {"%E-edge", "ANY", "BOTH", "POS", "NEG", "HIGH", - "LOW", "COMBO", "INITIAL", "SETTLE", "NEVER"}; + static const char* const names[] = {"%E-edge", + "CHANGED", + "BOTH", + "POS", + "NEG", + "EVENT", + "DPIEXPORT" + "TRUE", + "COMBO", + "HYBRID", + "STATIC", + "INITIAL", + "FINAL", + "NEVER"}; return names[m_e]; } const char* verilogKwd() const { - static const char* const names[] - = {"%E-edge", "[any]", "edge", "posedge", "negedge", "[high]", - "[low]", "*", "[initial]", "[settle]", "[never]"}; + static const char* const names[] = { + "%E-edge", "[changed]", "edge", "posedge", "negedge", "[event]", "[dpiexport]", + "[true]", "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; return names[m_e]; } // Return true iff this and the other have mutually exclusive transitions bool exclusiveEdge(const VEdgeType& other) const { switch (m_e) { case VEdgeType::ET_POSEDGE: - switch (other.m_e) { - case VEdgeType::ET_NEGEDGE: // FALLTHRU - case VEdgeType::ET_LOWEDGE: return true; - default:; - } + if (other.m_e == VEdgeType::ET_NEGEDGE) return true; break; case VEdgeType::ET_NEGEDGE: - switch (other.m_e) { - case VEdgeType::ET_POSEDGE: // FALLTHRU - case VEdgeType::ET_HIGHEDGE: return true; - default:; - } + if (other.m_e == VEdgeType::ET_POSEDGE) return true; break; - default:; + default: break; } return false; } @@ -371,7 +392,7 @@ public: TYPENAME, // V3Width processes // VAR_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes - VAR_CLOCK_ENABLE, // V3LinkParse moves to AstVar::attrClockEn + VAR_CLOCK_ENABLE, // Ignored, accepted for compatibility VAR_FORCEABLE, // V3LinkParse moves to AstVar::isForceable VAR_PUBLIC, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic @@ -427,7 +448,7 @@ public: BIT, BYTE, CHANDLE, - EVENTVALUE, // See comments in t_event_copy as to why this is EVENTVALUE + EVENT, INT, INTEGER, LOGIC, @@ -441,6 +462,7 @@ public: SCOPEPTR, CHARPTR, MTASKSTATE, + TRIGGERVEC, // Unsigned and two state; fundamental types UINT32, UINT64, @@ -452,18 +474,20 @@ public: enum en m_e; const char* ascii() const { static const char* const names[] - = {"%E-unk", "bit", "byte", "chandle", "event", - "int", "integer", "logic", "longint", "real", - "shortint", "time", "string", "VerilatedScope*", "char*", - "VlMTaskState", "IData", "QData", "LOGIC_IMPLICIT", " MAX"}; + = {"%E-unk", "bit", "byte", "chandle", "event", + "int", "integer", "logic", "longint", "real", + "shortint", "time", "string", "VerilatedScope*", "char*", + "VlMTaskState", "VlTriggerVec", "IData", "QData", "LOGIC_IMPLICIT", + " MAX"}; return names[m_e]; } const char* dpiType() const { static const char* const names[] - = {"%E-unk", "svBit", "char", "void*", "char", - "int", "%E-integer", "svLogic", "long long", "double", - "short", "%E-time", "const char*", "dpiScope", "const char*", - "%E-mtaskstate", "IData", "QData", "%E-logic-implct", " MAX"}; + = {"%E-unk", "svBit", "char", "void*", "char", + "int", "%E-integer", "svLogic", "long long", "double", + "short", "%E-time", "const char*", "dpiScope", "const char*", + "%E-mtaskstate", "%E-triggervec", "IData", "QData", "%E-logic-implct", + " MAX"}; return names[m_e]; } static void selfTest() { @@ -484,7 +508,7 @@ public: case BIT: return 1; // scalar, can't bit extract unless ranged case BYTE: return 8; case CHANDLE: return 64; - case EVENTVALUE: return 1; + case EVENT: return 1; case INT: return 32; case INTEGER: return 32; case LOGIC: return 1; // scalar, can't bit extract unless ranged @@ -496,6 +520,7 @@ public: case SCOPEPTR: return 0; // opaque case CHARPTR: return 0; // opaque case MTASKSTATE: return 0; // opaque + case TRIGGERVEC: return 0; // opaque case UINT32: return 32; case UINT64: return 64; default: return 0; @@ -506,15 +531,14 @@ public: || m_e == DOUBLE; } bool isUnsigned() const { - return m_e == CHANDLE || m_e == EVENTVALUE || m_e == STRING || m_e == SCOPEPTR - || m_e == CHARPTR || m_e == UINT32 || m_e == UINT64 || m_e == BIT || m_e == LOGIC - || m_e == TIME; + return m_e == CHANDLE || m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR + || m_e == UINT32 || m_e == UINT64 || m_e == BIT || m_e == LOGIC || m_e == TIME; } bool isFourstate() const { return m_e == INTEGER || m_e == LOGIC || m_e == LOGIC_IMPLICIT || m_e == TIME; } bool isZeroInit() const { // Otherwise initializes to X - return (m_e == BIT || m_e == BYTE || m_e == CHANDLE || m_e == EVENTVALUE || m_e == INT + return (m_e == BIT || m_e == BYTE || m_e == CHANDLE || m_e == EVENT || m_e == INT || m_e == LONGINT || m_e == SHORTINT || m_e == STRING || m_e == DOUBLE); } bool isIntNumeric() const { // Enum increment supported @@ -532,11 +556,11 @@ public: || m_e == DOUBLE || m_e == SHORTINT || m_e == UINT32 || m_e == UINT64); } bool isOpaque() const { // IE not a simple number we can bit optimize - return (m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR || m_e == MTASKSTATE - || m_e == DOUBLE); + return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR + || m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DOUBLE); } bool isDouble() const { return m_e == DOUBLE; } - bool isEventValue() const { return m_e == EVENTVALUE; } + bool isEvent() const { return m_e == EVENT; } bool isString() const { return m_e == STRING; } bool isMTaskState() const { return m_e == MTASKSTATE; } // Does this represent a C++ LiteralType? (can be constexpr) @@ -2113,6 +2137,15 @@ template <> inline bool AstNode::privateMayBeUnder(const AstNode* if (VN_IS(nodep, NodeStmt)) return false; // Should be directly under CFunc return true; } +template <> inline bool AstNode::privateMayBeUnder(const AstNode* nodep) { + return !VN_IS(nodep, Active); // AstActives do not nest +} +template <> inline bool AstNode::privateMayBeUnder(const AstNode* nodep) { + return !VN_IS(nodep, Scope); // AstScopes do not nest +} +template <> inline bool AstNode::privateMayBeUnder(const AstNode* nodep) { + return !VN_IS(nodep, SenTree); // AstSenTree do not nest +} inline std::ostream& operator<<(std::ostream& os, const AstNode* rhs) { if (!rhs) { @@ -2137,7 +2170,7 @@ public: VNRef(U&& x) : std::reference_wrapper{x} {} - VNRef(const VNRef& other) noexcept + VNRef(const std::reference_wrapper& other) : std::reference_wrapper{other} {} }; @@ -2655,6 +2688,7 @@ public: return text() == asamep->text(); } const string& text() const { return m_text; } + void text(const string& value) { m_text = value; } }; class AstNodeDType VL_NOT_FINAL : public AstNode { diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index fc6999853..920e6d5bd 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -706,6 +706,10 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const { info.m_type = "std::string"; } else if (bdtypep->keyword().isMTaskState()) { info.m_type = "VlMTaskVertex"; + } else if (bdtypep->isTriggerVec()) { + info.m_type = "VlTriggerVec<" + cvtToStr(dtypep->width()) + ">"; + } else if (bdtypep->isEvent()) { + info.m_type = "VlEvent"; } else if (dtypep->widthMin() <= 8) { // Handle unpacked arrays; not bdtypep->width info.m_type = "CData" + bitvec; } else if (dtypep->widthMin() <= 16) { @@ -854,6 +858,29 @@ string AstScope::nameDotless() const { return out; } +AstVarScope* AstScope::createTemp(const string& name, unsigned width) { + FileLine* const flp = fileline(); + AstVar* const varp + = new AstVar{flp, VVarType::MODULETEMP, name, VFlagBitPacked{}, static_cast(width)}; + modp()->addStmtp(varp); + AstVarScope* const vscp = new AstVarScope{flp, this, varp}; + addVarp(vscp); + return vscp; +} + +AstVarScope* AstScope::createTemp(const string& name, AstNodeDType* dtypep) { + FileLine* const flp = fileline(); + AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep}; + modp()->addStmtp(varp); + AstVarScope* const vscp = new AstVarScope{flp, this, varp}; + addVarp(vscp); + return vscp; +} + +AstVarScope* AstScope::createTempLike(const string& name, AstVarScope* vscp) { + return createTemp(name, vscp->dtypep()); +} + string AstScopeName::scopePrettyNameFormatter(AstText* scopeTextp) const { string out; for (AstText* textp = scopeTextp; textp; textp = VN_AS(textp->nextp(), Text)) { @@ -886,10 +913,10 @@ bool AstSenTree::hasClocked() const { } return false; } -bool AstSenTree::hasSettle() const { +bool AstSenTree::hasStatic() const { UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it"); for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) { - if (senp->isSettle()) return true; + if (senp->isStatic()) return true; } return false; } @@ -900,6 +927,13 @@ bool AstSenTree::hasInitial() const { } return false; } +bool AstSenTree::hasFinal() const { + UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it"); + for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) { + if (senp->isFinal()) return true; + } + return false; +} bool AstSenTree::hasCombo() const { UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it"); for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) { @@ -907,6 +941,13 @@ bool AstSenTree::hasCombo() const { } return false; } +bool AstSenTree::hasHybrid() const { + UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it"); + for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) { + if (senp->isHybrid()) return true; + } + return false; +} AstTypeTable::AstTypeTable(FileLine* fl) : ASTGEN_SUPER_TypeTable(fl) { @@ -1738,7 +1779,6 @@ void AstVar::dump(std::ostream& str) const { if (isSigPublic()) str << " [P]"; if (isLatched()) str << " [LATCHED]"; if (isUsedLoopIdx()) str << " [LOOP]"; - if (attrClockEn()) str << " [aCLKEN]"; if (attrIsolateAssign()) str << " [aISO]"; if (attrFileDescr()) str << " [aFD]"; if (isFuncReturn()) { diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index 5082ae08d..7bb29ab21 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -934,7 +934,8 @@ public: } bool isBitLogic() const { return keyword().isBitLogic(); } bool isDouble() const { return keyword().isDouble(); } - bool isEventValue() const { return keyword().isEventValue(); } + bool isEvent() const { return keyword() == VBasicDTypeKwd::EVENT; } + bool isTriggerVec() const { return keyword() == VBasicDTypeKwd::TRIGGERVEC; } bool isOpaque() const { return keyword().isOpaque(); } bool isString() const { return keyword().isString(); } bool isZeroInit() const { return keyword().isZeroInit(); } @@ -1979,7 +1980,6 @@ private: bool m_usedLoopIdx : 1; // Variable subject of for unrolling bool m_funcLocal : 1; // Local variable for a function bool m_funcReturn : 1; // Return variable for a function - bool m_attrClockEn : 1; // User clock enable attribute bool m_attrScBv : 1; // User force bit vector attribute bool m_attrIsolateAssign : 1; // User isolate_assignments attribute bool m_attrSFormat : 1; // User sformat attribute @@ -2019,7 +2019,6 @@ private: m_sigUserRWPublic = false; m_funcLocal = false; m_funcReturn = false; - m_attrClockEn = false; m_attrScBv = false; m_attrIsolateAssign = false; m_attrSFormat = false; @@ -2157,7 +2156,6 @@ public: virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); } void ansi(bool flag) { m_ansi = flag; } void declTyped(bool flag) { m_declTyped = flag; } - void attrClockEn(bool flag) { m_attrClockEn = flag; } void attrClocker(VVarAttrClocker flag) { m_attrClocker = flag; } void attrFileDescr(bool flag) { m_fileDescr = flag; } void attrScClocked(bool flag) { m_scClocked = flag; } @@ -2262,7 +2260,6 @@ public: bool isFuncReturn() const { return m_funcReturn; } bool isPullup() const { return m_isPullup; } bool isPulldown() const { return m_isPulldown; } - bool attrClockEn() const { return m_attrClockEn; } bool attrScBv() const { return m_attrScBv; } bool attrFileDescr() const { return m_fileDescr; } bool attrScClocked() const { return m_scClocked; } @@ -2276,14 +2273,13 @@ public: void propagateAttrFrom(AstVar* fromp) { // This is getting connected to fromp; keep attributes // Note the method below too - if (fromp->attrClockEn()) attrClockEn(true); if (fromp->attrFileDescr()) attrFileDescr(true); if (fromp->attrIsolateAssign()) attrIsolateAssign(true); if (fromp->isContinuously()) isContinuously(true); } bool gateMultiInputOptimizable() const { // Ok to gate optimize; must return false if propagateAttrFrom would do anything - return (!attrClockEn() && !isUsedClock()); + return !isUsedClock(); } void combineType(AstVar* typevarp) { // This is same as typevarp (for combining input & reg decls) @@ -2372,15 +2368,20 @@ public: string nameDotless() const; string nameVlSym() const { return ((string("vlSymsp->")) + nameDotless()); } AstNodeModule* modp() const { return m_modp; } + // op1: AstVarScope's + AstVarScope* varsp() const { return VN_AS(op1p(), VarScope); } void addVarp(AstVarScope* nodep) { addOp1p((AstNode*)nodep); } - AstVarScope* varsp() const { return VN_AS(op1p(), VarScope); } // op1 = AstVarScope's + // op2: Logic blocks/AstActive/AstExecGraph + AstNode* blocksp() const { return op2p(); } void addActivep(AstNode* nodep) { addOp2p(nodep); } - AstNode* blocksp() const { return op2p(); } // op2 = Block names - void addFinalClkp(AstNode* nodep) { addOp3p(nodep); } - AstNode* finalClksp() const { return op3p(); } // op3 = Final assigns for clock correction + // AstScope* aboveScopep() const { return m_aboveScopep; } AstCell* aboveCellp() const { return m_aboveCellp; } bool isTop() const { return aboveScopep() == nullptr; } // At top of hierarchy + // Create new MODULETEMP variable under this scope + AstVarScope* createTemp(const string& name, unsigned width); + AstVarScope* createTemp(const string& name, AstNodeDType* dtypep); + AstVarScope* createTempLike(const string& name, AstVarScope* vscp); }; class AstTopScope final : public AstNode { @@ -3317,15 +3318,16 @@ class AstSenItem final : public AstNode { private: VEdgeType m_edgeType; // Edge type public: - class Combo {}; // for creator type-overload selection - class Illegal {}; // for creator type-overload selection - class Initial {}; // for creator type-overload selection - class Settle {}; // for creator type-overload selection - class Never {}; // for creator type-overload selection - AstSenItem(FileLine* fl, VEdgeType edgeType, AstNode* varrefp) + class Combo {}; // for constructor type-overload selection + class Illegal {}; // for constructor type-overload selection + class Static {}; // for constructor type-overload selection + class Initial {}; // for constructor type-overload selection + class Final {}; // for constructor type-overload selection + class Never {}; // for constructor type-overload selection + AstSenItem(FileLine* fl, VEdgeType edgeType, AstNode* senp) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{edgeType} { - setOp1p(varrefp); + setOp1p(senp); } AstSenItem(FileLine* fl, Combo) : ASTGEN_SUPER_SenItem(fl) @@ -3333,12 +3335,15 @@ public: AstSenItem(FileLine* fl, Illegal) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_ILLEGAL} {} + AstSenItem(FileLine* fl, Static) + : ASTGEN_SUPER_SenItem(fl) + , m_edgeType{VEdgeType::ET_STATIC} {} AstSenItem(FileLine* fl, Initial) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_INITIAL} {} - AstSenItem(FileLine* fl, Settle) + AstSenItem(FileLine* fl, Final) : ASTGEN_SUPER_SenItem(fl) - , m_edgeType{VEdgeType::ET_SETTLE} {} + , m_edgeType{VEdgeType::ET_FINAL} {} AstSenItem(FileLine* fl, Never) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_NEVER} {} @@ -3347,23 +3352,23 @@ public: virtual bool same(const AstNode* samep) const override { return edgeType() == static_cast(samep)->edgeType(); } - VEdgeType edgeType() const { return m_edgeType; } // * = Posedge/negedge + VEdgeType edgeType() const { return m_edgeType; } void edgeType(VEdgeType type) { m_edgeType = type; editCountInc(); - } // * = Posedge/negedge - AstNode* sensp() const { return op1p(); } // op1 = Signal sensitized - AstNodeVarRef* varrefp() const { - return VN_CAST(op1p(), NodeVarRef); - } // op1 = Signal sensitized + } + // op1 = Expression sensitized, if any + AstNode* sensp() const { return op1p(); } + AstNodeVarRef* varrefp() const { return VN_CAST(op1p(), NodeVarRef); } // bool isClocked() const { return edgeType().clockedStmt(); } bool isCombo() const { return edgeType() == VEdgeType::ET_COMBO; } + bool isHybrid() const { return edgeType() == VEdgeType::ET_HYBRID; } + bool isStatic() const { return edgeType() == VEdgeType::ET_STATIC; } bool isInitial() const { return edgeType() == VEdgeType::ET_INITIAL; } + bool isFinal() const { return edgeType() == VEdgeType::ET_FINAL; } bool isIllegal() const { return edgeType() == VEdgeType::ET_ILLEGAL; } - bool isSettle() const { return edgeType() == VEdgeType::ET_SETTLE; } bool isNever() const { return edgeType() == VEdgeType::ET_NEVER; } - bool hasVar() const { return !(isCombo() || isInitial() || isSettle() || isNever()); } }; class AstSenTree final : public AstNode { @@ -3387,9 +3392,11 @@ public: void multi(bool flag) { m_multi = true; } // METHODS bool hasClocked() const; // Includes a clocked statement - bool hasSettle() const; // Includes a SETTLE SenItem + bool hasStatic() const; // Includes a STATIC SenItem bool hasInitial() const; // Includes a INITIAL SenItem + bool hasFinal() const; // Includes a FINAL SenItem bool hasCombo() const; // Includes a COMBO SenItem + bool hasHybrid() const; // Includes a HYBRID SenItem }; class AstFinal final : public AstNodeProcedure { @@ -3594,6 +3601,20 @@ public: AstNode* lhsp() const { return op1p(); } }; +class AstFireEvent final : public AstNodeStmt { + // '-> _' and '->> _' event trigger statements + bool m_delayed; // Delayed (->>) vs non-delayed (->) +public: + AstFireEvent(FileLine* fl, AstNode* operandp, bool delayed) + : ASTGEN_SUPER_FireEvent(fl) + , m_delayed{delayed} { + setOp1p(operandp); + } + ASTNODE_NODE_FUNCS(FireEvent); + AstNode* operandp() const { return op1p(); } + bool isDeleyed() const { return m_delayed; } +}; + class AstAssignPre final : public AstNodeAssign { // Like Assign, but predelayed assignment requiring special order handling public: @@ -4827,24 +4848,6 @@ public: AstJumpLabel* labelp() const { return m_labelp; } }; -class AstChangeDet final : public AstNodeStmt { - // A comparison to determine change detection, common & must be fast. -public: - // Null lhs+rhs used to indicate change needed with no spec vars - AstChangeDet(FileLine* fl, AstNode* lhsp, AstNode* rhsp) - : ASTGEN_SUPER_ChangeDet(fl) { - setNOp1p(lhsp); - setNOp2p(rhsp); - } - ASTNODE_NODE_FUNCS(ChangeDet) - AstNode* lhsp() const { return op1p(); } - AstNode* rhsp() const { return op2p(); } - virtual bool isGateOptimizable() const override { return false; } - virtual bool isPredictOptimizable() const override { return false; } - virtual int instrCount() const override { return widthInstrs() * 2; } // xor, or/logor - virtual bool same(const AstNode* samep) const override { return true; } -}; - class AstConsAssoc final : public AstNodeMath { // Construct an assoc array and return object, '{} // Parents: math @@ -5429,10 +5432,7 @@ public: return nullptr; } virtual void cloneRelink() override { - if (m_sensesp->clonep()) { - m_sensesp = m_sensesp->clonep(); - UASSERT(m_sensesp, "Bad clone cross link: " << this); - } + if (m_sensesp->clonep()) m_sensesp = m_sensesp->clonep(); } // Statements are broken into pieces, as some must come before others. void sensesp(AstSenTree* nodep) { m_sensesp = nodep; } @@ -5440,13 +5440,12 @@ public: // op1 = Sensitivity tree, if a clocked block in early stages void sensesStorep(AstSenTree* nodep) { addOp1p(nodep); } AstSenTree* sensesStorep() const { return VN_AS(op1p(), SenTree); } - // op2 = Combo logic + // op2 = Logic AstNode* stmtsp() const { return op2p(); } void addStmtsp(AstNode* nodep) { addOp2p(nodep); } // METHODS - bool hasInitial() const { return m_sensesp->hasInitial(); } - bool hasSettle() const { return m_sensesp->hasSettle(); } bool hasClocked() const { return m_sensesp->hasClocked(); } + bool hasCombo() const { return m_sensesp->hasCombo(); } }; class AstAttrOf final : public AstNode { @@ -8884,7 +8883,6 @@ private: bool m_isTrace : 1; // Function is related to tracing bool m_dontCombine : 1; // V3Combine shouldn't compare this func tree, it's special bool m_declPrivate : 1; // Declare it private - bool m_isFinal : 1; // This is a function corresponding to a SystemVerilog 'final' block bool m_slow : 1; // Slow routine, called once or just at init time bool m_funcPublic : 1; // From user public task/function bool m_isConstructor : 1; // Is C class constructor @@ -8913,7 +8911,6 @@ public: m_isTrace = false; m_dontCombine = false; m_declPrivate = false; - m_isFinal = false; m_slow = false; m_funcPublic = false; m_isConstructor = false; @@ -8971,8 +8968,6 @@ public: bool dontInline() const { return dontCombine() || slow() || funcPublic(); } bool declPrivate() const { return m_declPrivate; } void declPrivate(bool flag) { m_declPrivate = flag; } - bool isFinal() const { return m_isFinal; } - void isFinal(bool flag) { m_isFinal = flag; } bool slow() const { return m_slow; } void slow(bool flag) { m_slow = flag; } bool funcPublic() const { return m_funcPublic; } @@ -9332,11 +9327,11 @@ private: AstConstPool* const m_constPoolp; // Reference to constant pool, for faster lookup AstPackage* m_dollarUnitPkgp = nullptr; // $unit AstCFunc* m_evalp = nullptr; // The '_eval' function + AstCFunc* m_evalNbap = nullptr; // The '_eval__nba' function AstVarScope* m_dpiExportTriggerp = nullptr; // The DPI export trigger variable AstTopScope* m_topScopep = nullptr; // The singleton AstTopScope under the top module VTimescale m_timeunit; // Global time unit VTimescale m_timeprecision; // Global time precision - bool m_changeRequest = false; // Have _change_request method bool m_timescaleSpecified = false; // Input HDL specified timescale uint32_t m_nextFreeMTaskID = 1; // Next unique MTask ID within netlist // starts at 1 so 0 means no MTask ID @@ -9367,8 +9362,6 @@ public: void addFilesp(AstNodeFile* filep) { addOp2p(filep); } void addMiscsp(AstNode* nodep) { addOp3p(nodep); } AstTypeTable* typeTablep() { return m_typeTablep; } - void changeRequest(bool specified) { m_changeRequest = specified; } - bool changeRequest() const { return m_changeRequest; } AstConstPool* constPoolp() { return m_constPoolp; } AstPackage* dollarUnitPkgp() const { return m_dollarUnitPkgp; } AstPackage* dollarUnitPkgAddp() { @@ -9382,8 +9375,11 @@ public: } return m_dollarUnitPkgp; } + AstCFunc* evalp() const { return m_evalp; } - void evalp(AstCFunc* evalp) { m_evalp = evalp; } + void evalp(AstCFunc* funcp) { m_evalp = funcp; } + AstCFunc* evalNbap() const { return m_evalNbap; } + void evalNbap(AstCFunc* funcp) { m_evalNbap = funcp; } AstVarScope* dpiExportTriggerp() const { return m_dpiExportTriggerp; } void dpiExportTriggerp(AstVarScope* varScopep) { m_dpiExportTriggerp = varScopep; } AstTopScope* topScopep() const { return m_topScopep; } diff --git a/src/V3CCtors.cpp b/src/V3CCtors.cpp index 2ebb9f73c..a2832adb3 100644 --- a/src/V3CCtors.cpp +++ b/src/V3CCtors.cpp @@ -185,7 +185,9 @@ void V3CCtors::cctorsAll() { for (AstNode* np = modp->stmtsp(); np; np = np->nextp()) { if (AstVar* const varp = VN_CAST(np, Var)) { if (!varp->isIfaceParent() && !varp->isIfaceRef() && !varp->noReset() - && !varp->isParam()) { + && !varp->isParam() + && !(varp->basicp() + && (varp->basicp()->isEvent() || varp->basicp()->isTriggerVec()))) { const auto vrefp = new AstVarRef{varp->fileline(), varp, VAccess::WRITE}; var_reset.add(new AstCReset{varp->fileline(), vrefp}); } diff --git a/src/V3Cast.cpp b/src/V3Cast.cpp index d0a26e6c9..4cf233f09 100644 --- a/src/V3Cast.cpp +++ b/src/V3Cast.cpp @@ -157,8 +157,9 @@ private: virtual void visit(AstVarRef* nodep) override { const AstNode* const backp = nodep->backp(); if (nodep->access().isReadOnly() && !VN_IS(backp, CCast) && VN_IS(backp, NodeMath) - && !VN_IS(backp, ArraySel) && !VN_IS(backp, RedXor) && backp->width() - && castSize(nodep) != castSize(nodep->varp())) { + && !VN_IS(backp, ArraySel) && !VN_IS(backp, RedXor) + && (nodep->varp()->basicp() && !nodep->varp()->basicp()->isTriggerVec()) + && backp->width() && castSize(nodep) != castSize(nodep->varp())) { // Cast vars to IData first, else below has upper bits wrongly set // CData x=3; out = (QData)(x<<30); insertCast(nodep, castSize(nodep)); diff --git a/src/V3Cdc.cpp b/src/V3Cdc.cpp index acb305ab0..28a64a3de 100644 --- a/src/V3Cdc.cpp +++ b/src/V3Cdc.cpp @@ -646,8 +646,8 @@ private: UINFO(4, " BLOCK " << nodep << endl); AstNode::user2ClearTree(); m_domainp = nodep->sensesp(); - if (!m_domainp || m_domainp->hasCombo() - || m_domainp->hasClocked()) { // IE not hasSettle/hasInitial + // Ignore static initializers, initial and final blocks + if (!m_domainp || m_domainp->hasCombo() || m_domainp->hasClocked()) { iterateNewStmt(nodep); } m_domainp = nullptr; diff --git a/src/V3Changed.cpp b/src/V3Changed.cpp deleted file mode 100644 index bddcf76a7..000000000 --- a/src/V3Changed.cpp +++ /dev/null @@ -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 - -//###################################################################### - -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([&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); -} diff --git a/src/V3Changed.h b/src/V3Changed.h deleted file mode 100644 index f9afbec85..000000000 --- a/src/V3Changed.h +++ /dev/null @@ -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 diff --git a/src/V3Clock.cpp b/src/V3Clock.cpp index a8b8f7006..180f087a0 100644 --- a/src/V3Clock.cpp +++ b/src/V3Clock.cpp @@ -33,6 +33,7 @@ #include "V3Global.h" #include "V3Clock.h" #include "V3Ast.h" +#include "V3Sched.h" #include @@ -69,116 +70,19 @@ public: class ClockVisitor final : public VNVisitor { private: - // NODE STATE - // Cleared each Module: - // AstVarScope::user1p() -> AstVarScope*. Temporary signal that was created. - const VNUser1InUse m_inuser1; - // STATE - AstNodeModule* m_modp = nullptr; // Current module - const AstTopScope* m_topScopep = nullptr; // Current top scope - AstScope* m_scopep = nullptr; // Current scope - AstCFunc* m_evalFuncp = nullptr; // Top eval function we are creating - AstCFunc* m_initFuncp = nullptr; // Top initial function we are creating - AstCFunc* m_finalFuncp = nullptr; // Top final function we are creating - AstCFunc* m_settleFuncp = nullptr; // Top settlement function we are creating AstSenTree* m_lastSenp = nullptr; // Last sensitivity match, so we can detect duplicates. AstIf* m_lastIfp = nullptr; // Last sensitivity if active to add more under - AstMTaskBody* m_mtaskBodyp = nullptr; // Current mtask body // METHODS VL_DEBUG_FUNC; // Declare debug() - AstVarScope* getCreateLastClk(AstVarScope* vscp) { - if (vscp->user1p()) return static_cast(vscp->user1p()); - const AstVar* const varp = vscp->varp(); - if (!varp->width1()) { - varp->v3warn(E_UNSUPPORTED, "Unsupported: Clock edge on non-single bit signal: " - << varp->prettyNameQ()); - } - const string newvarname - = (string("__Vclklast__") + vscp->scopep()->nameDotless() + "__" + varp->name()); - AstVar* const newvarp = new AstVar(vscp->fileline(), VVarType::MODULETEMP, newvarname, - VFlagLogicPacked(), 1); - newvarp->noReset(true); // Reset by below assign - m_modp->addStmtp(newvarp); - AstVarScope* const newvscp = new AstVarScope(vscp->fileline(), m_scopep, newvarp); - vscp->user1p(newvscp); - m_scopep->addVarp(newvscp); - // Add init - AstNode* fromp = new AstVarRef(newvarp->fileline(), vscp, VAccess::READ); - if (v3Global.opt.xInitialEdge()) fromp = new AstNot(fromp->fileline(), fromp); - AstNode* const newinitp = new AstAssign( - vscp->fileline(), new AstVarRef(newvarp->fileline(), newvscp, VAccess::WRITE), fromp); - addToInitial(newinitp); - // At bottom, assign them - AstAssign* const finalp = new AstAssign( - vscp->fileline(), new AstVarRef(vscp->fileline(), newvscp, VAccess::WRITE), - new AstVarRef(vscp->fileline(), vscp, VAccess::READ)); - m_evalFuncp->addFinalsp(finalp); - // - UINFO(4, "New Last: " << newvscp << endl); - return newvscp; - } - AstNode* createSenItemEquation(AstSenItem* nodep) { - // We know the var is clean, and one bit, so we use binary ops - // for speed instead of logical ops. - // POSEDGE: var & ~var_last - // NEGEDGE: ~var & var_last - // BOTHEDGE: var ^ var_last - // HIGHEDGE: var - // LOWEDGE: ~var - // ANYEDGE: var ^ var_last - AstNode* newp = nullptr; - if (nodep->edgeType() == VEdgeType::ET_ILLEGAL) { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: Complicated event expression in sensitive activity list"); - return nullptr; - } - UASSERT_OBJ(nodep->varrefp(), nodep, "No clock found on sense item"); - AstVarScope* const clkvscp = nodep->varrefp()->varScopep(); - if (nodep->edgeType() == VEdgeType::ET_POSEDGE) { - AstVarScope* const lastVscp = getCreateLastClk(clkvscp); - newp = new AstAnd( - nodep->fileline(), - new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(), VAccess::READ), - new AstNot(nodep->fileline(), - new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ))); - } else if (nodep->edgeType() == VEdgeType::ET_NEGEDGE) { - AstVarScope* const lastVscp = getCreateLastClk(clkvscp); - newp = new AstAnd( - nodep->fileline(), - new AstNot(nodep->fileline(), - new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(), - VAccess::READ)), - new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ)); - } else if (nodep->edgeType() == VEdgeType::ET_BOTHEDGE) { - AstVarScope* const lastVscp = getCreateLastClk(clkvscp); - newp = new AstXor( - nodep->fileline(), - new AstVarRef(nodep->fileline(), nodep->varrefp()->varScopep(), VAccess::READ), - new AstVarRef(nodep->fileline(), lastVscp, VAccess::READ)); - } else if (nodep->edgeType() == VEdgeType::ET_HIGHEDGE) { - newp = new AstVarRef(nodep->fileline(), clkvscp, VAccess::READ); - } else if (nodep->edgeType() == VEdgeType::ET_LOWEDGE) { - newp = new AstNot(nodep->fileline(), - new AstVarRef(nodep->fileline(), clkvscp, VAccess::READ)); - } else { - nodep->v3fatalSrc("Bad edge type"); - } - return newp; - } AstNode* createSenseEquation(AstSenItem* nodesp) { - // Nodep may be a list of elements; we need to walk it AstNode* senEqnp = nullptr; for (AstSenItem* senp = nodesp; senp; senp = VN_AS(senp->nextp(), SenItem)) { - AstNode* const senOnep = createSenItemEquation(senp); - if (senEqnp) { - // Add new OR to the sensitivity list equation - senEqnp = new AstOr(senp->fileline(), senEqnp, senOnep); - } else { - senEqnp = senOnep; - } + UASSERT_OBJ(senp->edgeType() == VEdgeType::ET_TRUE, senp, "Should have been lowered"); + AstNode* const senOnep = senp->sensp()->cloneTree(false); + senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep; } return senEqnp; } @@ -192,116 +96,7 @@ private: m_lastSenp = nullptr; m_lastIfp = nullptr; } - AstCFunc* makeTopFunction(const string& name, bool slow = false) { - AstCFunc* const funcp = new AstCFunc{m_topScopep->fileline(), name, m_topScopep->scopep()}; - funcp->dontCombine(true); - funcp->isStatic(false); - funcp->isLoose(true); - funcp->entryPoint(true); - funcp->slow(slow); - funcp->isConst(false); - funcp->declPrivate(true); - m_topScopep->scopep()->addActivep(funcp); - return funcp; - } - void splitCheck(AstCFunc* ofuncp) { - if (!v3Global.opt.outputSplitCFuncs() || !ofuncp->stmtsp()) return; - if (ofuncp->nodeCount() < v3Global.opt.outputSplitCFuncs()) return; - - int funcnum = 0; - int func_stmts = 0; - AstCFunc* funcp = nullptr; - - // Unlink all statements, then add item by item to new sub-functions - AstBegin* const tempp = new AstBegin{ofuncp->fileline(), "[EditWrapper]", - ofuncp->stmtsp()->unlinkFrBackWithNext()}; - if (ofuncp->finalsp()) tempp->addStmtsp(ofuncp->finalsp()->unlinkFrBackWithNext()); - while (tempp->stmtsp()) { - AstNode* const itemp = tempp->stmtsp()->unlinkFrBack(); - const int stmts = itemp->nodeCount(); - if (!funcp || (func_stmts + stmts) > v3Global.opt.outputSplitCFuncs()) { - // Make a new function - funcp - = new AstCFunc{ofuncp->fileline(), ofuncp->name() + "__" + cvtToStr(funcnum++), - m_topScopep->scopep()}; - funcp->dontCombine(true); - funcp->isStatic(false); - funcp->isLoose(true); - funcp->slow(ofuncp->slow()); - m_topScopep->scopep()->addActivep(funcp); - // - AstCCall* const callp = new AstCCall{funcp->fileline(), funcp}; - ofuncp->addStmtsp(callp); - func_stmts = 0; - } - funcp->addStmtsp(itemp); - func_stmts += stmts; - } - VL_DO_DANGLING(tempp->deleteTree(), tempp); - } - // VISITORS - virtual void visit(AstTopScope* nodep) override { - UINFO(4, " TOPSCOPE " << nodep << endl); - m_topScopep = nodep; - m_scopep = nodep->scopep(); - UASSERT_OBJ(m_scopep, nodep, - "No scope found on top level, perhaps you have no statements?"); - // VV***** We reset all user1p() - AstNode::user1ClearTree(); - // Make top functions - m_evalFuncp = makeTopFunction("_eval"); - m_initFuncp = makeTopFunction("_eval_initial", /* slow: */ true); - m_settleFuncp = makeTopFunction("_eval_settle", /* slow: */ true); - m_finalFuncp = makeTopFunction("_final", /* slow: */ true); - // Process the activates - iterateChildren(nodep); - UINFO(4, " TOPSCOPE iter done " << nodep << endl); - // Clear the DPI export trigger flag at the end of eval - if (AstVarScope* const dpiExportTriggerp = v3Global.rootp()->dpiExportTriggerp()) { - FileLine* const fl = dpiExportTriggerp->fileline(); - AstAssign* const assignp - = new AstAssign{fl, new AstVarRef{fl, dpiExportTriggerp, VAccess::WRITE}, - new AstConst{fl, AstConst::BitFalse{}}}; - m_evalFuncp->addFinalsp(assignp); - } - // Split large functions - splitCheck(m_evalFuncp); - splitCheck(m_initFuncp); - splitCheck(m_finalFuncp); - splitCheck(m_settleFuncp); - // Done, clear so we can detect errors - UINFO(4, " TOPSCOPEDONE " << nodep << endl); - clearLastSen(); - m_topScopep = nullptr; - m_scopep = nullptr; - } - virtual void visit(AstNodeModule* nodep) override { - // UINFO(4, " MOD " << nodep << endl); - VL_RESTORER(m_modp); - { - m_modp = nodep; - iterateChildren(nodep); - } - } - virtual void visit(AstScope* nodep) override { - // UINFO(4, " SCOPE " << nodep << endl); - m_scopep = nodep; - iterateChildren(nodep); - if (AstNode* const movep = nodep->finalClksp()) { - UASSERT_OBJ(m_topScopep, nodep, "Final clocks under non-top scope"); - movep->unlinkFrBackWithNext(); - m_evalFuncp->addFinalsp(movep); - } - m_scopep = nullptr; - } - virtual void visit(AstNodeProcedure* nodep) override { - if (AstNode* const stmtsp = nodep->bodysp()) { - stmtsp->unlinkFrBackWithNext(); - nodep->addNextHere(stmtsp); - } - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } virtual void visit(AstCoverToggle* nodep) override { // nodep->dumpTree(cout, "ct:"); // COVERTOGGLE(INC, ORIG, CHANGE) -> @@ -319,110 +114,44 @@ private: nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); } - virtual void visit(AstCFunc* nodep) override { - iterateChildren(nodep); - // Link to global function - if (nodep->isFinal()) { - UINFO(4, " isFinal " << nodep << endl); - AstCCall* const callp = new AstCCall(nodep->fileline(), nodep); - m_finalFuncp->addStmtsp(callp); - } - } virtual void visit(AstSenTree* nodep) override { - // Delete it later; Actives still pointing to it nodep->unlinkFrBack(); - pushDeletep(nodep); - } - void addToEvalLoop(AstNode* stmtsp) { - m_evalFuncp->addStmtsp(stmtsp); // add to top level function - } - void addToSettleLoop(AstNode* stmtsp) { - m_settleFuncp->addStmtsp(stmtsp); // add to top level function - } - void addToInitial(AstNode* stmtsp) { - m_initFuncp->addStmtsp(stmtsp); // add to top level function + pushDeletep(nodep); // Delete it later, AstActives still pointing to it } virtual void visit(AstActive* nodep) override { // Careful if adding variables here, ACTIVES can be under other ACTIVES // Need to save and restore any member state in AstUntilStable block - if (!m_topScopep || !nodep->stmtsp()) { - // Not at the top or empty block... - // Only empty blocks should be leftover on the non-top. Killem. - UASSERT_OBJ(!nodep->stmtsp(), nodep, "Non-empty lower active"); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } else if (m_mtaskBodyp) { - UINFO(4, " TR ACTIVE " << nodep << endl); - AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext(); - if (nodep->hasClocked()) { - UASSERT_OBJ(!nodep->hasInitial(), nodep, - "Initial block should not have clock sensitivity"); - if (m_lastSenp && nodep->sensesp()->sameTree(m_lastSenp)) { - UINFO(4, " sameSenseTree\n"); - } else { - clearLastSen(); - m_lastSenp = nodep->sensesp(); - // Make a new if statement - m_lastIfp = makeActiveIf(m_lastSenp); - m_mtaskBodyp->addStmtsp(m_lastIfp); - } - // Move statements to if - m_lastIfp->addIfsp(stmtsp); - } else if (nodep->hasInitial() || nodep->hasSettle()) { - nodep->v3fatalSrc("MTask should not include initial/settle logic."); - } else { - // Combo logic. Move statements to mtask func. + VNRelinker relinker; + nodep->unlinkFrBack(&relinker); + UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not have been created if empty"); + AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext(); + if (nodep->hasClocked()) { + // Create 'if' statement, if needed + if (!m_lastSenp || !nodep->sensesp()->sameTree(m_lastSenp)) { clearLastSen(); - m_mtaskBodyp->addStmtsp(stmtsp); + m_lastSenp = nodep->sensesp(); + // Make a new if statement + m_lastIfp = makeActiveIf(m_lastSenp); + relinker.relink(m_lastIfp); } - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + // Move statements to if + m_lastIfp->addIfsp(stmtsp); + } else if (nodep->hasCombo()) { + clearLastSen(); + // Move statements to body + relinker.relink(stmtsp); } else { - UINFO(4, " ACTIVE " << nodep << endl); - AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext(); - if (nodep->hasClocked()) { - // Remember the latest sensitivity so we can compare it next time - UASSERT_OBJ(!nodep->hasInitial(), nodep, - "Initial block should not have clock sensitivity"); - if (m_lastSenp && nodep->sensesp()->sameTree(m_lastSenp)) { - UINFO(4, " sameSenseTree\n"); - } else { - clearLastSen(); - m_lastSenp = nodep->sensesp(); - // Make a new if statement - m_lastIfp = makeActiveIf(m_lastSenp); - addToEvalLoop(m_lastIfp); - } - // Move statements to if - m_lastIfp->addIfsp(stmtsp); - } else if (nodep->hasInitial()) { - // Don't need to: clearLastSen();, as we're adding it to different cfunc - // Move statements to function - addToInitial(stmtsp); - } else if (nodep->hasSettle()) { - // Don't need to: clearLastSen();, as we're adding it to different cfunc - // Move statements to function - addToSettleLoop(stmtsp); - } else { - // Combo - clearLastSen(); - // Move statements to function - addToEvalLoop(stmtsp); - } - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + nodep->v3fatalSrc("Should have been removed by V3Sched::schedule"); } + VL_DO_DANGLING(nodep->deleteTree(), nodep); } virtual void visit(AstExecGraph* nodep) override { - VL_RESTORER(m_mtaskBodyp); - for (m_mtaskBodyp = nodep->mTaskBodiesp(); m_mtaskBodyp; - m_mtaskBodyp = VN_AS(m_mtaskBodyp->nextp(), MTaskBody)) { + for (AstMTaskBody* mtaskBodyp = nodep->mTaskBodiesp(); mtaskBodyp; + mtaskBodyp = VN_AS(mtaskBodyp->nextp(), MTaskBody)) { clearLastSen(); - iterate(m_mtaskBodyp); + iterate(mtaskBodyp); } clearLastSen(); - // Move the ExecGraph into _eval. Its location marks the - // spot where the graph will execute, relative to other - // (serial) logic in the cycle. - nodep->unlinkFrBack(); - addToEvalLoop(nodep); } //-------------------- @@ -430,12 +159,7 @@ private: public: // CONSTRUCTORS - explicit ClockVisitor(AstNetlist* nodep) { - iterate(nodep); - // Allow downstream modules to find _eval() - // easily without iterating through the tree. - nodep->evalp(m_evalFuncp); - } + explicit ClockVisitor(AstNetlist* netlistp) { iterate(netlistp); } virtual ~ClockVisitor() override = default; }; diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 759033185..2cf230a7f 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2595,7 +2595,14 @@ private: // Constants in sensitivity lists may be removed (we'll simplify later) if (nodep->isClocked()) { // A constant can never get a pos/negedge if (onlySenItemInSenTree(nodep)) { - nodep->replaceWith(new AstSenItem(nodep->fileline(), AstSenItem::Never())); + if (nodep->edgeType() == VEdgeType::ET_CHANGED) { + // TODO: This really is dodgy, as strictgly compliant simulators will not + // execute this block, but but t_func_check relies on it + nodep->replaceWith( + new AstSenItem(nodep->fileline(), AstSenItem::Initial())); + } else { + nodep->replaceWith(new AstSenItem(nodep->fileline(), AstSenItem::Never())); + } VL_DO_DANGLING(nodep->deleteTree(), nodep); } else { VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); @@ -2619,12 +2626,6 @@ private: UASSERT_OBJ(senvarp, sensp, "Non-varref sensitivity variable"); sensp->replaceWith(senvarp); VL_DO_DANGLING(sensp->deleteTree(), sensp); - } else if (!m_doNConst // Deal with later when doNConst missing - && (VN_IS(nodep->sensp(), EnumItemRef) || VN_IS(nodep->sensp(), Const))) { - } else if (nodep->isIllegal()) { // Deal with later - } else { - UASSERT_OBJ(!(nodep->hasVar() && !nodep->varrefp()), nodep, - "Null sensitivity variable"); } } @@ -2633,8 +2634,10 @@ private: if (lhsp->type() < rhsp->type()) return true; if (lhsp->type() > rhsp->type()) return false; // Looks visually better if we keep sorted by name - if (!lhsp->varrefp() && rhsp->varrefp()) return true; - if (lhsp->varrefp() && !rhsp->varrefp()) return false; + if (!lhsp->sensp() && rhsp->sensp()) return true; + if (lhsp->sensp() && !rhsp->sensp()) return false; + if (lhsp->varrefp() && !rhsp->varrefp()) return true; + if (!lhsp->varrefp() && rhsp->varrefp()) return false; if (lhsp->varrefp() && rhsp->varrefp()) { if (lhsp->varrefp()->name() < rhsp->varrefp()->name()) return true; if (lhsp->varrefp()->name() > rhsp->varrefp()->name()) return false; @@ -2644,6 +2647,27 @@ private: // Or rarely, different data types if (lhsp->varrefp()->dtypep() < rhsp->varrefp()->dtypep()) return true; if (lhsp->varrefp()->dtypep() > rhsp->varrefp()->dtypep()) return false; + } else if (AstCMethodHard* const lp = VN_CAST(lhsp->sensp(), CMethodHard)) { + if (AstCMethodHard* const rp = VN_CAST(rhsp->sensp(), CMethodHard)) { + if (AstVarRef* const lRefp = VN_CAST(lp->fromp(), VarRef)) { + if (AstVarRef* const rRefp = VN_CAST(rp->fromp(), VarRef)) { + if (lRefp->name() < rRefp->name()) return true; + if (lRefp->name() > rRefp->name()) return false; + // But might be same name with different scopes + if (lRefp->varScopep() < rRefp->varScopep()) return true; + if (lRefp->varScopep() > rRefp->varScopep()) return false; + // Or rarely, different data types + if (lRefp->dtypep() < rRefp->dtypep()) return true; + if (lRefp->dtypep() > rRefp->dtypep()) return false; + } + } + if (AstConst* lConstp = VN_CAST(lp->pinsp(), Const)) { + if (AstConst* rConstp = VN_CAST(rp->pinsp(), Const)) { + if (lConstp->toUInt() < rConstp->toUInt()) return true; + if (lConstp->toUInt() > rConstp->toUInt()) return false; + } + } + } } // Sort by edge, AFTER variable, as we want multiple edges for same var adjacent. // note the SenTree optimizer requires this order (more @@ -2706,17 +2730,13 @@ private: AstSenItem* const litemp = senp; AstSenItem* const ritemp = nextp; if (ritemp) { - if ((litemp->varrefp() && ritemp->varrefp() - && litemp->varrefp()->sameGateTree(ritemp->varrefp())) - || (!litemp->varrefp() && !ritemp->varrefp())) { + if ((litemp->sensp() && ritemp->sensp() + && litemp->sensp()->sameGateTree(ritemp->sensp())) + || (!litemp->sensp() && !ritemp->sensp())) { // We've sorted in the order ANY, BOTH, POS, NEG, // so we don't need to try opposite orders - if ((litemp->edgeType() - == VEdgeType::ET_ANYEDGE) // ANY or {BOTH|POS|NEG} -> ANY - || (litemp->edgeType() - == VEdgeType::ET_BOTHEDGE) // BOTH or {POS|NEG} -> BOTH - || (litemp->edgeType() == VEdgeType::ET_POSEDGE // POS or NEG -> BOTH - && ritemp->edgeType() == VEdgeType::ET_NEGEDGE) + if ((litemp->edgeType() == VEdgeType::ET_POSEDGE // POS or NEG -> BOTH + && ritemp->edgeType() == VEdgeType::ET_NEGEDGE) || (litemp->edgeType() == ritemp->edgeType()) // Identical edges ) { // Fix edge of old node diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index 6347fc098..e0485ae99 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -131,7 +131,7 @@ private: // Class packages might have no children, but need to remain as // long as the class they refer to is needed if (VN_IS(m_modp, Class) || VN_IS(m_modp, ClassPackage)) nodep->user1Inc(); - if (!nodep->isTop() && !nodep->varsp() && !nodep->blocksp() && !nodep->finalClksp()) { + if (!nodep->isTop() && !nodep->varsp() && !nodep->blocksp()) { m_scopesp.push_back(nodep); } } diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index 57fe02833..b962792a5 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -93,7 +93,7 @@ private: AstAssignDly* m_nextDlyp = nullptr; // Next delayed assignment in a list of assignments bool m_inDly = false; // True in delayed assignments bool m_inLoop = false; // True in for loops - bool m_inInitial = false; // True in initial blocks + bool m_inInitial = false; // True in static initializers and initial blocks using VarMap = std::map, AstVar*>; VarMap m_modVarMap; // Table of new var names created under module VDouble0 m_statSharedSet; // Statistic tracking @@ -160,7 +160,7 @@ private: return varscp; } - AstActive* createActivePost(AstVarRef* varrefp) { + AstActive* createActive(AstNode* varrefp) { AstActive* const newactp = new AstActive(varrefp->fileline(), "sequentdly", m_activep->sensesp()); // Was addNext(), but addNextHere() avoids a linear search. @@ -336,7 +336,7 @@ private: } else { // first time we've dealt with this memory finalp = new AstAlwaysPost(nodep->fileline(), nullptr /*sens*/, nullptr /*body*/); UINFO(9, " Created " << finalp << endl); - AstActive* const newactp = createActivePost(varrefp); + AstActive* const newactp = createActive(varrefp); newactp->addStmtsp(finalp); varrefp->varScopep()->user4p(finalp); finalp->user2p(newactp); @@ -383,12 +383,54 @@ private: m_activep = nodep; VL_RESTORER(m_inInitial); { - m_inInitial = nodep->hasInitial(); + AstSenTree* const senTreep = nodep->sensesp(); + m_inInitial = senTreep->hasStatic() || senTreep->hasInitial(); // Two sets to same variable in different actives must use different vars. AstNode::user3ClearTree(); iterateChildren(nodep); } } + virtual void visit(AstFireEvent* nodep) override { + UASSERT_OBJ(v3Global.hasEvents(), nodep, "Inconsistent"); + FileLine* const flp = nodep->fileline(); + if (nodep->isDeleyed()) { + AstVarRef* const vrefp = VN_AS(nodep->operandp(), VarRef); + vrefp->unlinkFrBack(); + const string newvarname = (string("__Vdly__") + vrefp->varp()->shortName()); + AstVarScope* const dlyvscp = createVarSc(vrefp->varScopep(), newvarname, 1, nullptr); + + const auto dlyRef = [=](VAccess access) { + return new AstVarRef{flp, dlyvscp, access}; + }; + + AstAssignPre* const prep = new AstAssignPre{flp, dlyRef(VAccess::WRITE), + new AstConst{flp, AstConst::BitFalse{}}}; + AstAlwaysPost* const postp = new AstAlwaysPost{flp, nullptr, nullptr}; + { + AstIf* const ifp = new AstIf{flp, dlyRef(VAccess::READ)}; + postp->addStmtp(ifp); + AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "fire"}; + callp->statement(true); + callp->dtypeSetVoid(); + ifp->addIfsp(callp); + } + + AstActive* const activep = createActive(nodep); + activep->addStmtsp(prep); + activep->addStmtsp(postp); + + AstAssign* const assignp = new AstAssign{flp, dlyRef(VAccess::WRITE), + new AstConst{flp, AstConst::BitTrue{}}}; + nodep->replaceWith(assignp); + } else { + AstCMethodHard* const callp + = new AstCMethodHard{flp, nodep->operandp()->unlinkFrBack(), "fire"}; + callp->dtypeSetVoid(); + callp->statement(true); + nodep->replaceWith(callp); + } + nodep->deleteTree(); + } virtual void visit(AstAssignDly* nodep) override { m_inDly = true; m_nextDlyp @@ -407,7 +449,7 @@ private: "loops (non-delayed is ok - see docs)"); } const AstBasicDType* const basicp = lhsp->dtypep()->basicp(); - if (basicp && basicp->isEventValue()) { + if (basicp && basicp->isEvent()) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: event arrays"); } if (newlhsp) { @@ -444,20 +486,10 @@ private: if (!dlyvscp) { // First use of this delayed variable const string newvarname = (string("__Vdly__") + nodep->varp()->shortName()); dlyvscp = createVarSc(oldvscp, newvarname, 0, nullptr); - AstNodeAssign* prep; - const AstBasicDType* const basicp = oldvscp->dtypep()->basicp(); - if (basicp && basicp->isEventValue()) { - // Events go to zero on next timestep unless reactivated - prep = new AstAssignPre( - nodep->fileline(), - new AstVarRef(nodep->fileline(), dlyvscp, VAccess::WRITE), - new AstConst(nodep->fileline(), AstConst::BitFalse())); - } else { - prep = new AstAssignPre( - nodep->fileline(), - new AstVarRef(nodep->fileline(), dlyvscp, VAccess::WRITE), - new AstVarRef(nodep->fileline(), oldvscp, VAccess::READ)); - } + AstNodeAssign* const prep = new AstAssignPre( + nodep->fileline(), + new AstVarRef(nodep->fileline(), dlyvscp, VAccess::WRITE), + new AstVarRef(nodep->fileline(), oldvscp, VAccess::READ)); AstNodeAssign* const postp = new AstAssignPost( nodep->fileline(), new AstVarRef(nodep->fileline(), oldvscp, VAccess::WRITE), @@ -465,7 +497,7 @@ private: postp->lhsp()->user2(true); // Don't detect this assignment oldvscp->user1p(dlyvscp); // So we can find it later // Make new ACTIVE with identical sensitivity tree - AstActive* const newactp = createActivePost(nodep); + AstActive* const newactp = createActive(nodep); dlyvscp->user2p(newactp); newactp->addStmtsp(prep); // Add to FRONT of statements newactp->addStmtsp(postp); diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 786b8368c..5598e500e 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -731,80 +731,3 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP } return ""; } - -void EmitCFunc::doubleOrDetect(AstChangeDet* changep, bool& gotOne) { - // cppcheck-suppress variableScope - static int s_addDoubleOr = 10; // Determined experimentally as best - if (!changep->rhsp()) { - if (!gotOne) { - gotOne = true; - } else { - puts(" | "); - } - iterateAndNextNull(changep->lhsp()); - } else { - AstNode* const lhsp = changep->lhsp(); - AstNode* const rhsp = changep->rhsp(); - UASSERT_OBJ(VN_IS(lhsp, VarRef) || VN_IS(lhsp, ArraySel), changep, "Not ref?"); - UASSERT_OBJ(VN_IS(rhsp, VarRef) || VN_IS(rhsp, ArraySel), changep, "Not ref?"); - for (int word = 0; word < (changep->lhsp()->isWide() ? changep->lhsp()->widthWords() : 1); - ++word) { - if (!gotOne) { - gotOne = true; - s_addDoubleOr = 10; - puts("("); - } else if (--s_addDoubleOr == 0) { - puts("|| ("); - s_addDoubleOr = 10; - } else { - puts(" | ("); - } - iterateAndNextNull(changep->lhsp()); - if (changep->lhsp()->isWide()) puts("[" + cvtToStr(word) + "]"); - if (changep->lhsp()->isDouble()) { - puts(" != "); - } else { - puts(" ^ "); - } - iterateAndNextNull(changep->rhsp()); - if (changep->lhsp()->isWide()) puts("[" + cvtToStr(word) + "]"); - puts(")"); - } - } -} - -void EmitCFunc::emitChangeDet() { - putsDecoration("// Change detection\n"); - puts("QData __req = false; // Logically a bool\n"); // But not because it results in - // faster code - bool gotOne = false; - for (AstChangeDet* const changep : m_blkChangeDetVec) { - if (changep->lhsp()) { - if (!gotOne) { // Not a clocked block - puts("__req |= ("); - } else { - puts("\n"); - } - doubleOrDetect(changep, gotOne); - } - } - if (gotOne) puts(");\n"); - if (gotOne && !v3Global.opt.protectIds()) { - // puts("VL_DEBUG_IF( if (__req) cout<<\"- CLOCKREQ );"); - for (AstChangeDet* nodep : m_blkChangeDetVec) { - if (nodep->lhsp()) { - puts("VL_DEBUG_IF( if(__req && ("); - bool gotOneIgnore = false; - doubleOrDetect(nodep, gotOneIgnore); - string varname; - if (VN_IS(nodep->lhsp(), VarRef)) { - varname = ": " + VN_AS(nodep->lhsp(), VarRef)->varp()->prettyName(); - } - puts(")) VL_DBG_MSGF(\" CHANGE: "); - puts(protect(nodep->fileline()->filename())); - puts(":" + cvtToStr(nodep->fileline()->lineno())); - puts(varname + "\\n\"); );\n"); - } - } - } -} diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 4bdfb1c57..a99a84737 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -119,7 +119,6 @@ private: int m_labelNum = 0; // Next label number int m_splitSize = 0; // # of cfunc nodes placed into output file bool m_inUC = false; // Inside an AstUCStmt or AstUCMath - std::vector m_blkChangeDetVec; // All encountered changes in block bool m_emitConstInit = false; // Emitting constant initializer protected: @@ -177,7 +176,6 @@ public: void emitVarReset(AstVar* varp); string emitVarResetRecurse(const AstVar* varp, const string& varNameProtected, AstNodeDType* dtypep, int depth, const string& suffix); - void doubleOrDetect(AstChangeDet* changep, bool& gotOne); void emitChangeDet(); void emitConstInit(AstNode* initp) { // We should refactor emit to produce output into a provided buffer, not go through members @@ -194,8 +192,6 @@ public: VL_RESTORER(m_cfuncp); m_cfuncp = nodep; - m_blkChangeDetVec.clear(); - splitSizeInc(nodep); puts("\n"); @@ -244,15 +240,11 @@ public: iterateAndNextNull(nodep->stmtsp()); } - if (!m_blkChangeDetVec.empty()) emitChangeDet(); - if (nodep->finalsp()) { putsDecoration("// Final\n"); iterateAndNextNull(nodep->finalsp()); } - if (!m_blkChangeDetVec.empty()) puts("return __req;\n"); - puts("}\n"); if (nodep->ifdef() != "") puts("#endif // " + nodep->ifdef() + "\n"); } @@ -1207,9 +1199,6 @@ public: UASSERT_OBJ(!nodep->mTaskBodiesp(), nodep, "These should have been lowered"); iterateChildrenConst(nodep); } - virtual void visit(AstChangeDet* nodep) override { // - m_blkChangeDetVec.push_back(nodep); - } // Default virtual void visit(AstNode* nodep) override { diff --git a/src/V3EmitCImp.cpp b/src/V3EmitCImp.cpp index f5da29d61..18d2b2710 100644 --- a/src/V3EmitCImp.cpp +++ b/src/V3EmitCImp.cpp @@ -372,6 +372,7 @@ class EmitCImp final : EmitCFunc { // lower level subinst code does it. } else if (varp->isParam()) { } else if (varp->isStatic() && varp->isConst()) { + } else if (varp->basicp() && varp->basicp()->isTriggerVec()) { } else { int vects = 0; AstNodeDType* elementp = varp->dtypeSkipRefp(); diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index e04c79f7e..591117762 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -314,97 +314,25 @@ class EmitCModel final : public EmitCFunc { puts("}\n"); } - void emitSettleLoop(AstNodeModule* modp, bool initial) { - const string topModNameProtected = prefixNameProtect(modp); - - putsDecoration("// Evaluate till stable\n"); - if (v3Global.rootp()->changeRequest()) { - puts("int __VclockLoop = 0;\n"); - puts("QData __Vchange = 1;\n"); - } - if (v3Global.opt.trace()) puts("vlSymsp->__Vm_activity = true;\n"); - puts("do {\n"); - puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ "); - puts(initial ? "Initial" : "Clock"); - puts(" loop\\n\"););\n"); - if (initial) - puts(topModNameProtected + "__" + protect("_eval_settle") + "(&(vlSymsp->TOP));\n"); - - if (v3Global.opt.profExec() && !initial) { - puts("VL_EXEC_TRACE_ADD_RECORD(vlSymsp).evalLoopBegin();\n"); - } - - puts(topModNameProtected + "__" + protect("_eval") + "(&(vlSymsp->TOP));\n"); - - if (v3Global.opt.profExec() && !initial) { - puts("VL_EXEC_TRACE_ADD_RECORD(vlSymsp).evalLoopEnd();\n"); - } - - if (v3Global.rootp()->changeRequest()) { - puts("if (VL_UNLIKELY(++__VclockLoop > " + cvtToStr(v3Global.opt.convergeLimit()) - + ")) {\n"); - puts("// About to fail, so enable debug to see what's not settling.\n"); - puts("// Note you must run make with OPT=-DVL_DEBUG for debug prints.\n"); - puts("int __Vsaved_debug = Verilated::debug();\n"); - puts("Verilated::debug(1);\n"); - puts("__Vchange = " + topModNameProtected + "__" + protect("_change_request") - + "(&(vlSymsp->TOP));\n"); - puts("Verilated::debug(__Vsaved_debug);\n"); - puts("VL_FATAL_MT("); - putsQuoted(protect(modp->fileline()->filename())); - puts(", "); - puts(cvtToStr(modp->fileline()->lineno())); - puts(", \"\",\n"); - puts("\"Verilated model didn't "); - if (initial) puts("DC "); - puts("converge\\n\"\n"); - puts("\"- See https://verilator.org/warn/DIDNOTCONVERGE\");\n"); - puts("} else {\n"); - puts("__Vchange = " + topModNameProtected + "__" + protect("_change_request") - + "(&(vlSymsp->TOP));\n"); - puts("}\n"); - } - puts("} while (" - + (v3Global.rootp()->changeRequest() ? std::string{"VL_UNLIKELY(__Vchange)"} - : std::string{"0"}) - + ");\n"); - } - void emitStandardMethods1(AstNodeModule* modp) { UASSERT_OBJ(modp->isTop(), modp, "Attempting to emitWrapEval for non-top class"); const string topModNameProtected = prefixNameProtect(modp); const string selfDecl = "(" + topModNameProtected + "* vlSelf)"; - putSectionDelimiter("Evaluation loop"); + putSectionDelimiter("Evaluation function"); // Forward declarations puts("\n"); - puts("void " + topModNameProtected + "__" + protect("_eval_initial") + selfDecl + ";\n"); - puts("void " + topModNameProtected + "__" + protect("_eval_settle") + selfDecl + ";\n"); - puts("void " + topModNameProtected + "__" + protect("_eval") + selfDecl + ";\n"); - if (v3Global.rootp()->changeRequest()) { - puts("QData " + topModNameProtected + "__" + protect("_change_request") + selfDecl - + ";\n"); - } puts("#ifdef VL_DEBUG\n"); puts("void " + topModNameProtected + "__" + protect("_eval_debug_assertions") + selfDecl + ";\n"); puts("#endif // VL_DEBUG\n"); - puts("void " + topModNameProtected + "__" + protect("_final") + selfDecl + ";\n"); + puts("void " + topModNameProtected + "__" + protect("_eval_static") + selfDecl + ";\n"); + puts("void " + topModNameProtected + "__" + protect("_eval_initial") + selfDecl + ";\n"); + puts("void " + topModNameProtected + "__" + protect("_eval_settle") + selfDecl + ";\n"); + puts("void " + topModNameProtected + "__" + protect("_eval") + selfDecl + ";\n"); - // _eval_initial_loop - puts("\nstatic void " + protect("_eval_initial_loop") + "(" + symClassVar() + ")" - + " {\n"); - puts("vlSymsp->__Vm_didInit = true;\n"); - puts(topModNameProtected + "__" + protect("_eval_initial") + "(&(vlSymsp->TOP));\n"); - emitSettleLoop(modp, /* initial: */ true); - ensureNewLine(); - puts("}\n"); - } - - void emitStandardMethods2(AstNodeModule* modp) { - const string topModNameProtected = prefixNameProtect(modp); // ::eval_step puts("\nvoid " + topClassName() + "::eval_step() {\n"); puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+++++TOP Evaluate " + topClassName() @@ -416,9 +344,17 @@ class EmitCModel final : public EmitCFunc { + "(&(vlSymsp->TOP));\n"); puts("#endif // VL_DEBUG\n"); - putsDecoration("// Initialize\n"); - puts("if (VL_UNLIKELY(!vlSymsp->__Vm_didInit)) " + protect("_eval_initial_loop") - + "(vlSymsp);\n"); + if (v3Global.opt.trace()) puts("vlSymsp->__Vm_activity = true;\n"); + + if (v3Global.hasEvents()) puts("vlSymsp->clearTriggeredEvents();\n"); + + puts("if (VL_UNLIKELY(!vlSymsp->__Vm_didInit)) {\n"); + puts("vlSymsp->__Vm_didInit = true;\n"); + puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ Initial\\n\"););\n"); + puts(topModNameProtected + "__" + protect("_eval_static") + "(&(vlSymsp->TOP));\n"); + puts(topModNameProtected + "__" + protect("_eval_initial") + "(&(vlSymsp->TOP));\n"); + puts(topModNameProtected + "__" + protect("_eval_settle") + "(&(vlSymsp->TOP));\n"); + puts("}\n"); if (v3Global.opt.threads() == 1) { const uint32_t mtaskId = 0; @@ -432,7 +368,8 @@ class EmitCModel final : public EmitCFunc { puts("VL_EXEC_TRACE_ADD_RECORD(vlSymsp).evalBegin();\n"); } - emitSettleLoop(modp, /* initial: */ false); + puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ Eval\\n\"););\n"); + puts(topModNameProtected + "__" + protect("_eval") + "(&(vlSymsp->TOP));\n"); putsDecoration("// Evaluate cleanup\n"); if (v3Global.opt.threads() == 1) { @@ -444,8 +381,10 @@ class EmitCModel final : public EmitCFunc { puts("}\n"); } - void emitStandardMethods3(AstNodeModule* modp) { + void emitStandardMethods2(AstNodeModule* modp) { const string topModNameProtected = prefixNameProtect(modp); + const string selfDecl = "(" + topModNameProtected + "* vlSelf)"; + // ::eval_end_step if (v3Global.needTraceDumper() && !optSystemC()) { puts("\nvoid " + topClassName() + "::eval_end_step() {\n"); @@ -473,9 +412,12 @@ class EmitCModel final : public EmitCFunc { } putSectionDelimiter("Invoke final blocks"); + // Forward declarations + puts("\n"); + puts("void " + topModNameProtected + "__" + protect("_eval_final") + selfDecl + ";\n"); // ::final puts("\nVL_ATTR_COLD void " + topClassName() + "::final() {\n"); - puts(/**/ topModNameProtected + "__" + protect("_final") + "(&(vlSymsp->TOP));\n"); + puts(/**/ topModNameProtected + "__" + protect("_eval_final") + "(&(vlSymsp->TOP));\n"); puts("}\n"); } @@ -599,7 +541,6 @@ class EmitCModel final : public EmitCFunc { emitDestructorImplementation(); emitStandardMethods1(modp); emitStandardMethods2(modp); - emitStandardMethods3(modp); if (v3Global.opt.trace()) { emitTraceMethods(modp); } if (v3Global.opt.savable()) { emitSerializationFunctions(); } diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index f9baa3fd5..6b67ece1e 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -443,6 +443,7 @@ void EmitCSyms::emitSymHdr() { puts("uint32_t __Vm_baseCode = 0;" " ///< Used by trace routines when tracing multiple models\n"); } + if (v3Global.hasEvents()) puts("std::vector __Vm_triggeredEvents;\n"); puts("bool __Vm_didInit = false;\n"); if (v3Global.opt.profExec()) { @@ -506,6 +507,22 @@ void EmitCSyms::emitSymHdr() { puts("\n// METHODS\n"); puts("const char* name() { return TOP.name(); }\n"); + if (v3Global.hasEvents()) { + puts("void enqueueTriggeredEventForClearing(VlEvent& event) {\n"); + puts("#ifdef VL_DEBUG\n"); + puts("if (VL_UNLIKELY(!event.isTriggered())) {\n"); + puts("VL_FATAL_MT(__FILE__, __LINE__, __FILE__, \"event passed to " + "'enqueueTriggeredEventForClearing' was not triggered\");\n"); + puts("}\n"); + puts("#endif\n"); + puts("__Vm_triggeredEvents.push_back(&event);\n"); + puts("}\n"); + puts("void clearTriggeredEvents() {\n"); + puts("for (const auto eventp : __Vm_triggeredEvents) eventp->clearTriggered();\n"); + puts("__Vm_triggeredEvents.clear();\n"); + puts("}\n"); + } + if (v3Global.needTraceDumper()) { if (!optSystemC()) puts("void _traceDump();\n"); puts("void _traceDumpOpen();\n"); diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 594795fa5..bd8956d96 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -421,6 +421,16 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor { puts(")"); } + virtual void visit(AstCMethodHard* nodep) override { + iterate(nodep->fromp()); + puts("." + nodep->name() + "("); + for (AstNode* pinp = nodep->pinsp(); pinp; pinp = pinp->nextp()) { + if (pinp != nodep->pinsp()) puts(", "); + iterate(pinp); + } + puts(")"); + } + // Operators virtual void emitVerilogFormat(AstNode* nodep, const string& format, AstNode* lhsp = nullptr, AstNode* const rhsp = nullptr, AstNode* thsp = nullptr, diff --git a/src/V3EmitXml.cpp b/src/V3EmitXml.cpp index d77a959ad..586690890 100644 --- a/src/V3EmitXml.cpp +++ b/src/V3EmitXml.cpp @@ -206,7 +206,6 @@ class EmitXmlFileVisitor final : public VNVisitor { } else if (nodep->attrClocker() == VVarAttrClocker::CLOCKER_NO) { puts(" clocker=\"false\""); } - if (nodep->attrClockEn()) puts(" clock_enable=\"true\""); if (nodep->attrIsolateAssign()) puts(" isolate_assignments=\"true\""); if (nodep->isLatched()) puts(" latched=\"true\""); if (nodep->isSigPublic()) puts(" public=\"true\""); diff --git a/src/V3Error.h b/src/V3Error.h index 077260995..ab7177d34 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -75,7 +75,7 @@ public: CASEX, // Casex CASTCONST, // Cast is constant CDCRSTLOGIC, // Logic in async reset path - CLKDATA, // Clock used as data + CLKDATA, // Clock used as data. Historical, never issued. CMPCONST, // Comparison is constant due to limited range COLONPLUS, // :+ instead of +: COMBDLY, // Combinatorial delayed assignment @@ -85,11 +85,11 @@ public: DEPRECATED, // Feature will be deprecated ENDLABEL, // End lable name mismatch EOFNEWLINE, // End-of-file missing newline - GENCLK, // Generated Clock + GENCLK, // Generated Clock. Historical, never issued. HIERBLOCK, // Ignored hierarchical block setting IFDEPTH, // If statements too deep IGNOREDRETURN, // Ignoring return value (function as task) - IMPERFECTSCH, // Imperfect schedule (disabled by default) + IMPERFECTSCH, // Imperfect schedule (disabled by default). Historical, never issued. IMPLICIT, // Implicit wire IMPORTSTAR, // Import::* in $unit IMPURE, // Impure function not being inlined @@ -124,7 +124,7 @@ public: TICKCOUNT, // Too large tick count TIMESCALEMOD, // Need timescale for module UNDRIVEN, // No drivers - UNOPT, // Unoptimizable block + UNOPT, // Unoptimizable block. Historical, never issued. UNOPTFLAT, // Unoptimizable block after flattening UNOPTTHREADS, // Thread partitioner unable to fill all requested threads UNPACKED, // Unsupported unpacked diff --git a/src/V3FileLine.cpp b/src/V3FileLine.cpp index 90e3c85e7..11c88fba5 100644 --- a/src/V3FileLine.cpp +++ b/src/V3FileLine.cpp @@ -324,8 +324,6 @@ void FileLine::warnStyleOff(bool flag) { bool FileLine::warnIsOff(V3ErrorCode code) const { if (!m_warnOn.test(code)) return true; if (!defaultFileLine().m_warnOn.test(code)) return true; // Global overrides local - // UNOPTFLAT implies UNOPT - if (code == V3ErrorCode::UNOPT && !m_warnOn.test(V3ErrorCode::UNOPTFLAT)) return true; if ((code.lintError() || code.styleError()) && !m_warnOn.test(V3ErrorCode::I_LINT)) { return true; } diff --git a/src/V3GenClk.cpp b/src/V3GenClk.cpp deleted file mode 100644 index a358ca2aa..000000000 --- a/src/V3GenClk.cpp +++ /dev/null @@ -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); -} diff --git a/src/V3GenClk.h b/src/V3GenClk.h deleted file mode 100644 index df20f0c82..000000000 --- a/src/V3GenClk.h +++ /dev/null @@ -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 diff --git a/src/V3Global.h b/src/V3Global.h index 84a75d7e2..48d99581d 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -104,6 +104,7 @@ class V3Global final { // Experimenting with always requiring heavy, see (#2701) bool m_needTraceDumper = false; // Need __Vm_dumperp in symbols bool m_dpi = false; // Need __Dpi include files + bool m_hasEvents = false; // Design uses SystemVerilog named events bool m_hasForceableSignals = false; // Need to apply V3Force pass bool m_hasSCTextSections = false; // Has `systemc_* sections that need to be emitted bool m_useParallelBuild = false; // Use parallel build for model @@ -148,6 +149,8 @@ public: void needTraceDumper(bool flag) { m_needTraceDumper = flag; } bool dpi() const { return m_dpi; } void dpi(bool flag) { m_dpi = flag; } + bool hasEvents() const { return m_hasEvents; } + void setHasEvents() { m_hasEvents = true; } bool hasForceableSignals() const { return m_hasForceableSignals; } void setHasForceableSignals() { m_hasForceableSignals = true; } bool hasSCTextSections() const { return m_hasSCTextSections; } diff --git a/src/V3LifePost.cpp b/src/V3LifePost.cpp index 14405ee3d..e44583a1e 100644 --- a/src/V3LifePost.cpp +++ b/src/V3LifePost.cpp @@ -131,8 +131,8 @@ struct LifePostLocation { class LifePostDlyVisitor final : public VNVisitor { private: // NODE STATE - // Cleared on entire tree - // AstVarScope::user4() -> AstVarScope*: Passed to LifePostElim to substitute this var + // AstVarScope::user1() -> bool: referenced outside _eval__nba + // AstVarScope::user4() -> AstVarScope*: Passed to LifePostElim to substitute this var const VNUser4InUse m_inuser4; // STATE @@ -155,6 +155,9 @@ private: const V3Graph* m_mtasksGraphp = nullptr; // Mtask tracking graph std::unique_ptr m_checker; + const AstCFunc* const m_evalNbap; // The _eval__nba function + bool m_inEvalNba = false; // Traversing under _eval__nba + // METHODS VL_DEBUG_FUNC; // Declare debug() @@ -185,8 +188,11 @@ private: return true; } void squashAssignposts() { - for (auto& itr : m_assignposts) { - const LifePostLocation* const app = &itr.second; + for (auto& pair : m_assignposts) { + // If referenced external to _eval__nba, don't optimize + if (pair.first->user1()) continue; + + const LifePostLocation* const app = &pair.second; const AstVarRef* const lhsp = VN_AS(app->nodep->lhsp(), VarRef); // original var const AstVarRef* const rhsp = VN_AS(app->nodep->rhsp(), VarRef); // dly var AstVarScope* const dlyVarp = rhsp->varScopep(); @@ -274,6 +280,12 @@ private: LifePostElimVisitor{nodep}; } virtual void visit(AstVarRef* nodep) override { + // Mark variables referenced outside _eval__nba + if (!m_inEvalNba) { + nodep->varScopep()->user1(true); + return; + } + // Consumption/generation of a variable, const AstVarScope* const vscp = nodep->varScopep(); UASSERT_OBJ(vscp, nodep, "Scope not assigned"); @@ -288,6 +300,7 @@ private: // The pre-assignment into the dly var should not count as its // first write; we only want to consider reads and writes that // would still happen if the dly var were eliminated. + if (!m_inEvalNba) iterateChildren(nodep); } virtual void visit(AstAssignPost* nodep) override { // Don't record ASSIGNPOST in the read/write maps, record them in a @@ -315,9 +328,11 @@ private: } virtual void visit(AstExecGraph* nodep) override { // Treat the ExecGraph like a call to each mtask body - UASSERT_OBJ(!m_mtasksGraphp, nodep, "Cannot handle more than one AstExecGraph"); - m_mtasksGraphp = nodep->depGraphp(); - for (V3GraphVertex* mtaskVxp = m_mtasksGraphp->verticesBeginp(); mtaskVxp; + if (m_inEvalNba) { + UASSERT_OBJ(!m_mtasksGraphp, nodep, "Cannot handle more than one AstExecGraph"); + m_mtasksGraphp = nodep->depGraphp(); + } + for (V3GraphVertex* mtaskVxp = nodep->depGraphp()->verticesBeginp(); mtaskVxp; mtaskVxp = mtaskVxp->verticesNextp()) { const ExecMTask* const mtaskp = dynamic_cast(mtaskVxp); m_execMTaskp = mtaskp; @@ -328,6 +343,8 @@ private: } virtual void visit(AstCFunc* nodep) override { if (!m_tracingCall && !nodep->entryPoint()) return; + VL_RESTORER(m_inEvalNba); + if (nodep == m_evalNbap) m_inEvalNba = true; m_tracingCall = false; iterateChildren(nodep); } @@ -337,7 +354,10 @@ private: public: // CONSTRUCTORS - explicit LifePostDlyVisitor(AstNetlist* nodep) { iterate(nodep); } + explicit LifePostDlyVisitor(AstNetlist* netlistp) + : m_evalNbap{netlistp->evalNbap()} { + iterate(netlistp); + } virtual ~LifePostDlyVisitor() override { V3Stats::addStat("Optimizations, Lifetime postassign deletions", m_statAssnDel); } diff --git a/src/V3LinkLValue.cpp b/src/V3LinkLValue.cpp index 6ab7eb8e7..e33ab7eb8 100644 --- a/src/V3LinkLValue.cpp +++ b/src/V3LinkLValue.cpp @@ -92,6 +92,13 @@ private: iterateAndNextNull(nodep->lhsp()); } } + virtual void visit(AstFireEvent* nodep) override { + VL_RESTORER(m_setRefLvalue); + { + m_setRefLvalue = VAccess::WRITE; + iterateAndNextNull(nodep->operandp()); + } + } virtual void visit(AstCastDynamic* nodep) override { VL_RESTORER(m_setRefLvalue); { diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index df1e36436..93222023e 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -305,7 +305,7 @@ private: VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (nodep->attrType() == VAttrType::VAR_CLOCK_ENABLE) { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); - m_varp->attrClockEn(true); + nodep->v3warn(DEPRECATED, "'clock_enable' attribute is deprecated and has no effect"); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (nodep->attrType() == VAttrType::VAR_FORCEABLE) { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp index a0d198bc7..c01a58632 100644 --- a/src/V3LinkResolve.cpp +++ b/src/V3LinkResolve.cpp @@ -148,39 +148,8 @@ private: virtual void visit(AstSenItem* nodep) override { // Remove bit selects, and bark if it's not a simple variable iterateChildren(nodep); - if (nodep->isClocked()) { - // If it's not a simple variable wrap in a temporary - // This is a bit unfortunate as we haven't done width resolution - // and any width errors will look a bit odd, but it works. - AstNode* const sensp = nodep->sensp(); - if (sensp && !VN_IS(sensp, NodeVarRef) && !VN_IS(sensp, Const)) { - // Make a new temp wire - const string newvarname = "__Vsenitemexpr" + cvtToStr(++m_senitemCvtNum); - AstVar* const newvarp = new AstVar(sensp->fileline(), VVarType::MODULETEMP, - newvarname, VFlagLogicPacked(), 1); - // We can't just add under the module, because we may be - // inside a generate, begin, etc. - // We know a SenItem should be under a SenTree/Always etc, - // we we'll just hunt upwards - AstNode* addwherep = nodep; // Add to this element's next - while (VN_IS(addwherep, SenItem) || VN_IS(addwherep, SenTree)) { - addwherep = addwherep->backp(); - } - if (!VN_IS(addwherep, Always)) { // Assertion perhaps? - sensp->v3warn(E_UNSUPPORTED, - "Unsupported: Non-single-bit pos/negedge clock statement under " - "some complicated block"); - addwherep = m_modp; - } - addwherep->addNext(newvarp); - - sensp->replaceWith(new AstVarRef(sensp->fileline(), newvarp, VAccess::READ)); - AstAssignW* const assignp = new AstAssignW( - sensp->fileline(), new AstVarRef(sensp->fileline(), newvarp, VAccess::WRITE), - sensp); - addwherep->addNext(assignp); - } - } else { // Old V1995 sensitivity list; we'll probably mostly ignore + if (!nodep->isClocked()) { + // Old V1995 sensitivity list; we'll probably mostly ignore bool did = true; while (did) { did = false; @@ -205,9 +174,7 @@ private: } } } - if (!VN_IS(nodep->sensp(), NodeVarRef) - && !VN_IS(nodep->sensp(), EnumItemRef) // V3Const will cleanup - && !nodep->isIllegal()) { + if (nodep->isIllegal()) { if (debug()) nodep->dumpTree(cout, "-tree: "); nodep->v3warn(E_UNSUPPORTED, "Unsupported: Complex statement in sensitivity list"); } diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 1b74f1062..88e1b4d31 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1192,7 +1192,9 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char } }); DECL_OPTION("-o", Set, &m_exeName); - DECL_OPTION("-order-clock-delay", OnOff, &m_orderClockDly); + DECL_OPTION("-order-clock-delay", CbOnOff, [fl](bool flag) { + fl->v3warn(DEPRECATED, "Option order-clock-delay is deprecated and has no effect."); + }); DECL_OPTION("-output-split", Set, &m_outputSplit); DECL_OPTION("-output-split-cfuncs", CbVal, [this, fl](const char* valp) { m_outputSplitCFuncs = std::atoi(valp); diff --git a/src/V3Options.h b/src/V3Options.h index f9cf8f5d5..01c9903f8 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -246,7 +246,6 @@ private: bool m_gmake = false; // main switch: --make gmake bool m_main = false; // main swithc: --main bool m_mergeConstPool = true; // main switch: --merge-const-pool - bool m_orderClockDly = true; // main switch: --order-clock-delay bool m_outFormatOk = false; // main switch: --cc, --sc or --sp was specified bool m_pedantic = false; // main switch: --Wpedantic bool m_pinsScUint = false; // main switch: --pins-sc-uint @@ -459,7 +458,6 @@ public: bool traceUnderscore() const { return m_traceUnderscore; } bool main() const { return m_main; } bool mergeConstPool() const { return m_mergeConstPool; } - bool orderClockDly() const { return m_orderClockDly; } bool outFormatOk() const { return m_outFormatOk; } bool keepTempFiles() const { return (V3Error::debugDefault() != 0); } bool pedantic() const { return m_pedantic; } diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 4f2890be6..7eaaf466a 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -18,11 +18,6 @@ // Compute near optimal scheduling of always/wire statements // Make a graph of the entire netlist // -// Add master "*INPUTS*" vertex. -// For inputs on top level -// Add vertex for each input var. -// Add edge INPUTS->var_vertex -// // For seq logic // Add logic_sensitive_vertex for this list of SenItems // Add edge for each sensitive_var->logic_sensitive_vertex @@ -93,6 +88,7 @@ #include "V3SenTree.h" #include "V3SplitVar.h" #include "V3Stats.h" +#include "V3Sched.h" #include "V3Order.h" #include "V3OrderGraph.h" @@ -105,73 +101,6 @@ #include #include #include -#include - -//###################################################################### -// Utilities - -static bool domainsExclusive(const AstSenTree* fromp, const AstSenTree* top) { - // Return 'true' if we can prove that both 'from' and 'to' cannot both - // be active on the same eval pass, or false if we can't prove this. - // - // This detects the case of 'always @(posedge clk)' - // and 'always @(negedge clk)' being exclusive. It also detects - // that initial/settle blocks and post-initial blocks are exclusive. - // - // Are there any other cases we need to handle? Maybe not, - // because these are not exclusive: - // always @(posedge A or posedge B) - // always @(negedge A) - // - // ... unless you know more about A and B, which sounds hard. - - const bool toInitial = top->hasInitial() || top->hasSettle(); - const bool fromInitial = fromp->hasInitial() || fromp->hasSettle(); - if (toInitial != fromInitial) return true; - - const AstSenItem* const fromSenListp = fromp->sensesp(); - const AstSenItem* const toSenListp = top->sensesp(); - - UASSERT_OBJ(fromSenListp, fromp, "sensitivity list empty"); - UASSERT_OBJ(toSenListp, top, "sensitivity list empty"); - - if (fromSenListp->nextp()) return false; - if (toSenListp->nextp()) return false; - - const AstNodeVarRef* const fromVarrefp = fromSenListp->varrefp(); - const AstNodeVarRef* const toVarrefp = toSenListp->varrefp(); - if (!fromVarrefp || !toVarrefp) return false; - - // We know nothing about the relationship between different clocks here, - // so give up on proving anything. - if (fromVarrefp->varScopep() != toVarrefp->varScopep()) return false; - - return fromSenListp->edgeType().exclusiveEdge(toSenListp->edgeType()); -} - -// Predicate returning true if the LHS of the given assignment is a signal marked as clocker -static bool isClockerAssignment(AstNodeAssign* nodep) { - class Visitor final : public VNVisitor { - public: - bool m_clkAss = false; // There is signals marked as clocker in the assignment - private: - // METHODS - VL_DEBUG_FUNC; // Declare debug() - virtual void visit(AstNodeAssign* nodep) override { - if (const AstVarRef* const varrefp = VN_CAST(nodep->lhsp(), VarRef)) { - if (varrefp->varp()->attrClocker() == VVarAttrClocker::CLOCKER_YES) { - m_clkAss = true; - UINFO(6, "node was marked as clocker " << varrefp << endl); - } - } - iterateChildren(nodep->rhsp()); - } - virtual void visit(AstNodeMath*) override {} // Accelerate - virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } - } visitor; - visitor.iterate(nodep); - return visitor.m_clkAss; -} //###################################################################### // Functions for above graph classes @@ -188,119 +117,6 @@ void OrderGraph::loopsVertexCb(V3GraphVertex* vertexp) { } } -//###################################################################### -// The class is used for propagating the clocker attribute for further -// avoiding marking clock signals as circular. -// Transformation: -// while (newClockerMarked) -// check all assignments -// if RHS is marked as clocker: -// mark LHS as clocker as well. -// newClockerMarked = true; -// -// In addition it also check whether clock and data signals are mixed, and -// produce a CLKDATA warning if so. -// - -class OrderClkMarkVisitor final : public VNVisitor { - bool m_hasClk = false; // flag indicating whether there is clock signal on rhs - bool m_inClocked = false; // Currently inside a sequential block - bool m_newClkMarked; // Flag for deciding whether a new run is needed - bool m_inAss = false; // Currently inside of a assignment - int m_childClkWidth = 0; // If in hasClk, width of clock signal in child - - // METHODS - VL_DEBUG_FUNC; // Declare debug() - - virtual void visit(AstNodeAssign* nodep) override { - m_hasClk = false; - m_inAss = true; - m_childClkWidth = 0; - iterateAndNextNull(nodep->rhsp()); - m_inAss = false; - if (m_hasClk) { - // do the marking - if (nodep->lhsp()->width() > m_childClkWidth) { - nodep->v3warn(CLKDATA, "Clock is assigned to part of data signal " - << nodep->lhsp()->prettyNameQ()); - UINFO(4, "CLKDATA: lhs with width " << nodep->lhsp()->width() << endl); - UINFO(4, " but rhs clock with width " << m_childClkWidth << endl); - return; // skip the marking - } - - const AstVarRef* const lhsp = VN_CAST(nodep->lhsp(), VarRef); - if (lhsp && (lhsp->varp()->attrClocker() == VVarAttrClocker::CLOCKER_UNKNOWN)) { - lhsp->varp()->attrClocker(VVarAttrClocker::CLOCKER_YES); // mark as clocker - m_newClkMarked = true; // enable a further run since new clocker is marked - UINFO(5, "node is newly marked as clocker by assignment " << lhsp << endl); - } - } - } - virtual void visit(AstVarRef* nodep) override { - if (m_inAss && nodep->varp()->attrClocker() == VVarAttrClocker::CLOCKER_YES) { - if (m_inClocked) { - nodep->v3warn(CLKDATA, - "Clock used as data (on rhs of assignment) in sequential block " - << nodep->prettyNameQ()); - } else { - m_hasClk = true; - m_childClkWidth = nodep->width(); // Pass up - UINFO(5, "node is already marked as clocker " << nodep << endl); - } - } - } - virtual void visit(AstConcat* nodep) override { - if (m_inAss) { - iterateAndNextNull(nodep->lhsp()); - const int lw = m_childClkWidth; - iterateAndNextNull(nodep->rhsp()); - const int rw = m_childClkWidth; - m_childClkWidth = lw + rw; // Pass up - } - } - virtual void visit(AstNodeSel* nodep) override { - if (m_inAss) { - iterateChildren(nodep); - // Pass up result width - if (m_childClkWidth > nodep->width()) m_childClkWidth = nodep->width(); - } - } - virtual void visit(AstSel* nodep) override { - if (m_inAss) { - iterateChildren(nodep); - if (m_childClkWidth > nodep->width()) m_childClkWidth = nodep->width(); - } - } - virtual void visit(AstReplicate* nodep) override { - if (m_inAss) { - iterateChildren(nodep); - if (VN_IS(nodep->rhsp(), Const)) { - m_childClkWidth = m_childClkWidth * VN_AS(nodep->rhsp(), Const)->toUInt(); - } else { - m_childClkWidth = nodep->width(); // can not check in this case. - } - } - } - virtual void visit(AstActive* nodep) override { - m_inClocked = nodep->hasClocked(); - iterateChildren(nodep); - m_inClocked = false; - } - virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } - - // CONSTRUCTORS - explicit OrderClkMarkVisitor(AstNode* nodep) { - do { - m_newClkMarked = false; - iterate(nodep); - } while (m_newClkMarked); - } - virtual ~OrderClkMarkVisitor() override = default; - -public: - static void process(AstNetlist* nodep) { OrderClkMarkVisitor{nodep}; } -}; - //###################################################################### // Order information stored under each AstNode::user1p()... @@ -363,35 +179,38 @@ class OrderBuildVisitor final : public VNVisitor { // NODE STATE // AstVarScope::user1 -> OrderUser instance for variable (via m_orderUser) // AstVarScope::user2 -> VarUsage within logic blocks + // AstVarScope::user3 -> bool: Hybrid sensitivity const VNUser1InUse user1InUse; const VNUser2InUse user2InUse; + const VNUser3InUse user3InUse; AstUser1Allocator m_orderUser; // STATE // The ordering graph built by this visitor OrderGraph* const m_graphp = new OrderGraph; - // Singleton vertex that all top level inputs depend on - OrderInputsVertex* const m_inputsVxp = new OrderInputsVertex{m_graphp, nullptr}; // Singleton DPI Export trigger event vertex OrderVarVertex* const m_dpiExportTriggerVxp = v3Global.rootp()->dpiExportTriggerp() ? getVarVertex(v3Global.rootp()->dpiExportTriggerp(), VarVertexType::STD) : nullptr; - OrderLogicVertex* m_activeSenVxp = nullptr; // Sensitivity vertex for clocked logic OrderLogicVertex* m_logicVxp = nullptr; // Current loic block being analyzed + // Map from Trigger reference AstSenItem to the original AstSenTree + const std::unordered_map& m_trigToSen; + // Current AstScope being processed AstScope* m_scopep = nullptr; - // Sensitivity list for non-combinational logic (incl. initial and settle), - // nullptr for combinational logic + // Sensitivity list for clocked logic, nullptr for combinational and hybird logic AstSenTree* m_domainp = nullptr; + // Sensitivity list for hybrid logic, nullptr for everything else + AstSenTree* m_hybridp = nullptr; bool m_inClocked = false; // Underneath clocked AstActive - bool m_inClkAss = false; // Underneath AstNodeAssign to clock bool m_inPre = false; // Underneath AstAssignPre bool m_inPost = false; // Underneath AstAssignPost/AstAlwaysPost bool m_inPostponed = false; // Underneath AstAlwaysPostponed + std::function m_readTriggersCombLogic; // METHODS VL_DEBUG_FUNC; // Declare debug() @@ -401,10 +220,7 @@ class OrderBuildVisitor final : public VNVisitor { // Reset VarUsage AstNode::user2ClearTree(); // Create LogicVertex for this logic node - m_logicVxp = new OrderLogicVertex(m_graphp, m_scopep, m_domainp, nodep); - // If this logic has a clocked activation, add a link from the sensitivity list LogicVertex - // to this LogicVertex. - if (m_activeSenVxp) new OrderEdge(m_graphp, m_activeSenVxp, m_logicVxp, WEIGHT_NORMAL); + m_logicVxp = new OrderLogicVertex{m_graphp, m_scopep, m_domainp, m_hybridp, nodep}; // Gather variable dependencies based on usage iterateChildren(nodep); // Finished with this logic @@ -416,222 +232,191 @@ class OrderBuildVisitor final : public VNVisitor { } // VISITORS - virtual void visit(AstSenTree* nodep) override { - // This should only find the global AstSenTrees under the AstTopScope, which we ignore - // here. We visit AstSenTrees separately when encountering the AstActive that references - // them. - UASSERT_OBJ(!m_scopep, nodep, "AstSenTrees should have been made global in V3ActiveTop"); - } - virtual void visit(AstScope* nodep) override { - UASSERT_OBJ(!m_scopep, nodep, "Should not nest"); - m_scopep = nodep; - iterateChildren(nodep); - m_scopep = nullptr; - } virtual void visit(AstActive* nodep) override { UASSERT_OBJ(!nodep->sensesStorep(), nodep, "AstSenTrees should have been made global in V3ActiveTop"); UASSERT_OBJ(m_scopep, nodep, "AstActive not under AstScope"); UASSERT_OBJ(!m_logicVxp, nodep, "AstActive under logic"); - UASSERT_OBJ(!m_inClocked && !m_activeSenVxp && !m_domainp, nodep, "Should not nest"); + UASSERT_OBJ(!m_inClocked && !m_domainp & !m_hybridp, nodep, "Should not nest"); - m_inClocked = nodep->sensesp()->hasClocked(); + // This is the original sensitivity of the block (i.e.: not the ref into the TRIGGERVEC) - // Analyze variable references in sensitivity list. Note that non-clocked sensitivity lists - // don't reference any variables (have no clocks), so the sensitivity list vertex would - // have no incoming dependencies and is hence redundant, therefore we only do this for - // clocked sensitivity lists. - if (m_inClocked) { - // Add LogicVertex for the sensitivity list of this AstActive. - m_activeSenVxp = new OrderLogicVertex(m_graphp, m_scopep, nodep->sensesp(), nodep); - // Analyze variables in the sensitivity list - iterateChildren(nodep->sensesp()); + const AstSenTree* const senTreep = nodep->sensesp()->hasCombo() + ? nodep->sensesp() + : m_trigToSen.at(nodep->sensesp()->sensesp()); + + m_inClocked = senTreep->hasClocked(); + + // Note: We don't need to analyse the sensitivity list, as currently all sensitivity + // lists simply reference an entry in a trigger vector, which are all set external to + // the code being ordered. + + // Combinational and hybrid logic will have it's domain assigned based on the driver + // domains. For clocked logic, we already know its domain. + if (!senTreep->hasCombo() && !senTreep->hasHybrid()) m_domainp = nodep->sensesp(); + + // Hybrid logic also includes additional sensitivities + if (senTreep->hasHybrid()) { + m_hybridp = nodep->sensesp(); + // Mark AstVarScopes that are explicit sensitivities + AstNode::user3ClearTree(); + senTreep->foreach([](const AstVarRef* refp) { // + refp->varScopep()->user3(true); + }); + m_readTriggersCombLogic = [](const AstVarScope* vscp) { return !vscp->user3(); }; + } else { + // Always triggers + m_readTriggersCombLogic = [](const AstVarScope*) { return true; }; } - // Ignore the sensitivity domain for combinational logic. We will assign combinational - // logic to a domain later, based on the domains of incoming variables. - if (!nodep->sensesp()->hasCombo()) m_domainp = nodep->sensesp(); - // Analyze logic underneath iterateChildren(nodep); // m_inClocked = false; - m_activeSenVxp = nullptr; m_domainp = nullptr; + m_hybridp = nullptr; } virtual void visit(AstNodeVarRef* nodep) override { // As we explicitly not visit (see ignored nodes below) any subtree that is not relevant // for ordering, we should be able to assert this: UASSERT_OBJ(m_scopep, nodep, "AstVarRef not under scope"); - UASSERT_OBJ(m_logicVxp || m_activeSenVxp, nodep, - "AstVarRef not under logic nor sensitivity list"); + UASSERT_OBJ(m_logicVxp, nodep, "AstVarRef not under logic"); AstVarScope* const varscp = nodep->varScopep(); UASSERT_OBJ(varscp, nodep, "Var didn't get varscoped in V3Scope.cpp"); - if (!m_logicVxp) { - // Variable reference in sensitivity list. Add clock dependency. - UASSERT_OBJ(!nodep->access().isWriteOrRW(), nodep, - "How can a sensitivity list be writing a variable?"); - // Add edge from sensed VarStdVertex -> to sensitivity list LogicVertex - OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); - varVxp->isClock(true); - new OrderEdge(m_graphp, varVxp, m_activeSenVxp, WEIGHT_MEDIUM); - } else { - // Variable reference in logic. Add data dependency. + // Variable reference in logic. Add data dependency. - // Check whether this variable was already generated/consumed in the same logic. We - // don't want to add extra edges if the logic has many usages of the same variable, - // so only proceed on first encounter. - const bool prevGen = varscp->user2() & VU_GEN; - const bool prevCon = varscp->user2() & VU_CON; + // Check whether this variable was already generated/consumed in the same logic. We + // don't want to add extra edges if the logic has many usages of the same variable, + // so only proceed on first encounter. + const bool prevGen = varscp->user2() & VU_GEN; + const bool prevCon = varscp->user2() & VU_CON; - // Compute whether the variable is produced (written) here - bool gen = false; - if (!prevGen && nodep->access().isWriteOrRW()) { - gen = true; - if (m_inPostponed) { - // IEE 1800-2017 (4.2.9) forbids any value updates in the postponed region, but - // Verilator generated trigger signals for $strobe are cleared after the - // display is executed. This is both safe to ignore (because their single read - // is in the same AstAlwaysPostponed, just prior to the clear), and is - // necessary to ignore to avoid a circular logic (UNOPTFLAT) warning. - UASSERT_OBJ(prevCon, nodep, "Should have been consumed in same process"); - gen = false; - } + // Compute whether the variable is produced (written) here + bool gen = false; + if (!prevGen && nodep->access().isWriteOrRW()) { + gen = true; + if (m_inPostponed) { + // IEEE 1800-2017 (4.2.9) forbids any value updates in the postponed region, but + // Verilator generated trigger signals for $strobe are cleared after the + // display is executed. This is both safe to ignore (because their single read + // is in the same AstAlwaysPostponed, just prior to the clear), and is + // necessary to ignore to avoid a circular logic (UNOPTFLAT) warning. + UASSERT_OBJ(prevCon, nodep, "Should have been consumed in same process"); + gen = false; } + } - // Compute whether the value is consumed (read) here - bool con = false; - if (!prevCon && nodep->access().isReadOrRW()) { - con = true; - if (prevGen && !m_inClocked) { - // Dangerous assumption: - // If a variable is consumed in the same combinational process that produced it - // earlier, consider it something like: - // foo = 1 - // foo = foo + 1 - // and still optimize. Note this will break though: - // if (sometimes) foo = 1 - // foo = foo + 1 - // TODO: Do this properly with liveness analysis (i.e.: if live, it's consumed) - // Note however that this construct is not nicely synthesizable (yields - // latch?). - con = false; - } - - // TODO: Explain how the following two exclusions are useful - if (varscp->varp()->attrClockEn() && !m_inPre && !m_inPost && !m_inClocked) { - // 'clock_enable' attribute on this signal: user's worrying about it for us - con = false; - } - if (m_inClkAss - && (varscp->varp()->attrClocker() != VVarAttrClocker::CLOCKER_YES)) { - // 'clocker' attribute on some other signal in same logic: same as - // 'clock_enable' attribute above - con = false; - UINFO(4, "nodep used as clock_enable " << varscp << " in " - << m_logicVxp->nodep() << endl); - } + // Compute whether the value is consumed (read) here + bool con = false; + if (!prevCon && nodep->access().isReadOrRW()) { + con = true; + if (prevGen && !m_inClocked) { + // Dangerous assumption: + // If a variable is consumed in the same combinational process that produced it + // earlier, consider it something like: + // foo = 1 + // foo = foo + 1 + // and still optimize. Note this will break though: + // if (sometimes) foo = 1 + // foo = foo + 1 + // TODO: Do this properly with liveness analysis (i.e.: if live, it's consumed) + // Note however that this construct is not nicely synthesizable (yields + // latch?). + con = false; } + } - // Roles of vertices: - // VarVertexType::STD: Data dependencies for combinational logic and delayed - // assignment updates (AssignPost). - // VarVertexType::POST: Ensures all sequential blocks reading a signal do so before - // any combinational or delayed assignments update that signal. - // VarVertexType::PORD: Ensures a _d = _q AssignPre is the first write of a _d, - // before any sequential blocks write to that _d. - // VarVertexType::PRE: This is an optimization. Try to ensure that a _d = _q - // AssignPre is the last read of a _q, after all reads of that - // _q by sequential logic. Note: The model is still correct if we - // cannot satisfy this due to other constraints. If this ordering - // is possible, then combined with the PORD constraint we get - // that all writes to _d are after all reads of a _q, which then - // allows us to eliminate the _d completely and assign to the _q - // directly (this is what V3LifePost does). + // Roles of vertices: + // VarVertexType::STD: Data dependencies for combinational logic and delayed + // assignment updates (AssignPost). + // VarVertexType::POST: Ensures all sequential blocks reading a signal do so before + // any combinational or delayed assignments update that signal. + // VarVertexType::PORD: Ensures a _d = _q AssignPre is the first write of a _d, + // before any sequential blocks write to that _d. + // VarVertexType::PRE: This is an optimization. Try to ensure that a _d = _q + // AssignPre is the last read of a _q, after all reads of that + // _q by sequential logic. Note: The model is still correct if we + // cannot satisfy this due to other constraints. If this ordering + // is possible, then combined with the PORD constraint we get + // that all writes to _d are after all reads of a _q, which then + // allows us to eliminate the _d completely and assign to the _q + // directly (this is what V3LifePost does). - // Variable is produced - if (gen) { - // Update VarUsage - varscp->user2(varscp->user2() | VU_GEN); - // Add edges for produced variables - if (!m_inClocked || m_inPost) { - // Combinational logic - OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); - // Add edge from producing LogicVertex -> produced VarStdVertex - if (m_inPost) { - new OrderPostCutEdge(m_graphp, m_logicVxp, varVxp); - // Mark the VarVertex as being produced by a delayed (non-blocking) - // assignment. This is used to control marking internal clocks - // circular, which must only happen if they are generated by delayed - // assignment. - varVxp->isDelayed(true); - UINFO(5, " Found delayed assignment (post) " << varVxp << endl); - } else if (varscp->varp()->attrClocker() == VVarAttrClocker::CLOCKER_YES) { - // If the variable has the 'clocker' attribute, avoid making it - // circular by adding a hard edge instead of normal cuttable edge. - new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); - } else { - new OrderComboCutEdge(m_graphp, m_logicVxp, varVxp); - } - - // Add edge from produced VarPostVertex -> to producing LogicVertex - - // For m_inPost: - // Add edge consumed_var_POST->logic_vertex - // This prevents a consumer of the "early" value to be scheduled - // after we've changed to the next-cycle value - // ALWAYS do it: - // There maybe a wire a=b; between the two blocks - OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST); - new OrderEdge(m_graphp, postVxp, m_logicVxp, WEIGHT_POST); - } else if (m_inPre) { // AstAssignPre - // Add edge from producing LogicVertex -> produced VarPordVertex - OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD); - new OrderEdge(m_graphp, m_logicVxp, ordVxp, WEIGHT_NORMAL); - // Add edge from producing LogicVertex -> produced VarStdVertex - OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); - new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); + // Variable is produced + if (gen) { + // Update VarUsage + varscp->user2(varscp->user2() | VU_GEN); + // Add edges for produced variables + if (!m_inClocked || m_inPost) { + // Combinational logic + OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); + // Add edge from producing LogicVertex -> produced VarStdVertex + if (m_inPost) { + new OrderPostCutEdge(m_graphp, m_logicVxp, varVxp); } else { - // Sequential (clocked) logic - // Add edge from produced VarPordVertex -> to producing LogicVertex - OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD); - new OrderEdge(m_graphp, ordVxp, m_logicVxp, WEIGHT_NORMAL); - // Add edge from producing LogicVertex-> to produced VarStdVertex - OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); } - } - // Variable is consumed - if (con) { - // Update VarUsage - varscp->user2(varscp->user2() | VU_CON); - // Add edges - if (!m_inClocked || m_inPost) { - // Combinational logic + // Add edge from produced VarPostVertex -> to producing LogicVertex + + // For m_inPost: + // Add edge consumed_var_POST->logic_vertex + // This prevents a consumer of the "early" value to be scheduled + // after we've changed to the next-cycle value + // ALWAYS do it: + // There maybe a wire a=b; between the two blocks + OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST); + new OrderEdge(m_graphp, postVxp, m_logicVxp, WEIGHT_POST); + } else if (m_inPre) { // AstAssignPre + // Add edge from producing LogicVertex -> produced VarPordVertex + OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD); + new OrderEdge(m_graphp, m_logicVxp, ordVxp, WEIGHT_NORMAL); + // Add edge from producing LogicVertex -> produced VarStdVertex + OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); + new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); + } else { + // Sequential (clocked) logic + // Add edge from produced VarPordVertex -> to producing LogicVertex + OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD); + new OrderEdge(m_graphp, ordVxp, m_logicVxp, WEIGHT_NORMAL); + // Add edge from producing LogicVertex-> to produced VarStdVertex + OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); + new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); + } + } + + // Variable is consumed + if (con) { + // Update VarUsage + varscp->user2(varscp->user2() | VU_CON); + // Add edges + if (!m_inClocked || m_inPost) { + // Combinational logic + if (m_readTriggersCombLogic(varscp)) { + // Ignore explicit sensitivities OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); // Add edge from consumed VarStdVertex -> to consuming LogicVertex new OrderEdge(m_graphp, varVxp, m_logicVxp, WEIGHT_MEDIUM); - } else if (m_inPre) { - // AstAssignPre logic - // Add edge from consumed VarPreVertex -> to consuming LogicVertex - // This one is cutable (vs the producer) as there's only one such consumer, - // but may be many producers - OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE); - new OrderPreCutEdge(m_graphp, preVxp, m_logicVxp); - } else { - // Sequential (clocked) logic - // Add edge from consuming LogicVertex -> to consumed VarPreVertex - // Generation of 'pre' because we want to indicate it should be before - // AstAssignPre - OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE); - new OrderEdge(m_graphp, m_logicVxp, preVxp, WEIGHT_NORMAL); - // Add edge from consuming LogicVertex -> to consumed VarPostVertex - OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST); - new OrderEdge(m_graphp, m_logicVxp, postVxp, WEIGHT_POST); } + } else if (m_inPre) { + // AstAssignPre logic + // Add edge from consumed VarPreVertex -> to consuming LogicVertex + // This one is cutable (vs the producer) as there's only one such consumer, + // but may be many producers + OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE); + new OrderPreCutEdge(m_graphp, preVxp, m_logicVxp); + } else { + // Sequential (clocked) logic + // Add edge from consuming LogicVertex -> to consumed VarPreVertex + // Generation of 'pre' because we want to indicate it should be before + // AstAssignPre + OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE); + new OrderEdge(m_graphp, m_logicVxp, preVxp, WEIGHT_NORMAL); + // Add edge from consuming LogicVertex -> to consumed VarPostVertex + OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST); + new OrderEdge(m_graphp, m_logicVxp, postVxp, WEIGHT_POST); } } } @@ -640,7 +425,7 @@ class OrderBuildVisitor final : public VNVisitor { // We just need to add an edge to the enclosing logic vertex (the vertex for the // AstAlways). OrderVarVertex* const varVxp = getVarVertex(nodep->varScopep(), VarVertexType::STD); - new OrderComboCutEdge(m_graphp, m_logicVxp, varVxp); + new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); // Only used for ordering, so we can get rid of it here nodep->unlinkFrBack(); VL_DO_DANGLING(pushDeletep(nodep), nodep); @@ -656,15 +441,15 @@ class OrderBuildVisitor final : public VNVisitor { } //--- Logic akin to SystemVerilog Processes (AstNodeProcedure) - virtual void visit(AstInitial* nodep) override { // - iterateLogic(nodep); - } + virtual void visit(AstInitial* nodep) override { // LCOV_EXCL_START + nodep->v3fatalSrc("AstInitial should not need ordering"); + } // LCOV_EXCL_STOP + virtual void visit(AstInitialStatic* nodep) override { // LCOV_EXCL_START + nodep->v3fatalSrc("AstInitialStatic should not need ordering"); + } // LCOV_EXCL_STOP virtual void visit(AstInitialAutomatic* nodep) override { // iterateLogic(nodep); } - virtual void visit(AstInitialStatic* nodep) override { // - iterateLogic(nodep); - } virtual void visit(AstAlways* nodep) override { // iterateLogic(nodep); } @@ -681,34 +466,25 @@ class OrderBuildVisitor final : public VNVisitor { m_inPostponed = false; } virtual void visit(AstFinal* nodep) override { // LCOV_EXCL_START - nodep->v3fatalSrc("AstFinal should have been removed already"); + nodep->v3fatalSrc("AstFinal should not need ordering"); } // LCOV_EXCL_STOP //--- Logic akin go SystemVerilog continuous assignments virtual void visit(AstAssignAlias* nodep) override { // iterateLogic(nodep); } - virtual void visit(AstAssignW* nodep) override { - UASSERT_OBJ(!m_inClkAss, nodep, "Should not nest"); - m_inClkAss = isClockerAssignment(nodep); - iterateLogic(nodep); - m_inClkAss = false; - } + virtual void visit(AstAssignW* nodep) override { iterateLogic(nodep); } virtual void visit(AstAssignPre* nodep) override { - UASSERT_OBJ(!m_inClkAss && !m_inPre, nodep, "Should not nest"); - m_inClkAss = isClockerAssignment(nodep); + UASSERT_OBJ(!m_inPre, nodep, "Should not nest"); m_inPre = true; iterateLogic(nodep); m_inPre = false; - m_inClkAss = false; } virtual void visit(AstAssignPost* nodep) override { - UASSERT_OBJ(!m_inClkAss && !m_inPost, nodep, "Should not nest"); - m_inClkAss = isClockerAssignment(nodep); + UASSERT_OBJ(!m_inPost, nodep, "Should not nest"); m_inPost = true; iterateLogic(nodep); m_inPost = false; - m_inClkAss = false; } //--- Verilator concoctions @@ -737,29 +513,31 @@ class OrderBuildVisitor final : public VNVisitor { virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } // CONSTRUCTOR - OrderBuildVisitor(AstNetlist* nodep) { + OrderBuildVisitor(AstNetlist* nodep, const std::vector& coll, + const std::unordered_map& trigToSen) + : m_trigToSen{trigToSen} { + // Enable debugging (3 is default if global debug; we want acyc debugging) if (debug()) m_graphp->debug(5); - // Add edges from the InputVertex to all top level input signal VarStdVertex - for (AstVarScope* vscp = nodep->topScopep()->scopep()->varsp(); vscp; - vscp = VN_AS(vscp->nextp(), VarScope)) { - if (vscp->varp()->isNonOutput()) { - OrderVarVertex* const varVxp = getVarVertex(vscp, VarVertexType::STD); - new OrderEdge(m_graphp, m_inputsVxp, varVxp, WEIGHT_INPUT); + // Build the rest of the graph + for (const V3Sched::LogicByScope* const lbsp : coll) { + for (const auto& pair : *lbsp) { + m_scopep = pair.first; + iterate(pair.second); + m_scopep = nullptr; } } - - // Build the rest of the graph - iterate(nodep); } virtual ~OrderBuildVisitor() = default; public: // Process the netlist and return the constructed ordering graph. It's 'process' because // this visitor does change the tree (removes some nodes related to DPI export trigger). - static std::unique_ptr process(AstNetlist* nodep) { - return std::unique_ptr{OrderBuildVisitor{nodep}.m_graphp}; + static std::unique_ptr + process(AstNetlist* nodep, const std::vector& coll, + const std::unordered_map& trigToSen) { + return std::unique_ptr{OrderBuildVisitor{nodep, coll, trigToSen}.m_graphp}; } }; @@ -840,6 +618,9 @@ template class ProcessMoveBuildGraph { // 'T_MoveVertex' template parameter; ProcessMoveBuildGraph otherwise // works the same way for both cases. + // NODE STATE + // AstSenTree::user1p() -> AstSenTree: Original AstSenTree for trigger + // TYPES using VxDomPair = std::pair; using Logic2Move = std::unordered_map; @@ -860,6 +641,8 @@ public: private: // MEMBERS const V3Graph* m_graphp; // Input graph of OrderLogicVertex's etc + // Map from Trigger reference AstSenItem to the original AstSenTree + const std::unordered_map& m_trigToSen; V3Graph* m_outGraphp; // Output graph of T_MoveVertex's MoveVertexMaker* const m_vxMakerp; // Factory class for T_MoveVertex's Logic2Move m_logic2move; // Map Logic to Vertex @@ -869,10 +652,13 @@ private: public: // CONSTRUCTORS - ProcessMoveBuildGraph(const V3Graph* logicGraphp, // Input graph of OrderLogicVertex etc. - V3Graph* outGraphp, // Output graph of T_MoveVertex's - MoveVertexMaker* vxMakerp) + ProcessMoveBuildGraph( + const V3Graph* logicGraphp, // Input graph of OrderLogicVertex etc. + const std::unordered_map& trigToSen, + V3Graph* outGraphp, // Output graph of T_MoveVertex's + MoveVertexMaker* vxMakerp) : m_graphp{logicGraphp} + , m_trigToSen{trigToSen} , m_outGraphp{outGraphp} , m_vxMakerp{vxMakerp} {} virtual ~ProcessMoveBuildGraph() = default; @@ -912,8 +698,66 @@ public: } private: + // Returns the AstSenItem that originally corresponds to this AstSenTree, or nullptr if no + // original AstSenTree, or if the original AstSenTree had multiple AstSenItems. + const AstSenItem* getOrigSenItem(AstSenTree* senTreep) { + if (!senTreep->user1p()) { + // Find the original simple AstSenTree, if any + AstNode* const origp = [&]() -> AstSenItem* { + // If more than one AstSenItems, then not a simple AstSenTree + if (senTreep->sensesp()->nextp()) return nullptr; + + // Find the original AstSenTree + auto it = m_trigToSen.find(senTreep->sensesp()); + if (it == m_trigToSen.end()) return nullptr; + + // If more than one AstSenItems on the original, then not a simple AstSenTree + if (it->second->sensesp()->nextp()) return nullptr; + + // Else we found it. + return it->second->sensesp(); + }(); + + // We use the node itself as a sentinel to denote 'no original node' + senTreep->user1p(origp ? origp : senTreep); + } + + return senTreep->user1p() == senTreep ? nullptr : VN_AS(senTreep->user1p(), SenItem); + } + + bool domainsExclusive(AstSenTree* fromp, AstSenTree* top) { + // Return 'true' if we can prove that both 'from' and 'to' cannot both + // be active on the same evaluation, or false if we can't prove this. + // + // This detects the case of 'always @(posedge clk)' + // and 'always @(negedge clk)' being exclusive. + // + // Are there any other cases we need to handle? Maybe not, + // because these are not exclusive: + // always @(posedge A or posedge B) + // always @(negedge A) + // + // ... unless you know more about A and B, which sounds hard. + + const AstSenItem* const fromSenItemp = getOrigSenItem(fromp); + if (!fromSenItemp) return false; + const AstSenItem* const toSenItemp = getOrigSenItem(top); + if (!toSenItemp) return false; + + const AstNodeVarRef* const fromVarrefp = fromSenItemp->varrefp(); + if (!fromVarrefp) return false; + const AstNodeVarRef* const toVarrefp = toSenItemp->varrefp(); + if (!toVarrefp) return false; + + // We know nothing about the relationship between different clocks here, + // so only proceed if strictly the same clock. + if (fromVarrefp->varScopep() != toVarrefp->varScopep()) return false; + + return fromSenItemp->edgeType().exclusiveEdge(toSenItemp->edgeType()); + } + // Return true if moveVxp has downstream dependencies - bool iterate(T_MoveVertex* moveVxp, const V3GraphVertex* origVxp, const AstSenTree* domainp) { + bool iterate(T_MoveVertex* moveVxp, const V3GraphVertex* origVxp, AstSenTree* domainp) { bool madeDeps = false; // Search forward from given original vertex, making new edges from // moveVxp forward @@ -934,8 +778,7 @@ private: new OrderEdge(m_outGraphp, moveVxp, m_logic2move[toLVertexp], weight); madeDeps = true; } else { - // This is an OrderVarVertex or other vertex representing - // data. (Could be var, settle, or input type vertex.) + // This is an OrderVarVertex. const V3GraphVertex* nonLogicVxp = edgep->top(); const VxDomPair key(nonLogicVxp, domainp); if (!m_var2move[key]) { @@ -1008,9 +851,6 @@ public: const OrderEitherVertex* varVertexp, const AstScope* scopep, const AstSenTree* domainp) override { - // Exclude initial/settle logic from the mtasks graph. - // We'll output time-zero logic separately. - if (domainp->hasInitial() || domainp->hasSettle()) return nullptr; return new MTaskMoveVertex(m_pomGraphp, lvertexp, varVertexp, scopep, domainp); } virtual void freeVertexp(MTaskMoveVertex* freeMep) override { @@ -1057,17 +897,24 @@ public: class OrderProcess final : VNDeleter { // NODE STATE - // AstNode::user3 -> Used by loop reporting // AstNode::user4 -> Used by V3Const::constifyExpensiveEdit - const VNUser3InUse user3InUse; // STATE OrderGraph& m_graph; // The ordering graph - OrderInputsVertex& m_inputsVtx; // The singleton OrderInputsVertex + + // Map from Trigger reference AstSenItem to the original AstSenTree + const std::unordered_map& m_trigToSen; + + // This is a function provided by the invoker of the ordering that can and provide additional + // sensitivity expression that when triggered indicates the passed AstVarScope might have + // changed external to the code being ordered. + const std::function m_externalDomain; + SenTreeFinder m_finder; // Global AstSenTree manager - AstSenTree* const m_comboDomainp; // The combinational domain AstSenTree AstSenTree* const m_deleteDomainp; // Dummy AstSenTree indicating needs deletion - AstScope& m_scopetop; // The top level AstScope + const string m_tag; // Subtring to add to generated names + const bool m_slow; // Ordering slow code + std::vector m_result; // The result nodes (~statements) in their sequential order AstCFunc* m_pomNewFuncp = nullptr; // Current function being created int m_pomNewStmts = 0; // Statements in function being created @@ -1075,7 +922,6 @@ class OrderProcess final : VNDeleter { V3List m_pomWaiting; // List of nodes needing inputs to become ready friend class OrderMoveDomScope; V3List m_pomReadyDomScope; // List of ready domain/scope pairs, by loopId - std::vector m_unoptflatVars; // Vector of variables in UNOPTFLAT loop std::map, unsigned> m_funcNums; // Function ordinals // STATS @@ -1084,13 +930,7 @@ class OrderProcess final : VNDeleter { // METHODS VL_DEBUG_FUNC; // Declare debug() - void process(); - void processCircular(); - using VertexVec = std::deque; - void processInputs(); - void processInputsInIterate(OrderEitherVertex* vertexp, VertexVec& todoVec); - void processInputsOutIterate(OrderEitherVertex* vertexp, VertexVec& todoVec); - void processSensitive(); + void process(bool multiThreaded); void processDomains(); void processDomainsIterate(OrderEitherVertex* vertexp); void processEdgeReport(); @@ -1114,16 +954,11 @@ class OrderProcess final : VNDeleter { MTaskState() = default; }; void processMTasks(); - enum InitialLogicE : uint8_t { LOGIC_INITIAL, LOGIC_SETTLE }; - void processMTasksInitial(InitialLogicE logic_type); string cfuncName(AstNodeModule* modp, AstSenTree* domainp, AstScope* scopep, AstNode* forWhatp) { - string name = domainp->hasCombo() ? "_combo" - : domainp->hasInitial() ? "_initial" - : domainp->hasSettle() ? "_settle" - : domainp->isMulti() ? "_multiclk" - : "_sequent"; + string name = "_" + m_tag; + name += domainp->isMulti() ? "_comb" : "_sequent"; name = name + "__" + scopep->nameDotless(); const unsigned funcnum = m_funcNums.emplace(std::make_pair(modp, name), 0).first->second++; name = name + "__" + cvtToStr(funcnum); @@ -1133,201 +968,34 @@ class OrderProcess final : VNDeleter { return name; } - bool nodeIsInitial(const OrderLogicVertex* LVtxp) { - return LVtxp && (VN_IS(LVtxp->nodep(), Initial) || VN_IS(LVtxp->nodep(), InitialStatic)); - } - - void nodeMarkCircular(OrderVarVertex* vertexp, OrderEdge* edgep) { - // To be marked circular requires being a clock assigned in a delayed assignment, or - // having a cutable in or out edge, none of which is true for the DPI export trigger. - AstVarScope* const nodep = vertexp->varScp(); - UASSERT(nodep != v3Global.rootp()->dpiExportTriggerp(), - "DPI export trigger should not be marked circular"); - const OrderLogicVertex* fromLVtxp = nullptr; - const OrderLogicVertex* toLVtxp = nullptr; - if (edgep) { - fromLVtxp = dynamic_cast(edgep->fromp()); - toLVtxp = dynamic_cast(edgep->top()); - } - // - if (nodeIsInitial(fromLVtxp) || nodeIsInitial(toLVtxp)) { - // IEEE does not specify ordering between initial blocks, so we - // can do whatever we want. We especially do not want to - // evaluate multiple times, so do not mark the edge circular - } else { - nodep->circular(true); - ++m_statCut[vertexp->type()]; - if (edgep) ++m_statCut[edgep->type()]; - // - if (vertexp->isClock()) { - // Seems obvious; no warning yet - // nodep->v3warn(GENCLK, "Signal unoptimizable: Generated clock: - // "<prettyNameQ()); - } else if (nodep->varp()->isSigPublic()) { - nodep->v3warn(UNOPT, - "Signal unoptimizable: Feedback to public clock or circular logic: " - << nodep->prettyNameQ()); - if (!nodep->fileline()->warnIsOff(V3ErrorCode::UNOPT) - && !nodep->fileline()->lastWarnWaived()) { - nodep->fileline()->modifyWarnOff(V3ErrorCode::UNOPT, - true); // Complain just once - // Give the user an example. - const bool tempWeight = (edgep && edgep->weight() == 0); - // Else the below loop detect can't see the loop - if (tempWeight) edgep->weight(1); - // Calls OrderGraph::loopsVertexCb - m_graph.reportLoops(&OrderEdge::followComboConnected, vertexp); - if (tempWeight) edgep->weight(0); - } - } else { - // We don't use UNOPT, as there are lots of V2 places where - // it was needed, that aren't any more - // First v3warn not inside warnIsOff so we can see the suppressions with --debug - nodep->v3warn(UNOPTFLAT, - "Signal unoptimizable: Feedback to clock or circular logic: " - << nodep->prettyNameQ()); - if (!nodep->fileline()->warnIsOff(V3ErrorCode::UNOPTFLAT) - && !nodep->fileline()->lastWarnWaived()) { - nodep->fileline()->modifyWarnOff(V3ErrorCode::UNOPTFLAT, - true); // Complain just once - // Give the user an example. - const bool tempWeight = (edgep && edgep->weight() == 0); - // Else the below loop detect can't see the loop - if (tempWeight) edgep->weight(1); - // Calls OrderGraph::loopsVertexCb - m_graph.reportLoops(&OrderEdge::followComboConnected, vertexp); - if (tempWeight) edgep->weight(0); - if (v3Global.opt.reportUnoptflat()) { - // Report candidate variables for splitting - reportLoopVars(vertexp); - // Do a subgraph for the UNOPTFLAT loop - OrderGraph loopGraph; - m_graph.subtreeLoops(&OrderEdge::followComboConnected, vertexp, - &loopGraph); - loopGraph.dumpDotFilePrefixedAlways("unoptflat"); - } - } - } - } - } - - // Find all variables in an UNOPTFLAT loop - // - // Ignore vars that are 1-bit wide and don't worry about generated - // variables (PRE and POST vars, __Vdly__, __Vcellin__ and __VCellout). - // What remains are candidates for splitting to break loops. - // - // node->user3 is used to mark if we have done a particular variable. - // vertex->user is used to mark if we have seen this vertex before. - // - // @todo We could be cleverer in the future and consider just - // the width that is generated/consumed. - void reportLoopVars(OrderVarVertex* vertexp) { - m_graph.userClearVertices(); - AstNode::user3ClearTree(); - m_unoptflatVars.clear(); - reportLoopVarsIterate(vertexp, vertexp->color()); - AstNode::user3ClearTree(); - m_graph.userClearVertices(); - // May be very large vector, so only report the "most important" - // elements. Up to 10 of the widest - std::cerr << V3Error::warnMore() << "... Widest candidate vars to split:\n"; - std::stable_sort(m_unoptflatVars.begin(), m_unoptflatVars.end(), - [](OrderVarStdVertex* vsv1p, OrderVarStdVertex* vsv2p) -> bool { - return vsv1p->varScp()->varp()->width() - > vsv2p->varScp()->varp()->width(); - }); - std::unordered_set canSplitList; - int lim = m_unoptflatVars.size() < 10 ? m_unoptflatVars.size() : 10; - for (int i = 0; i < lim; i++) { - OrderVarStdVertex* const vsvertexp = m_unoptflatVars[i]; - AstVar* const varp = vsvertexp->varScp()->varp(); - const bool canSplit = V3SplitVar::canSplitVar(varp); - std::cerr << V3Error::warnMore() << " " << varp->fileline() << " " - << varp->prettyName() << std::dec << ", width " << varp->width() - << ", fanout " << vsvertexp->fanout(); - if (canSplit) { - std::cerr << ", can split_var"; - canSplitList.insert(varp); - } - std::cerr << '\n'; - } - // Up to 10 of the most fanned out - std::cerr << V3Error::warnMore() << "... Most fanned out candidate vars to split:\n"; - std::stable_sort(m_unoptflatVars.begin(), m_unoptflatVars.end(), - [](OrderVarStdVertex* vsv1p, OrderVarStdVertex* vsv2p) -> bool { - return vsv1p->fanout() > vsv2p->fanout(); - }); - lim = m_unoptflatVars.size() < 10 ? m_unoptflatVars.size() : 10; - for (int i = 0; i < lim; i++) { - OrderVarStdVertex* const vsvertexp = m_unoptflatVars[i]; - AstVar* const varp = vsvertexp->varScp()->varp(); - const bool canSplit = V3SplitVar::canSplitVar(varp); - std::cerr << V3Error::warnMore() << " " << varp->fileline() << " " - << varp->prettyName() << ", width " << std::dec << varp->width() - << ", fanout " << vsvertexp->fanout(); - if (canSplit) { - std::cerr << ", can split_var"; - canSplitList.insert(varp); - } - std::cerr << '\n'; - } - if (!canSplitList.empty()) { - std::cerr << V3Error::warnMore() - << "... Suggest add /*verilator split_var*/ to appropriate variables above." - << std::endl; - } - V3Stats::addStat("Order, SplitVar, candidates", canSplitList.size()); - m_unoptflatVars.clear(); - } - - void reportLoopVarsIterate(V3GraphVertex* vertexp, uint32_t color) { - if (vertexp->user()) return; // Already done - vertexp->user(1); - if (OrderVarStdVertex* const vsvertexp = dynamic_cast(vertexp)) { - // Only reporting on standard variable vertices - AstVar* const varp = vsvertexp->varScp()->varp(); - if (!varp->user3()) { - const string name = varp->prettyName(); - if ((varp->width() != 1) && (name.find("__Vdly") == string::npos) - && (name.find("__Vcell") == string::npos)) { - // Variable to report on and not yet done - m_unoptflatVars.push_back(vsvertexp); - } - varp->user3Inc(); - } - } - // Iterate through all the to and from vertices of the same color - for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) { - if (edgep->top()->color() == color) reportLoopVarsIterate(edgep->top(), color); - } - for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { - if (edgep->fromp()->color() == color) reportLoopVarsIterate(edgep->fromp(), color); - } - } - - // Only for member initialization in constructor - static OrderInputsVertex& findInputVertex(OrderGraph& graph) { - for (V3GraphVertex* vtxp = graph.verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) { - if (auto* const ivtxp = dynamic_cast(vtxp)) return *ivtxp; - } - VL_UNREACHABLE + // Make a domain that merges the two domains + AstSenTree* combineDomains(AstSenTree* ap, AstSenTree* bp) { + AstSenTree* const senTreep = ap->cloneTree(false); + senTreep->addSensesp(bp->sensesp()->cloneTree(true)); + V3Const::constifyExpensiveEdit(senTreep); // Remove duplicates + senTreep->multi(true); // Comment that it was made from 2 domains + AstSenTree* const resultp = m_finder.getSenTree(senTreep); + VL_DO_DANGLING(senTreep->deleteTree(), senTreep); // getSenTree clones, so delete this + return resultp; } // Only for member initialization in constructor static AstSenTree* makeDeleteDomainSenTree(FileLine* fl) { - // TODO: Using "Never" instead of "Settle" causes a test failure, it probably shouldn't ... - return new AstSenTree{fl, new AstSenItem{fl, AstSenItem::Settle{}}}; + return new AstSenTree{fl, new AstSenItem{fl, AstSenItem::Illegal{}}}; } // CONSTRUCTOR - OrderProcess(AstNetlist* netlistp, OrderGraph& graph) + OrderProcess(AstNetlist* netlistp, OrderGraph& graph, + const std::unordered_map& trigToSen, + const string& tag, bool slow, + std::function externalDomain) : m_graph{graph} - , m_inputsVtx{findInputVertex(graph)} + , m_trigToSen{trigToSen} + , m_externalDomain{externalDomain} , m_finder{netlistp} - , m_comboDomainp{m_finder.getComb()} , m_deleteDomainp{makeDeleteDomainSenTree(netlistp->fileline())} - , m_scopetop{*netlistp->topScopep()->scopep()} { + , m_tag{tag} + , m_slow{slow} { pushDeletep(m_deleteDomainp); } @@ -1343,8 +1011,14 @@ class OrderProcess final : VNDeleter { public: // Order the logic - static void main(AstNetlist* netlistp, OrderGraph& graph) { - OrderProcess{netlistp, graph}.process(); + static std::vector + main(AstNetlist* netlistp, OrderGraph& graph, + const std::unordered_map& trigToSen, + const string& tag, bool parallel, bool slow, + std::function externalDomain) { + OrderProcess visitor{netlistp, graph, trigToSen, tag, slow, externalDomain}; + visitor.process(parallel); + return std::move(visitor.m_result); } }; @@ -1370,159 +1044,6 @@ inline void OrderMoveDomScope::movedVertex(OrderProcess* opp, OrderMoveVertex* v } //###################################################################### -// OrderProcess methods - -void OrderProcess::processInputs() { - m_graph.userClearVertices(); // Vertex::user() // 1 if input recursed, 2 if marked as input, - // 3 if out-edges recursed - // Start at input vertex, process from input-to-output order - VertexVec todoVec; // List of newly-input marked vectors we need to process - todoVec.push_front(&m_inputsVtx); - m_inputsVtx.isFromInput(true); // By definition - while (!todoVec.empty()) { - OrderEitherVertex* const vertexp = todoVec.back(); - todoVec.pop_back(); - processInputsOutIterate(vertexp, todoVec); - } -} - -void OrderProcess::processInputsInIterate(OrderEitherVertex* vertexp, VertexVec& todoVec) { - // Propagate PrimaryIn through simple assignments - if (vertexp->user()) return; // Already processed - if (false && debug() >= 9) { - UINFO(9, " InIIter " << vertexp << endl); - if (OrderLogicVertex* const vvertexp = dynamic_cast(vertexp)) { - vvertexp->nodep()->dumpTree(cout, "- TT: "); - } - } - vertexp->user(1); // Processing - // First handle all inputs to this vertex, in most cases they'll be already processed earlier - // Also, determine if this vertex is an input - int inonly = 1; // 0=no, 1=maybe, 2=yes until a no - for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { - OrderEitherVertex* const frVertexp = static_cast(edgep->fromp()); - processInputsInIterate(frVertexp, todoVec); - if (frVertexp->isFromInput()) { - if (inonly == 1) inonly = 2; - } else if (dynamic_cast(frVertexp)) { - // Ignore post assignments, just for ordering - } else { - // UINFO(9, " InItStopDueTo " << frVertexp << endl); - inonly = 0; - break; - } - } - - if (inonly == 2 - && vertexp->user() < 2) { // Set it. Note may have already been set earlier, too - UINFO(9, " Input reassignment: " << vertexp << endl); - vertexp->isFromInput(true); - vertexp->user(2); // 2 means on list - // Can't work on out-edges of a node we know is an input immediately, - // as it might visit other nodes before their input state is resolved. - // So push to list and work on it later when all in-edges known resolved - todoVec.push_back(vertexp); - } - // UINFO(9, " InIdone " << vertexp << endl); -} - -void OrderProcess::processInputsOutIterate(OrderEitherVertex* vertexp, VertexVec& todoVec) { - if (vertexp->user() == 3) return; // Already out processed - // UINFO(9, " InOIter " << vertexp << endl); - // First make sure input path is fully recursed - processInputsInIterate(vertexp, todoVec); - // Propagate PrimaryIn through simple assignments - UASSERT_OBJ(vertexp->isFromInput(), vertexp, - "processInputsOutIterate only for input marked vertexes"); - vertexp->user(3); // out-edges processed - - { - // Propagate PrimaryIn through simple assignments, following target of vertex - for (V3GraphEdge* edgep = vertexp->outBeginp(); edgep; edgep = edgep->outNextp()) { - OrderEitherVertex* const toVertexp = static_cast(edgep->top()); - if (OrderVarStdVertex* const vvertexp = dynamic_cast(toVertexp)) { - processInputsInIterate(vvertexp, todoVec); - } - if (OrderLogicVertex* const vvertexp = dynamic_cast(toVertexp)) { - if (VN_IS(vvertexp->nodep(), NodeAssign)) { - processInputsInIterate(vvertexp, todoVec); - } - } - } - } -} - -//###################################################################### -// OrderVisitor - Circular detection - -void OrderProcess::processCircular() { - // Take broken edges and add circular flags - // The change detect code will use this to force changedets - for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) { - if (OrderVarStdVertex* const vvertexp = dynamic_cast(itp)) { - if (vvertexp->isClock() && !vvertexp->isFromInput()) { - // If a clock is generated internally, we need to do another - // loop through the entire evaluation. This fixes races; see - // t_clk_dpulse test. - // - // This all seems to hinge on how the clock is generated. If - // it is generated by delayed assignment, we need the loop. If - // it is combinatorial, we do not (and indeed it will break - // other tests such as t_gated_clk_1. - if (!v3Global.opt.orderClockDly()) { - UINFO(5, "Circular Clock, no-order-clock-delay " << vvertexp << endl); - nodeMarkCircular(vvertexp, nullptr); - } else if (vvertexp->isDelayed()) { - UINFO(5, "Circular Clock, delayed " << vvertexp << endl); - nodeMarkCircular(vvertexp, nullptr); - } else { - UINFO(5, "Circular Clock, not delayed " << vvertexp << endl); - } - } - // Also mark any cut edges - for (V3GraphEdge* edgep = vvertexp->outBeginp(); edgep; edgep = edgep->outNextp()) { - if (edgep->weight() == 0) { // was cut - OrderEdge* const oedgep = dynamic_cast(edgep); - UASSERT_OBJ(oedgep, vvertexp->varScp(), "Cutable edge not of proper type"); - UINFO(6, " CutCircularO: " << vvertexp->name() << endl); - nodeMarkCircular(vvertexp, oedgep); - } - } - for (V3GraphEdge* edgep = vvertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { - if (edgep->weight() == 0) { // was cut - OrderEdge* const oedgep = dynamic_cast(edgep); - UASSERT_OBJ(oedgep, vvertexp->varScp(), "Cutable edge not of proper type"); - UINFO(6, " CutCircularI: " << vvertexp->name() << endl); - nodeMarkCircular(vvertexp, oedgep); - } - } - } - } -} - -void OrderProcess::processSensitive() { - // Sc sensitives are required on all inputs that go to a combo - // block. (Not inputs that go only to clocked blocks.) - for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) { - if (OrderVarStdVertex* const vvertexp = dynamic_cast(itp)) { - if (vvertexp->varScp()->varp()->isNonOutput()) { - // UINFO(0, " scsen " << vvertexp << endl); - for (V3GraphEdge* edgep = vvertexp->outBeginp(); edgep; - edgep = edgep->outNextp()) { - if (OrderEitherVertex* const toVertexp - = dynamic_cast(edgep->top())) { - if (edgep->weight() && toVertexp->domainp()) { - // UINFO(0, " " << toVertexp->domainp() << endl); - if (toVertexp->domainp()->hasCombo()) { - vvertexp->varScp()->varp()->scSensitive(true); - } - } - } - } - } - } - } -} void OrderProcess::processDomains() { for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) { @@ -1540,75 +1061,55 @@ void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) { // else, if all inputs are from flops, it's end-of-sequential code // else, it's full combo code if (vertexp->domainp()) return; // Already processed, or sequential logic + UINFO(5, " pdi: " << vertexp << endl); - OrderVarVertex* const vvertexp = dynamic_cast(vertexp); AstSenTree* domainp = nullptr; - if (vvertexp && vvertexp->varScp()->varp()->isNonOutput()) domainp = m_comboDomainp; - if (vvertexp && vvertexp->varScp()->isCircular()) domainp = m_comboDomainp; - if (!domainp) { - for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { - OrderEitherVertex* const fromVertexp = static_cast(edgep->fromp()); - if (edgep->weight() && fromVertexp->domainMatters()) { - UINFO(9, " from d=" << cvtToHex(fromVertexp->domainp()) << " " << fromVertexp - << endl); - if (!domainp // First input to this vertex - || domainp->hasSettle() // or, we can ignore being in the settle domain - || domainp->hasInitial()) { - domainp = fromVertexp->domainp(); - } else if (domainp->hasCombo()) { - // Once in combo, keep in combo; already as severe as we can get - } else if (fromVertexp->domainp()->hasCombo()) { - // Any combo input means this vertex must remain combo - domainp = m_comboDomainp; - } else if (fromVertexp->domainp()->hasSettle() - || fromVertexp->domainp()->hasInitial()) { - // Ignore that we have a constant (initial) input - } else if (domainp != fromVertexp->domainp()) { - // Make a domain that merges the two domains - const bool ddebug = debug() >= 9; - - if (ddebug) { // LCOV_EXCL_START - - cout << endl; - UINFO(0, " conflicting domain " << fromVertexp << endl); - UINFO(0, " dorig=" << domainp << endl); - domainp->dumpTree(cout); - UINFO(0, " d2 =" << fromVertexp->domainp() << endl); - fromVertexp->domainp()->dumpTree(cout); - } // LCOV_EXCL_STOP - AstSenTree* const newtreep = domainp->cloneTree(false); - AstSenItem* newtree2p = fromVertexp->domainp()->sensesp()->cloneTree(true); - UASSERT_OBJ(newtree2p, fromVertexp->domainp(), - "No senitem found under clocked domain"); - newtreep->addSensesp(newtree2p); - newtree2p = nullptr; // Below edit may replace it - V3Const::constifyExpensiveEdit(newtreep); // Remove duplicates - newtreep->multi(true); // Comment that it was made from 2 clock domains - domainp = m_finder.getSenTree(newtreep); - if (ddebug) { // LCOV_EXCL_START - UINFO(0, " dnew =" << newtreep << endl); - newtreep->dumpTree(cout); - UINFO(0, " find =" << domainp << endl); - domainp->dumpTree(cout); - cout << endl; - } // LCOV_EXCL_STOP - VL_DO_DANGLING(newtreep->deleteTree(), newtreep); + if (OrderLogicVertex* const lvtxp = dynamic_cast(vertexp)) { + domainp = lvtxp->hybridp(); + } + for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { + OrderEitherVertex* const fromVertexp = static_cast(edgep->fromp()); + if (edgep->weight() && fromVertexp->domainMatters()) { + AstSenTree* fromDomainp = fromVertexp->domainp(); + if (OrderVarVertex* const varVtxp = dynamic_cast(fromVertexp)) { + AstVarScope* const vscp = varVtxp->varScp(); + if (AstSenTree* const externalDomainp = m_externalDomain(vscp)) { + fromDomainp = fromDomainp == m_deleteDomainp + ? externalDomainp + : combineDomains(fromDomainp, externalDomainp); } } - } // next input edgep - // Default the domain - // This is a node which has only constant inputs, or is otherwise indeterminate. - // It should have already been copied into the settle domain. Presumably it has - // inputs which we never trigger, or nothing it's sensitive to, so we can rip it out. - if (!domainp && vertexp->scopep()) domainp = m_deleteDomainp; + UINFO(9, " from d=" << cvtToHex(fromDomainp) << " " << fromVertexp << endl); + UASSERT(!fromDomainp->hasCombo(), "There should be no need for combinational domains"); + + // Irrelevant input vertex (never triggered) + if (fromDomainp == m_deleteDomainp) continue; + + // First input to this vertex + if (!domainp) domainp = fromDomainp; + + // Once in combo, keep in combo; already as severe as we can get + if (domainp->hasCombo()) break; + + // Make a domain that merges the two domains + if (domainp != fromDomainp) domainp = combineDomains(domainp, fromDomainp); + } } - // - vertexp->domainp(domainp); - if (vertexp->domainp()) { + + // Default the domain + // This is a node which has only constant inputs, or is otherwise indeterminate. + // Presumably it has inputs which we never trigger, or nothing it's sensitive to, + // so we can rip it out. + if (!domainp && vertexp->scopep()) domainp = m_deleteDomainp; + + if (domainp) { + vertexp->domainp(domainp); UINFO(5, " done d=" << cvtToHex(vertexp->domainp()) - << (vertexp->domainp()->hasCombo() ? " [COMB]" : "") - << (vertexp->domainp()->isMulti() ? " [MULT]" : "") << " " - << vertexp << endl); + << (domainp == m_deleteDomainp ? " [DEL]" + : vertexp->domainp()->hasCombo() ? " [COMB]" + : vertexp->domainp()->isMulti() ? " [MULT]" + : "") + << " " << vertexp << endl); } } @@ -1617,13 +1118,17 @@ void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) { void OrderProcess::processEdgeReport() { // Make report of all signal names and what clock edges they have - const string filename = v3Global.debugFilename("order_edges.txt"); + const string filename = v3Global.debugFilename(m_tag + "_order_edges.txt"); const std::unique_ptr logp{V3File::new_ofstream(filename)}; if (logp->fail()) v3fatal("Can't write " << filename); - // Testing emitter: V3EmitV::verilogForTree(v3Global.rootp(), *logp); std::deque report; + // Rebuild the trigger to original AstSenTree map using equality key comparison, as + // merging domains have created new AstSenTree instances which are not in the map + std::unordered_map, const AstSenTree*> trigToSen; + for (const auto& pair : m_trigToSen) trigToSen.emplace(*pair.first, pair.second); + for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) { if (OrderVarVertex* const vvertexp = dynamic_cast(itp)) { string name(vvertexp->varScp()->prettyName()); @@ -1637,8 +1142,21 @@ void OrderProcess::processEdgeReport() { std::ostringstream os; os.setf(std::ios::left); os << " " << cvtToHex(vvertexp->varScp()) << " " << std::setw(50) << name << " "; - AstSenTree* const sentreep = vvertexp->domainp(); - if (sentreep) V3EmitV::verilogForTree(sentreep, os); + AstSenTree* const senTreep = vvertexp->domainp(); + if (senTreep == m_deleteDomainp) { + os << "DELETED"; + } else { + for (AstSenItem* senItemp = senTreep->sensesp(); senItemp; + senItemp = VN_AS(senItemp->nextp(), SenItem)) { + if (senItemp != senTreep->sensesp()) os << " or "; + const auto it = trigToSen.find(*senItemp); + if (it != trigToSen.end()) { + V3EmitV::verilogForTree(it->second, os); + } else { + V3EmitV::verilogForTree(senItemp, os); + } + } + } report.push_back(os.str()); } } @@ -1663,7 +1181,7 @@ void OrderProcess::processMoveBuildGraph() { m_pomGraph.userClearVertices(); OrderMoveVertexMaker createOrderMoveVertex(&m_pomGraph, &m_pomWaiting); - ProcessMoveBuildGraph serialPMBG(&m_graph, &m_pomGraph, + ProcessMoveBuildGraph serialPMBG(&m_graph, m_trigToSen, &m_pomGraph, &createOrderMoveVertex); serialPMBG.build(); } @@ -1784,7 +1302,7 @@ void OrderProcess::processMoveOne(OrderMoveVertex* vertexp, OrderMoveDomScope* d << " s=" << cvtToHex(scopep) << " " << lvertexp << endl); AstActive* const newActivep = processMoveOneLogic(lvertexp, m_pomNewFuncp /*ref*/, m_pomNewStmts /*ref*/); - if (newActivep) m_scopetop.addActivep(newActivep); + if (newActivep) m_result.push_back(newActivep); processMoveDoneOne(vertexp); } @@ -1796,24 +1314,26 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, AstNode* nodep = lvertexp->nodep(); AstNodeModule* const modp = scopep->modp(); UASSERT(modp, "nullptr"); - if (VN_IS(nodep, SenTree)) { + if (VN_IS(nodep, Active)) { // Just ignore sensitivities, we'll deal with them when we move statements that need them } else { // Normal logic // Move the logic into a CFunc - nodep->unlinkFrBack(); + if (nodep->backp()) nodep->unlinkFrBack(); // Process procedures per statement (unless profCFuncs), so we can split CFuncs within // procedures. Everything else is handled in one go - AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure); - if (procp && !v3Global.opt.profCFuncs()) { + if (AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure)) { nodep = procp->bodysp(); pushDeletep(procp); } + // When profCFuncs, create a new function for all logic block + if (v3Global.opt.profCFuncs()) newFuncpr = nullptr; + while (nodep) { - // Make or borrow a CFunc to contain the new statements - if (v3Global.opt.profCFuncs() - || (v3Global.opt.outputSplitCFuncs() + // Split the CFunc if too large (but not when profCFuncs) + if (!v3Global.opt.profCFuncs() + && (v3Global.opt.outputSplitCFuncs() && v3Global.opt.outputSplitCFuncs() < newStmtsr)) { // Put every statement into a unique function to ease profiling or reduce function // size @@ -1824,8 +1344,8 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, newFuncpr = new AstCFunc(nodep->fileline(), name, scopep); newFuncpr->isStatic(false); newFuncpr->isLoose(true); + newFuncpr->slow(m_slow); newStmtsr = 0; - if (domainp->hasInitial() || domainp->hasSettle()) newFuncpr->slow(true); scopep->addActivep(newFuncpr); // Create top call to it AstCCall* const callp = new AstCCall(nodep->fileline(), newFuncpr); @@ -1845,14 +1365,11 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, if (nodep->backp()) nodep->unlinkFrBack(); if (domainp == m_deleteDomainp) { - UINFO(4, " Ordering deleting pre-settled " << nodep << endl); VL_DO_DANGLING(pushDeletep(nodep), nodep); } else { newFuncpr->addStmtsp(nodep); - if (v3Global.opt.outputSplitCFuncs()) { - // Add in the number of nodes we're adding - newStmtsr += nodep->nodeCount(); - } + // Add in the number of nodes we're adding + if (v3Global.opt.outputSplitCFuncs()) newStmtsr += nodep->nodeCount(); } nodep = nextp; @@ -1861,38 +1378,11 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, return activep; } -void OrderProcess::processMTasksInitial(InitialLogicE logic_type) { - // Emit initial/settle logic. Initial blocks won't be part of the - // mtask partition, aren't eligible for parallelism. - // - int initStmts = 0; - AstCFunc* initCFunc = nullptr; - const AstScope* lastScopep = nullptr; - for (V3GraphVertex* initVxp = m_graph.verticesBeginp(); initVxp; - initVxp = initVxp->verticesNextp()) { - OrderLogicVertex* const initp = dynamic_cast(initVxp); - if (!initp) continue; - if ((logic_type == LOGIC_INITIAL) && !initp->domainp()->hasInitial()) continue; - if ((logic_type == LOGIC_SETTLE) && !initp->domainp()->hasSettle()) continue; - if (initp->scopep() != lastScopep) { - // Start new cfunc, don't let the cfunc cross scopes - initCFunc = nullptr; - lastScopep = initp->scopep(); - } - AstActive* const newActivep - = processMoveOneLogic(initp, initCFunc /*ref*/, initStmts /*ref*/); - if (newActivep) m_scopetop.addActivep(newActivep); - } -} - void OrderProcess::processMTasks() { // For nondeterminism debug: V3Partition::hashGraphDebug(&m_graph, "V3Order's m_graph"); - processMTasksInitial(LOGIC_INITIAL); - processMTasksInitial(LOGIC_SETTLE); - - // We already produced a graph of every var, input, logic, and settle + // We already produced a graph of every var, input, and logic // block and all dependencies; this is 'm_graph'. // // Now, starting from m_graph, make a slightly-coarsened graph representing @@ -1900,7 +1390,8 @@ void OrderProcess::processMTasks() { // This is quite similar to the 'm_pomGraph' of the serial code gen: V3Graph logicGraph; OrderMTaskMoveVertexMaker create_mtask_vertex(&logicGraph); - ProcessMoveBuildGraph mtask_pmbg(&m_graph, &logicGraph, &create_mtask_vertex); + ProcessMoveBuildGraph mtask_pmbg(&m_graph, m_trigToSen, &logicGraph, + &create_mtask_vertex); mtask_pmbg.build(); // Needed? We do this for m_pomGraph in serial mode, so do it here too: @@ -1961,7 +1452,7 @@ void OrderProcess::processMTasks() { // of the MTask graph. FileLine* const rootFlp = v3Global.rootp()->fileline(); AstExecGraph* const execGraphp = new AstExecGraph{rootFlp, "eval"}; - m_scopetop.addActivep(execGraphp); + m_result.push_back(execGraphp); // Create CFuncs and bodies for each MTask. GraphStream emit_mtasks(&mtasks); @@ -2020,9 +1511,9 @@ void OrderProcess::processMTasks() { //###################################################################### // OrderVisitor - Top processing -void OrderProcess::process() { +void OrderProcess::process(bool multiThreaded) { // Dump data - m_graph.dumpDotFilePrefixed("orderg_pre"); + m_graph.dumpDotFilePrefixed(m_tag + "_orderg_pre"); // Break cycles. Each strongly connected subgraph (including cutable // edges) will have its own color, and corresponds to a loop in the @@ -2030,38 +1521,29 @@ void OrderProcess::process() { // edges are actually still there, just with weight 0). UINFO(2, " Acyclic & Order...\n"); m_graph.acyclic(&V3GraphEdge::followAlwaysTrue); - m_graph.dumpDotFilePrefixed("orderg_acyc"); + m_graph.dumpDotFilePrefixed(m_tag + "_orderg_acyc"); // Assign ranks so we know what to follow // Then, sort vertices and edges by that ordering m_graph.order(); - m_graph.dumpDotFilePrefixed("orderg_order"); - - // This finds everything that can be traced from an input (which by - // definition are the source clocks). After this any vertex which was - // traced has isFromInput() true. - UINFO(2, " Process Clocks...\n"); - processInputs(); // must be before processCircular - - UINFO(2, " Process Circulars...\n"); - processCircular(); // must be before processDomains + m_graph.dumpDotFilePrefixed(m_tag + "_orderg_order"); // Assign logic vertices to new domains UINFO(2, " Domains...\n"); processDomains(); - m_graph.dumpDotFilePrefixed("orderg_domain"); + m_graph.dumpDotFilePrefixed(m_tag + "_orderg_domain"); if (debug() && v3Global.opt.dumpTree()) processEdgeReport(); - if (!v3Global.opt.mtasks()) { + if (!multiThreaded) { UINFO(2, " Construct Move Graph...\n"); processMoveBuildGraph(); if (debug() >= 4) { - m_pomGraph.dumpDotFilePrefixed( - "ordermv_start"); // Different prefix (ordermv) as it's not the same graph + // Different prefix (ordermv) as it's not the same graph + m_pomGraph.dumpDotFilePrefixed(m_tag + "_ordermv_start"); } m_pomGraph.removeRedundantEdges(&V3GraphEdge::followAlwaysTrue); - if (debug() >= 4) m_pomGraph.dumpDotFilePrefixed("ordermv_simpl"); + if (debug() >= 4) m_pomGraph.dumpDotFilePrefixed(m_tag + "_ordermv_simpl"); UINFO(2, " Move...\n"); processMove(); @@ -2070,34 +1552,46 @@ void OrderProcess::process() { processMTasks(); } - // Any SC inputs feeding a combo domain must be marked, so we can make them sc_sensitive - UINFO(2, " Sensitive...\n"); - processSensitive(); // must be after processDomains - // Dump data - m_graph.dumpDotFilePrefixed("orderg_done"); - if (false && debug()) { - const string dfilename - = v3Global.opt.makeDir() + "/" + v3Global.opt.prefix() + "_INT_order"; - const std::unique_ptr logp{V3File::new_ofstream(dfilename)}; - if (logp->fail()) v3fatal("Can't write " << dfilename); - m_graph.dump(*logp); - } + m_graph.dumpDotFilePrefixed(m_tag + "_orderg_done"); } //###################################################################### -// Order class functions -void V3Order::orderAll(AstNetlist* netlistp) { - UINFO(2, __FUNCTION__ << ": " << endl); - // Propagate 'clocker' attribute through logic - OrderClkMarkVisitor::process(netlistp); - // Build ordering graph - std::unique_ptr orderGraph = OrderBuildVisitor::process(netlistp); - // Order the netlist - OrderProcess::main(netlistp, *orderGraph.get()); - // Reset debug level - orderGraph.get()->debug(V3Error::debugDefault()); - // Dump tree - V3Global::dumpCheckGlobalTree("order", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); +namespace V3Order { + +AstCFunc* order(AstNetlist* netlistp, // + const std::vector& logic, // + const std::unordered_map& trigToSen, + const string& tag, // + bool parallel, // + bool slow, // + std::function externalDomain) { + // Order the code + const std::unique_ptr graph + = OrderBuildVisitor::process(netlistp, logic, trigToSen); + const auto& nodeps = OrderProcess::main(netlistp, *graph.get(), trigToSen, tag, parallel, slow, + externalDomain); + + // Create the result function + AstScope* const scopeTopp = netlistp->topScopep()->scopep(); + AstCFunc* const funcp = new AstCFunc{netlistp->fileline(), "_eval_" + tag, scopeTopp, ""}; + funcp->dontCombine(true); + funcp->isStatic(false); + funcp->isLoose(true); + funcp->slow(slow); + funcp->isConst(false); + funcp->declPrivate(true); + scopeTopp->addActivep(funcp); + + // Add ordered statements to the result function + for (AstNode* const nodep : nodeps) funcp->addStmtsp(nodep); + + // Dispose of the remnants of the inputs + for (auto* const lbsp : logic) lbsp->deleteActives(); + + // Done + return funcp; } + +} // namespace V3Order diff --git a/src/V3Order.h b/src/V3Order.h index 13b82ad69..12ef3d2eb 100644 --- a/src/V3Order.h +++ b/src/V3Order.h @@ -20,13 +20,32 @@ #include "config_build.h" #include "verilatedos.h" +#include +#include +#include + +class AstCFunc; class AstNetlist; +class AstSenItem; +class AstSenTree; +class AstVarScope; + +namespace V3Sched { +struct LogicByScope; +}; //============================================================================ -class V3Order final { -public: - static void orderAll(AstNetlist* nodep); -}; +namespace V3Order { + +AstCFunc* order(AstNetlist* netlistp, // + const std::vector& logic, // + const std::unordered_map& trigToSen, + const string& tag, // + bool parallel, // + bool slow, // + std::function externalDomain); + +}; // namespace V3Order #endif // Guard diff --git a/src/V3OrderGraph.h b/src/V3OrderGraph.h index 2fb8c33ab..bf8da4954 100644 --- a/src/V3OrderGraph.h +++ b/src/V3OrderGraph.h @@ -19,7 +19,6 @@ // OrderMoveVertex // MTaskMoveVertex // OrderEitherVertex -// OrderInputsVertex // OrderLogicVertex // OrderVarVertex // OrderVarStdVertex @@ -29,7 +28,6 @@ // // V3GraphEdge // OrderEdge -// OrderComboCutEdge // OrderPostCutEdge // OrderPreCutEdge //************************************************************************* @@ -53,7 +51,6 @@ class OrderMoveDomScope; //###################################################################### enum OrderWeights : uint8_t { - WEIGHT_INPUT = 1, // Low weight just so dot graph looks nice WEIGHT_COMBO = 1, // Breakable combo logic WEIGHT_POST = 2, // Post-delayed used var WEIGHT_PRE = 3, // Breakable pre-delayed used var @@ -64,13 +61,11 @@ enum OrderWeights : uint8_t { struct OrderVEdgeType { enum en : uint8_t { VERTEX_UNKNOWN = 0, - VERTEX_INPUTS, VERTEX_LOGIC, VERTEX_VARSTD, VERTEX_VARPRE, VERTEX_VARPOST, VERTEX_VARPORD, - VERTEX_VARSETTLE, VERTEX_MOVE, EDGE_STD, EDGE_COMBOCUT, @@ -80,10 +75,9 @@ struct OrderVEdgeType { }; const char* ascii() const { static const char* const names[] - = {"%E-vedge", "VERTEX_INPUTS", "VERTEX_LOGIC", "VERTEX_VARSTD", - "VERTEX_VARPRE", "VERTEX_VARPOST", "VERTEX_VARPORD", "VERTEX_VARSETTLE", - "VERTEX_MOVE", "EDGE_STD", "EDGE_COMBOCUT", "EDGE_PRECUT", - "EDGE_POSTCUT", "_ENUM_END"}; + = {"%E-vedge", "VERTEX_LOGIC", "VERTEX_VARSTD", "VERTEX_VARPRE", + "VERTEX_VARPOST", "VERTEX_VARPORD", "VERTEX_MOVE", "EDGE_STD", + "EDGE_COMBOCUT", "EDGE_PRECUT", "EDGE_POSTCUT", "_ENUM_END"}; return names[m_e]; } enum en m_e; @@ -123,13 +117,11 @@ public: class OrderEitherVertex VL_NOT_FINAL : public V3GraphVertex { AstScope* const m_scopep; // Scope the vertex is in AstSenTree* m_domainp; // Clock domain (nullptr = to be computed as we iterate) - bool m_isFromInput = false; // From input, or derived therefrom (conservatively false) protected: OrderEitherVertex(V3Graph* graphp, const OrderEitherVertex& old) : V3GraphVertex{graphp, old} , m_scopep{old.m_scopep} - , m_domainp{old.m_domainp} - , m_isFromInput{old.m_isFromInput} {} + , m_domainp{old.m_domainp} {} public: OrderEitherVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp) @@ -146,43 +138,26 @@ public: void domainp(AstSenTree* domainp) { m_domainp = domainp; } AstScope* scopep() const { return m_scopep; } AstSenTree* domainp() const { return m_domainp; } - void isFromInput(bool flag) { m_isFromInput = flag; } - bool isFromInput() const { return m_isFromInput; } -}; - -class OrderInputsVertex final : public OrderEitherVertex { - OrderInputsVertex(V3Graph* graphp, const OrderInputsVertex& old) - : OrderEitherVertex{graphp, old} {} - -public: - OrderInputsVertex(V3Graph* graphp, AstSenTree* domainp) - : OrderEitherVertex{graphp, nullptr, domainp} { - isFromInput(true); // By definition - } - virtual ~OrderInputsVertex() override = default; - virtual OrderInputsVertex* clone(V3Graph* graphp) const override { - return new OrderInputsVertex(graphp, *this); - } - virtual OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_INPUTS; } - virtual string name() const override { return "*INPUTS*"; } - virtual string dotColor() const override { return "green"; } - virtual string dotName() const override { return ""; } - virtual string dotShape() const override { return "invhouse"; } - virtual bool domainMatters() override { return false; } }; class OrderLogicVertex final : public OrderEitherVertex { AstNode* const m_nodep; + AstSenTree* const m_hybridp; protected: OrderLogicVertex(V3Graph* graphp, const OrderLogicVertex& old) : OrderEitherVertex{graphp, old} - , m_nodep{old.m_nodep} {} + , m_nodep{old.m_nodep} + , m_hybridp{old.m_hybridp} {} public: - OrderLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp, AstNode* nodep) + OrderLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp, AstSenTree* hybridp, + AstNode* nodep) : OrderEitherVertex{graphp, scopep, domainp} - , m_nodep{nodep} {} + , m_nodep{nodep} + , m_hybridp{hybridp} { + UASSERT_OBJ(!(domainp && hybridp), nodep, "Can't have bot domainp and hybridp set"); + } virtual ~OrderLogicVertex() override = default; virtual OrderLogicVertex* clone(V3Graph* graphp) const override { return new OrderLogicVertex(graphp, *this); @@ -194,6 +169,7 @@ public: return (cvtToHex(m_nodep) + "\\n " + cvtToStr(nodep()->typeName())); } AstNode* nodep() const { return m_nodep; } + AstSenTree* hybridp() const { return m_hybridp; } virtual string dotShape() const override { return VN_IS(m_nodep, Active) ? "doubleoctagon" : "rect"; } @@ -201,14 +177,11 @@ public: class OrderVarVertex VL_NOT_FINAL : public OrderEitherVertex { AstVarScope* const m_varScp; - bool m_isClock = false; // Used as clock - bool m_isDelayed = false; // Set in a delayed assignment + protected: OrderVarVertex(V3Graph* graphp, const OrderVarVertex& old) : OrderEitherVertex{graphp, old} - , m_varScp{old.m_varScp} - , m_isClock{old.m_isClock} - , m_isDelayed{old.m_isDelayed} {} + , m_varScp{old.m_varScp} {} public: OrderVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp) @@ -220,10 +193,6 @@ public: virtual FileLine* fileline() const override { return varScp()->fileline(); } // ACCESSORS AstVarScope* varScp() const { return m_varScp; } - void isClock(bool flag) { m_isClock = flag; } - bool isClock() const { return m_isClock; } - void isDelayed(bool flag) { m_isDelayed = flag; } - bool isDelayed() const { return m_isDelayed; } virtual string dotShape() const override { return "ellipse"; } }; @@ -445,35 +414,6 @@ public: V3GraphVertex* top) const override { return new OrderEdge(graphp, fromp, top, *this); } - // When ordering combo blocks with stronglyConnected, follow edges not - // involving pre/pos variables - virtual bool followComboConnected() const { return true; } - static bool followComboConnected(const V3GraphEdge* edgep) { - const OrderEdge* const oedgep = dynamic_cast(edgep); - if (!oedgep) v3fatalSrc("Following edge of non-OrderEdge type"); - return (oedgep->followComboConnected()); - } -}; - -class OrderComboCutEdge final : public OrderEdge { - // Edge created from output of combo logic - // Breakable if the output var is also a input, - // in which case we'll need a change detect loop around this var. - OrderComboCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, - const OrderComboCutEdge& old) - : OrderEdge{graphp, fromp, top, old} {} - -public: - OrderComboCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) - : OrderEdge{graphp, fromp, top, WEIGHT_COMBO, CUTABLE} {} - virtual OrderVEdgeType type() const override { return OrderVEdgeType::EDGE_COMBOCUT; } - virtual ~OrderComboCutEdge() override = default; - virtual OrderComboCutEdge* clone(V3Graph* graphp, V3GraphVertex* fromp, - V3GraphVertex* top) const override { - return new OrderComboCutEdge(graphp, fromp, top, *this); - } - virtual string dotColor() const override { return "yellowGreen"; } - virtual bool followComboConnected() const override { return true; } }; class OrderPostCutEdge final : public OrderEdge { @@ -494,7 +434,6 @@ public: return new OrderPostCutEdge(graphp, fromp, top, *this); } virtual string dotColor() const override { return "PaleGreen"; } - virtual bool followComboConnected() const override { return false; } }; class OrderPreCutEdge final : public OrderEdge { @@ -515,7 +454,6 @@ public: } virtual ~OrderPreCutEdge() override = default; virtual string dotColor() const override { return "khaki"; } - virtual bool followComboConnected() const override { return false; } }; #endif // Guard diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index a4177c214..793e971ff 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -1841,7 +1841,6 @@ private: for (V3GraphEdge* edgep = (*ovvIt)->inBeginp(); edgep; edgep = edgep->inNextp()) { const OrderLogicVertex* const logicp = dynamic_cast(edgep->fromp()); if (!logicp) continue; - if (logicp->domainp()->hasInitial() || logicp->domainp()->hasSettle()) continue; LogicMTask* const writerMtaskp = m_olv2mtask.at(logicp); (*tasksByRankp)[writerMtaskp->rank()].insert(writerMtaskp); } @@ -1849,7 +1848,6 @@ private: for (V3GraphEdge* edgep = (*ovvIt)->outBeginp(); edgep; edgep = edgep->outNextp()) { const OrderLogicVertex* const logicp = dynamic_cast(edgep->fromp()); if (!logicp) continue; - if (logicp->domainp()->hasInitial() || logicp->domainp()->hasSettle()) continue; LogicMTask* const readerMtaskp = m_olv2mtask.at(logicp); (*tasksByRankp)[readerMtaskp->rank()].insert(readerMtaskp); } @@ -2563,6 +2561,11 @@ void V3Partition::go(V3Graph* mtasksp) { // (and the logic nodes therein.) uint32_t totalGraphCost = 0; { + // Artificial single entry point vertex in the MTask graph to allow sibling merges. + // This is required as otherwise disjoint sub-graphs could not be merged, but the + // coarsening algorithm assumes that the graph is connected. + LogicMTask* const entryMTask = new LogicMTask{mtasksp, nullptr}; + // The V3InstrCount within LogicMTask will set user5 on each AST // node, to assert that we never count any node twice. const VNUser5InUse inUser5; @@ -2578,8 +2581,21 @@ void V3Partition::go(V3Graph* mtasksp) { totalGraphCost += mtaskp->cost(); } + // Artificial single exit point vertex in the MTask graph to allow sibling merges. + // this enables merging MTasks with no downstream dependents if that is the ideal merge. + LogicMTask* const exitMTask = new LogicMTask{mtasksp, nullptr}; + // Create the mtask->mtask dep edges based on vertex deps setupMTaskDeps(mtasksp, &vx2mtask); + + // Add the entry/exit edges + for (V3GraphVertex* vtxp = mtasksp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) { + if (vtxp == entryMTask) continue; + if (vtxp == exitMTask) continue; + LogicMTask* const lmtp = static_cast(vtxp); + if (vtxp->inEmpty()) new MTaskEdge{mtasksp, entryMTask, lmtp, 1}; + if (vtxp->outEmpty()) new MTaskEdge{mtasksp, lmtp, exitMTask, 1}; + } } V3Partition::debugMTaskGraphStats(mtasksp, "initial"); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp new file mode 100644 index 000000000..17c44ac01 --- /dev/null +++ b/src/V3Sched.cpp @@ -0,0 +1,1047 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Code scheduling +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// V3Sched::schedule is the top level entry-point to the scheduling algorithm +// at a high level, the process is: +// +// - Gather and classify all logic in the design based on what triggers its execution +// - Schedule static, initial and final logic classes in source order +// - Break combinational cycles by introducing hybrid logic +// - Create 'settle' region that restores the combinational invariant +// - Partition the clocked and combinational (including hybrid) logic into pre/act/nba. +// All clocks (signals referenced in an AstSenTree) generated via a blocking assignment +// (including combinationally generated signals) are computed within the act region. +// - Replicate combinational logic +// - Create input combinational logic loop +// - Create the pre/act/nba triggers +// - Create the 'act' region evaluation function +// - Create the 'nba' region evaluation function +// - Bolt it all together to create the '_eval' function +// +// Details of the algorithm are described in the internals documentation docs/internals.rst +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3EmitCBase.h" +#include "V3EmitV.h" +#include "V3Order.h" +#include "V3Sched.h" +#include "V3Stats.h" +#include "V3UniqueNames.h" + +#include "unordered_map" +#include "unordered_set" + +namespace V3Sched { + +namespace { + +//============================================================================ +// Utility functions + +AstCFunc* makeSubFunction(AstNetlist* netlistp, const string& name, bool slow) { + AstScope* const scopeTopp = netlistp->topScopep()->scopep(); + AstCFunc* const funcp = new AstCFunc{netlistp->fileline(), name, scopeTopp, ""}; + funcp->dontCombine(true); + funcp->isStatic(false); + funcp->isLoose(true); + funcp->slow(slow); + funcp->isConst(false); + funcp->declPrivate(true); + scopeTopp->addActivep(funcp); + return funcp; +} + +AstCFunc* makeTopFunction(AstNetlist* netlistp, const string& name, bool slow) { + AstCFunc* const funcp = makeSubFunction(netlistp, name, slow); + funcp->entryPoint(true); + return funcp; +} + +std::vector getSenTreesUsedBy(std::vector lbsps) { + const VNUser1InUse user1InUse; + std::vector result; + for (const LogicByScope* const lbsp : lbsps) { + for (const auto& pair : *lbsp) { + AstActive* const activep = pair.second; + AstSenTree* const senTreep = activep->sensesp(); + if (senTreep->user1SetOnce()) continue; + if (senTreep->hasClocked() || senTreep->hasHybrid()) result.push_back(senTreep); + } + } + return result; +} + +AstAssign* setVar(AstVarScope* vscp, uint32_t val) { + FileLine* const flp = vscp->fileline(); + AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::WRITE}; + AstConst* const zerop = new AstConst{flp, AstConst::DtypedValue{}, vscp->dtypep(), val}; + return new AstAssign{flp, refp, zerop}; +}; + +void remapSensitivities(LogicByScope& lbs, + std::unordered_map senTreeMap) { + for (const auto& pair : lbs) { + AstActive* const activep = pair.second; + AstSenTree* const senTreep = activep->sensesp(); + if (senTreep->hasCombo()) continue; + activep->sensesp(senTreeMap.at(senTreep)); + } +} + +void invertAndMergeSenTreeMap(std::unordered_map& result, + std::unordered_map senTreeMap) { + for (const auto& pair : senTreeMap) { + UASSERT_OBJ(!pair.second->sensesp()->nextp(), pair.second, "Should be single AstSenIem"); + result.emplace(pair.second->sensesp(), pair.first); + } +} + +//============================================================================ +// Split large function according to --output-split-cfuncs + +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++), + ofuncp->scopep()}; + funcp->dontCombine(true); + funcp->isStatic(false); + funcp->isLoose(true); + funcp->slow(ofuncp->slow()); + ofuncp->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); +} + +//============================================================================ +// Collect and classify all logic in the design + +LogicClasses gatherLogicClasses(AstNetlist* netlistp) { + LogicClasses result; + + netlistp->foreach([&](AstScope* scopep) { + std::vector empty; + + scopep->foreach([&](AstActive* activep) { + AstSenTree* const senTreep = activep->sensesp(); + if (!activep->stmtsp()) { + // Some AstActives might be empty due to previous optimizations + empty.push_back(activep); + } else if (senTreep->hasStatic()) { + UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep, + "static initializer with additional sensitivities"); + result.m_static.emplace_back(scopep, activep); + } else if (senTreep->hasInitial()) { + UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep, + "'initial' logic with additional sensitivities"); + result.m_initial.emplace_back(scopep, activep); + } else if (senTreep->hasFinal()) { + UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep, + "'final' logic with additional sensitivities"); + result.m_final.emplace_back(scopep, activep); + } else if (senTreep->hasCombo()) { + UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep, + "combinational logic with additional sensitivities"); + result.m_comb.emplace_back(scopep, activep); + } else { + UASSERT_OBJ(senTreep->hasClocked(), activep, "What else could it be?"); + result.m_clocked.emplace_back(scopep, activep); + } + }); + + for (AstActive* const activep : empty) activep->unlinkFrBack()->deleteTree(); + }); + + return result; +} + +//============================================================================ +// Simple ordering in source order + +void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) { + const VNUser1InUse user1InUse; // AstScope -> AstCFunc: the sub-function for the scope + for (const auto& pair : lbs) { + AstScope* const scopep = pair.first; + AstActive* const activep = pair.second; + if (!scopep->user1p()) { + // Create a sub-function per scope so we can V3Combine them later + const string subName{funcp->name() + "__" + scopep->nameDotless()}; + AstCFunc* const subFuncp = new AstCFunc{scopep->fileline(), subName, scopep}; + subFuncp->isLoose(true); + subFuncp->isConst(false); + subFuncp->declPrivate(true); + subFuncp->slow(funcp->slow()); + scopep->addActivep(subFuncp); + scopep->user1p(subFuncp); + // Call it from the top function + funcp->addStmtsp(new AstCCall{scopep->fileline(), subFuncp}); + } + AstCFunc* const subFuncp = VN_AS(scopep->user1p(), CFunc); + // Add statements to sub-function + for (AstNode *logicp = activep->stmtsp(), *nextp; logicp; logicp = nextp) { + nextp = logicp->nextp(); + if (AstNodeProcedure* const procp = VN_CAST(logicp, NodeProcedure)) { + if (AstNode* const bodyp = procp->bodysp()) { + bodyp->unlinkFrBackWithNext(); + subFuncp->addStmtsp(bodyp); + } + } else { + logicp->unlinkFrBack(); + subFuncp->addStmtsp(logicp); + } + } + if (activep->backp()) activep->unlinkFrBack(); + VL_DO_DANGLING(activep->deleteTree(), activep); + } +} + +//============================================================================ +// Create simply ordered functions + +void createStatic(AstNetlist* netlistp, const LogicClasses& logicClasses) { + AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_static", /* slow: */ true); + orderSequentially(funcp, logicClasses.m_static); + splitCheck(funcp); +} + +AstCFunc* createInitial(AstNetlist* netlistp, const LogicClasses& logicClasses) { + AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_initial", /* slow: */ true); + orderSequentially(funcp, logicClasses.m_initial); + return funcp; // Not splitting yet as it is not final +} + +void createFinal(AstNetlist* netlistp, const LogicClasses& logicClasses) { + AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_final", /* slow: */ true); + orderSequentially(funcp, logicClasses.m_final); + splitCheck(funcp); +} + +//============================================================================ +// SenExprBuilder constructs the expressions used to compute if an +// AstSenTree have triggered + +class SenExprBuilder final { + // STATE + AstCFunc* const m_initp; // The initialization function + AstScope* const m_scopeTopp; // Top level scope + AstVarScope* const m_dpiExportTriggerp; // The DPI export trigger variable + + std::vector m_updates; // Update assignments + + std::unordered_map, AstVarScope*> m_prev; // The 'previous value' signals + std::unordered_set> m_hasUpdate; // Whether the given sen expression already + // has an update statement in m_updates + + V3UniqueNames m_uniqueNames{"__Vtrigprev__expression"}; // For generating unique signal names + + // METHODS + AstVarScope* getPrev(AstNode* currp) { + FileLine* const flp = currp->fileline(); + const auto rdCurr = [=]() { return currp->cloneTree(false); }; + + // Create the 'previous value' variable + auto it = m_prev.find(*currp); + if (it == m_prev.end()) { + // For readability, use the scoped signal name if the trigger is a simple AstVarRef + string name; + if (AstVarRef* const refp = VN_CAST(currp, VarRef)) { + AstVarScope* vscp = refp->varScopep(); + name = "__Vtrigrprev__" + vscp->scopep()->nameDotless() + "__" + + vscp->varp()->name(); + } else { + name = m_uniqueNames.get(currp); + } + + AstVarScope* const prevp = m_scopeTopp->createTemp(name, currp->dtypep()); + it = m_prev.emplace(*currp, prevp).first; + + // Add the initializer init + AstNode* const initp = rdCurr(); + m_initp->addStmtsp( + new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE}, initp}); + } + + AstVarScope* const prevp = it->second; + + // Add update if it does not exist yet in this round + if (m_hasUpdate.emplace(*currp).second) { + m_updates.push_back( + new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE}, rdCurr()}); + } + + return prevp; + } + + std::pair createTerm(AstSenItem* senItemp) { + FileLine* const flp = senItemp->fileline(); + AstNode* const senp = senItemp->sensp(); + + const auto currp = [=]() { return senp->cloneTree(false); }; + const auto prevp = [=]() { return new AstVarRef{flp, getPrev(senp), VAccess::READ}; }; + const auto lsb = [=](AstNodeMath* opp) { return new AstSel{flp, opp, 0, 1}; }; + + // All event signals should be 1-bit at this point + switch (senItemp->edgeType()) { + case VEdgeType::ET_ILLEGAL: + return {nullptr, false}; // We already warn for this in V3LinkResolve + case VEdgeType::ET_CHANGED: + case VEdgeType::ET_HYBRID: // + return {new AstNeq(flp, currp(), prevp()), true}; + case VEdgeType::ET_BOTHEDGE: // + return {lsb(new AstXor{flp, currp(), prevp()}), false}; + case VEdgeType::ET_POSEDGE: // + return {lsb(new AstAnd{flp, currp(), new AstNot{flp, prevp()}}), false}; + case VEdgeType::ET_NEGEDGE: // + return {lsb(new AstAnd{flp, new AstNot{flp, currp()}, prevp()}), false}; + case VEdgeType::ET_EVENT: { + UASSERT_OBJ(v3Global.hasEvents(), senItemp, "Inconsistent"); + { + // If the event is fired, set up the clearing process + AstCMethodHard* const callp = new AstCMethodHard{flp, currp(), "isFired"}; + callp->dtypeSetBit(); + AstIf* const ifp = new AstIf{flp, callp}; + m_updates.push_back(ifp); + + // Clear 'fired' state when done + AstCMethodHard* const clearp = new AstCMethodHard{flp, currp(), "clearFired"}; + ifp->addIfsp(clearp); + clearp->dtypeSetVoid(); + clearp->statement(true); + + // Enqueue for clearing 'triggered' state on next eval + AstTextBlock* const blockp = new AstTextBlock{flp}; + ifp->addIfsp(blockp); + const auto add = [&](const string& text) { blockp->addText(flp, text, true); }; + add("vlSymsp->enqueueTriggeredEventForClearing("); + blockp->addNodep(currp()); + add(");\n"); + } + + // Get 'fired' state + AstCMethodHard* const callp = new AstCMethodHard{flp, currp(), "isFired"}; + callp->dtypeSetBit(); + return {callp, false}; + } + case VEdgeType::ET_DPIEXPORT: { + // If the DPI export trigger is checked, always clear it after trigger computation + UASSERT_OBJ(m_dpiExportTriggerp, senItemp, "ET_DPIEXPORT without trigger variable"); + AstVarRef* const refp = new AstVarRef{flp, m_dpiExportTriggerp, VAccess::WRITE}; + m_updates.push_back(new AstAssign{flp, refp, new AstConst{flp, AstConst::BitFalse{}}}); + return {currp(), false}; + } + default: // LCOV_EXCL_START + senItemp->v3fatalSrc("Unknown edge type"); + return {nullptr, false}; + } // LCOV_EXCL_STOP + } + +public: + // Returns the expression computing the trigger, and a bool indicating that + // this trigger should be fired on the first evaluation (at initialization) + std::pair build(const AstSenTree* senTreep) { + FileLine* const flp = senTreep->fileline(); + AstNode* resultp = nullptr; + bool firedAtInitialization = false; + for (AstSenItem* senItemp = senTreep->sensesp(); senItemp; + senItemp = VN_AS(senItemp->nextp(), SenItem)) { + const auto& pair = createTerm(senItemp); + if (AstNode* const termp = pair.first) { + resultp = resultp ? new AstOr{flp, resultp, termp} : termp; + firedAtInitialization |= pair.second; + } + } + return {resultp, firedAtInitialization}; + } + + std::vector getAndClearUpdates() { + m_hasUpdate.clear(); + return std::move(m_updates); + } + + // CONSTRUCTOR + SenExprBuilder(AstNetlist* netlistp, AstCFunc* initp) + : m_initp{initp} + , m_scopeTopp{netlistp->topScopep()->scopep()} + , m_dpiExportTriggerp{netlistp->dpiExportTriggerp()} {} +}; + +//============================================================================ +// A TriggerKit holds all the components related to a TRIGGERVEC variable + +struct TriggerKit { + // The TRIGGERVEC AstVarScope representing these trigger flags + AstVarScope* const m_vscp; + // The AstCFunc that computes the current active triggers + AstCFunc* const m_funcp; + // The AstCFunc that dumps the current active triggers + AstCFunc* const m_dumpp; + // The map from input sensitivity list to trigger sensitivity list + const std::unordered_map m_map; + + VL_UNCOPYABLE(TriggerKit); + + // Create an AstSenTree that is sensitive to the given trigger index. Must not exist yet! + AstSenTree* createTriggerSenTree(AstNetlist* netlistp, uint32_t index) const { + AstTopScope* const topScopep = netlistp->topScopep(); + FileLine* const flp = topScopep->fileline(); + AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::READ}; + AstCMethodHard* const callp + = new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, index}}; + callp->dtypeSetBit(); + callp->pure(true); + AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, callp}; + AstSenTree* const resultp = new AstSenTree{flp, senItemp}; + topScopep->addSenTreep(resultp); + return resultp; + } + + // Utility that assigns the given index trigger to fire when the given variable is zero + void addFirstIterationTriggerAssignment(AstVarScope* counterp, uint32_t index) const { + FileLine* const flp = counterp->fileline(); + AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE}; + AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, 0}}; + callp->dtypeSetBit(); + callp->pure(true); + m_funcp->stmtsp()->addHereThisAsNext(new AstAssign{ + flp, callp, + new AstEq{flp, new AstVarRef{flp, counterp, VAccess::READ}, new AstConst{flp, 0}}}); + } +}; + +//============================================================================ +// Create a TRIGGERVEC and the related TriggerKit for the given AstSenTree vector + +const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBuilder, + std::vector senTreeps, const string& name, + unsigned extra, bool slow = false) { + AstTopScope* const topScopep = netlistp->topScopep(); + AstScope* const scopeTopp = topScopep->scopep(); + FileLine* const flp = scopeTopp->fileline(); + + std::unordered_map map; + + const uint32_t nTriggers = senTreeps.size() + extra; + + // Create the TRIGGERVEC variable + AstBasicDType* const tDtypep = new AstBasicDType(flp, VBasicDTypeKwd::TRIGGERVEC, + VSigning::UNSIGNED, nTriggers, nTriggers); + netlistp->typeTablep()->addTypesp(tDtypep); + AstVarScope* const vscp = scopeTopp->createTemp("__V" + name + "Triggered", tDtypep); + + // Create the trigger computation function + AstCFunc* const funcp = makeSubFunction(netlistp, "_eval_triggers__" + name, slow); + + // Create the trigger dump function (for debugging) + AstCFunc* const dumpp = makeSubFunction(netlistp, "_dump_triggers__" + name, slow); + dumpp->ifdef("VL_DEBUG"); + + // Add a print to the dumping function if there are no triggers pending + { + AstCMethodHard* const callp + = new AstCMethodHard{flp, new AstVarRef{flp, vscp, VAccess::READ}, "any"}; + callp->dtypeSetBit(); + AstIf* const ifp = new AstIf{flp, callp}; + dumpp->addStmtsp(ifp); + ifp->addElsesp( + new AstText{flp, "VL_DBG_MSGF(\" No triggers active\\n\");\n", true}); + } + + // Create a reference to a trigger flag + const auto getTrigRef = [&](uint32_t index, VAccess access) { + AstVarRef* const vrefp = new AstVarRef{flp, vscp, access}; + AstConst* const idxp = new AstConst{flp, index}; + AstCMethodHard* callp = new AstCMethodHard{flp, vrefp, "at", idxp}; + callp->dtypeSetBit(); + callp->pure(true); + return callp; + }; + + // Add a debug dumping statement for this trigger + const auto addDebug = [&](uint32_t index, const string& text = "") { + std::stringstream ss; + ss << "VL_DBG_MSGF(\" '" << name << "' region trigger index " << cvtToStr(index) + << " is active"; + if (!text.empty()) ss << ": " << text; + ss << "\\n\");\n"; + const string message{ss.str()}; + + AstIf* const ifp = new AstIf{flp, getTrigRef(index, VAccess::READ)}; + dumpp->addStmtsp(ifp); + ifp->addIfsp(new AstText{flp, message, true}); + }; + + // Add a print for each of the extra triggers + for (unsigned i = 0; i < extra; ++i) addDebug(i); + + // Add trigger computation + uint32_t triggerNumber = extra; + AstNode* initialTrigsp = nullptr; + for (const AstSenTree* const senTreep : senTreeps) { + UASSERT_OBJ(senTreep->hasClocked() || senTreep->hasHybrid(), senTreep, + "Cannot create trigger expression for non-clocked sensitivity"); + + // Create the trigger AstSenTrees and associate it with the original AstSenTree + AstCMethodHard* const senp = getTrigRef(triggerNumber, VAccess::READ); + AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, senp}; + AstSenTree* const trigpSenp = new AstSenTree{flp, senItemp}; + topScopep->addSenTreep(trigpSenp); + map[senTreep] = trigpSenp; + + // Add the trigger computation + const auto& pair = senExprBuilder.build(senTreep); + funcp->addStmtsp( + new AstAssign{flp, getTrigRef(triggerNumber, VAccess::WRITE), pair.first}); + + // Add initialization time trigger + if (pair.second || v3Global.opt.xInitialEdge()) { + AstNode* const assignp = new AstAssign{flp, getTrigRef(triggerNumber, VAccess::WRITE), + new AstConst{flp, 1}}; + initialTrigsp = AstNode::addNext(initialTrigsp, assignp); + } + + // Add a debug statement for this trigger + std::stringstream ss; + V3EmitV::verilogForTree(senTreep, ss); + addDebug(triggerNumber, ss.str()); + + // + ++triggerNumber; + } + // Add the update statements + for (AstNodeStmt* const nodep : senExprBuilder.getAndClearUpdates()) funcp->addStmtsp(nodep); + + // Add the initialization statements + if (initialTrigsp) { + AstVarScope* const vscp = scopeTopp->createTemp("__V" + name + "DidInit", 1); + AstVarRef* const condp = new AstVarRef{flp, vscp, VAccess::READ}; + AstIf* const ifp = new AstIf{flp, new AstNot{flp, condp}}; + funcp->addStmtsp(ifp); + ifp->branchPred(VBranchPred::BP_UNLIKELY); + ifp->addIfsp(setVar(vscp, 1)); + ifp->addIfsp(initialTrigsp); + } + + // Add a call to the dumping function if debug is enabled + { + AstTextBlock* const blockp = new AstTextBlock{flp}; + funcp->addStmtsp(blockp); + const auto add = [&](const string& text) { blockp->addText(flp, text, true); }; + add("#ifdef VL_DEBUG\n"); + add("if (VL_UNLIKELY(vlSymsp->_vm_contextp__->debug())) {\n"); + blockp->addNodep(new AstCCall(flp, dumpp)); + add("}\n"); + add("#endif\n"); + } + + return {vscp, funcp, dumpp, map}; +} + +//============================================================================ +// Helpers to construct an evaluation loop. + +AstNode* buildLoop(AstNetlist* netlistp, const string& name, + std::function build) // +{ + AstTopScope* const topScopep = netlistp->topScopep(); + AstScope* const scopeTopp = topScopep->scopep(); + FileLine* const flp = scopeTopp->fileline(); + // Create the loop condition variable + AstVarScope* const condp = scopeTopp->createTemp("__V" + name + "Continue", 1); + // Initialize the loop condition variable to true + AstNode* const resp = setVar(condp, 1); + // Add the loop + AstWhile* const loopp = new AstWhile{flp, new AstVarRef{flp, condp, VAccess::READ}}; + resp->addNext(loopp); + // Clear the loop condition variable in the loop + loopp->addBodysp(setVar(condp, 0)); + // Build the body + build(condp, loopp); + // Done + return resp; +}; + +std::pair makeEvalLoop(AstNetlist* netlistp, const string& tag, + const string& name, AstVarScope* trigVscp, + AstCFunc* trigDumpp, + std::function computeTriggers, + std::function makeBody) { + UASSERT_OBJ(trigVscp->dtypep()->basicp()->isTriggerVec(), trigVscp, "Not TRIGGERVEC"); + AstTopScope* const topScopep = netlistp->topScopep(); + AstScope* const scopeTopp = topScopep->scopep(); + FileLine* const flp = scopeTopp->fileline(); + + AstVarScope* const counterp = scopeTopp->createTemp("__V" + tag + "IterCount", 32); + + AstNode* nodep = setVar(counterp, 0); + nodep->addNext(buildLoop(netlistp, tag, [&](AstVarScope* continuep, AstWhile* loopp) { + // Compute triggers + loopp->addBodysp(computeTriggers()); + // Invoke body if triggered + { + AstVarRef* const refp = new AstVarRef{flp, trigVscp, VAccess::READ}; + AstCMethodHard* const callp = new AstCMethodHard{flp, refp, "any"}; + callp->dtypeSetBit(); + AstIf* const ifp = new AstIf{flp, callp}; + loopp->addBodysp(ifp); + ifp->addIfsp(setVar(continuep, 1)); + + // If we exceeded the iteration limit, die + { + const uint32_t limit = v3Global.opt.convergeLimit(); + AstVarRef* const refp = new AstVarRef{flp, counterp, VAccess::READ}; + AstConst* const constp + = new AstConst{flp, AstConst::DtypedValue{}, counterp->dtypep(), limit}; + AstNodeMath* const condp = new AstGt{flp, refp, constp}; + AstIf* const failp = new AstIf{flp, condp}; + ifp->addIfsp(failp); + AstTextBlock* const blockp = new AstTextBlock{flp}; + failp->addIfsp(blockp); + FileLine* const locp = netlistp->topModulep()->fileline(); + const string& file = EmitCBaseVisitor::protect(locp->filename()); + const string& line = cvtToStr(locp->lineno()); + const auto add = [&](const string& text) { blockp->addText(flp, text, true); }; + add("#ifdef VL_DEBUG\n"); + blockp->addNodep(new AstCCall{flp, trigDumpp}); + add("#endif\n"); + add("VL_FATAL_MT(\"" + file + "\", " + line + ", \"\", "); + add("\"" + name + " region did not converge.\");\n"); + } + + // Increment iteration count + { + AstVarRef* const wrefp = new AstVarRef{flp, counterp, VAccess::WRITE}; + AstVarRef* const rrefp = new AstVarRef{flp, counterp, VAccess::READ}; + AstConst* const onep + = new AstConst{flp, AstConst::DtypedValue{}, counterp->dtypep(), 1}; + ifp->addIfsp(new AstAssign{flp, wrefp, new AstAdd{flp, rrefp, onep}}); + } + + // Add body + ifp->addIfsp(makeBody()); + } + })); + + return {counterp, nodep}; +} + +//============================================================================ +// Order the combinational logic to create the settle loop + +void createSettle(AstNetlist* netlistp, SenExprBuilder& senExprBulider, + LogicClasses& logicClasses) { + AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_settle", true); + + // Clone, because ordering is destructive, but we still need them for "_eval" + LogicByScope comb = logicClasses.m_comb.clone(); + LogicByScope hybrid = logicClasses.m_hybrid.clone(); + + // Nothing to do if there is no logic. + // While this is rare in real designs, it reduces noise in small tests. + if (comb.empty() && hybrid.empty()) return; + + // We have an extra trigger denoting this is the first iteration of the settle loop + constexpr unsigned firstIterationTrigger = 0; + constexpr unsigned extraTriggers = firstIterationTrigger + 1; + + // Gather the relevant sensitivity expressions and create the trigger kit + const auto& senTreeps = getSenTreesUsedBy({&comb, &hybrid}); + const TriggerKit& trig + = createTriggers(netlistp, senExprBulider, senTreeps, "stl", extraTriggers, true); + + // Remap sensitivities (comb has none, so only do the hybrid) + remapSensitivities(hybrid, trig.m_map); + + // Create the inverse map from trigger ref AstSenTree to original AstSenTree + std::unordered_map trigToSen; + invertAndMergeSenTreeMap(trigToSen, trig.m_map); + + // First trigger is for pure combinational triggers (first iteration) + AstSenTree* const inputChanged = trig.createTriggerSenTree(netlistp, firstIterationTrigger); + + // Create and the body function + AstCFunc* const stlFuncp + = V3Order::order(netlistp, {&comb, &hybrid}, trigToSen, "stl", false, true, + [=](const AstVarScope*) { return inputChanged; }); + splitCheck(stlFuncp); + + // Create the eval loop + const auto& pair = makeEvalLoop( + netlistp, "stl", "Settle", trig.m_vscp, trig.m_dumpp, + [&]() { // Trigger + return new AstCCall{stlFuncp->fileline(), trig.m_funcp}; + }, + [&]() { // Body + return new AstCCall{stlFuncp->fileline(), stlFuncp}; + }); + + // Add the first iteration trigger to the trigger computation function + trig.addFirstIterationTriggerAssignment(pair.first, firstIterationTrigger); + + // Add the eval loop to the top function + funcp->addStmtsp(pair.second); +} + +//============================================================================ +// Order the replicated combinational logic to create the 'ico' region + +AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilder, + LogicByScope& logic) { + // Nothing to do if no combinational logic is sensitive to top level inputs + if (logic.empty()) return nullptr; + + // SystemC only: Any top level inputs feeding a combinational logic must be marked, + // so we can make them sc_sensitive + if (v3Global.opt.systemC()) { + logic.foreachLogic([](AstNode* logicp) { + logicp->foreach([](AstVarRef* refp) { + if (refp->access().isWriteOnly()) return; + AstVarScope* const vscp = refp->varScopep(); + if (vscp->scopep()->isTop() && vscp->varp()->isNonOutput()) { + vscp->varp()->scSensitive(true); + } + }); + }); + } + + // We have an extra trigger denoting this is the first iteration of the ico loop + constexpr unsigned firstIterationTrigger = 0; + constexpr unsigned extraTriggers = firstIterationTrigger + 1; + + // Gather the relevant sensitivity expressions and create the trigger kit + const auto& senTreeps = getSenTreesUsedBy({&logic}); + const TriggerKit& trig + = createTriggers(netlistp, senExprBuilder, senTreeps, "ico", extraTriggers); + + // Remap sensitivities + remapSensitivities(logic, trig.m_map); + + // Create the inverse map from trigger ref AstSenTree to original AstSenTree + std::unordered_map trigToSen; + invertAndMergeSenTreeMap(trigToSen, trig.m_map); + + // First trigger is for pure combinational triggers (first iteration) + AstSenTree* const inputChanged = trig.createTriggerSenTree(netlistp, firstIterationTrigger); + + // Create and Order the body function + AstCFunc* const icoFuncp = V3Order::order( + netlistp, {&logic}, trigToSen, "ico", false, false, [=](const AstVarScope* vscp) { + return vscp->scopep()->isTop() && vscp->varp()->isNonOutput() ? inputChanged : nullptr; + }); + splitCheck(icoFuncp); + + // Create the eval loop + const auto& pair = makeEvalLoop( + netlistp, "ico", "Input combinational", trig.m_vscp, trig.m_dumpp, + [&]() { // Trigger + return new AstCCall{icoFuncp->fileline(), trig.m_funcp}; + }, + [&]() { // Body + return new AstCCall{icoFuncp->fileline(), icoFuncp}; + }); + + // Add the first iteration trigger to the trigger computation function + trig.addFirstIterationTriggerAssignment(pair.first, firstIterationTrigger); + + // Return the eval loop itself + return pair.second; +} + +//============================================================================ +// Bold together parts to create the top level _eval function + +void createEval(AstNetlist* netlistp, // + AstNode* icoLoop, // + const TriggerKit& actTrig, // + AstVarScope* preTrigsp, // + AstVarScope* nbaTrigsp, // + AstCFunc* actFuncp, // + AstCFunc* nbaFuncp // +) { + FileLine* const flp = netlistp->fileline(); + + AstCFunc* const funcp = makeTopFunction(netlistp, "_eval", false); + netlistp->evalp(funcp); + + // Start with the ico loop, if any + if (icoLoop) funcp->addStmtsp(icoLoop); + + // Create the NBA trigger dumping function, which is the same as act trigger + // dumping function, but referencing the nba trigger vector. + AstCFunc* const nbaDumpp = actTrig.m_dumpp->cloneTree(false); + actTrig.m_dumpp->addNextHere(nbaDumpp); + nbaDumpp->name("_dump_triggers__nba"); + nbaDumpp->foreach([&](AstVarRef* refp) { + UASSERT_OBJ(refp->access().isReadOnly(), refp, "Should only read state"); + if (refp->varScopep() == actTrig.m_vscp) { + refp->replaceWith(new AstVarRef{refp->fileline(), nbaTrigsp, VAccess::READ}); + } + }); + nbaDumpp->foreach([&](AstText* textp) { // + textp->text(VString::replaceWord(textp->text(), "act", "nba")); + }); + + // Create the active eval loop + AstNode* const activeEvalLoopp + = makeEvalLoop( + netlistp, "act", "Active", actTrig.m_vscp, actTrig.m_dumpp, + [&]() { // Trigger + return new AstCCall{flp, actTrig.m_funcp}; + }, + [&]() { // Body + AstNode* resultp = nullptr; + + // Compute the pre triggers + { + AstVarRef* const lhsp = new AstVarRef{flp, preTrigsp, VAccess::WRITE}; + AstVarRef* const opap = new AstVarRef{flp, actTrig.m_vscp, VAccess::READ}; + AstVarRef* const opbp = new AstVarRef{flp, nbaTrigsp, VAccess::READ}; + opap->addNext(opbp); + AstCMethodHard* const callp = new AstCMethodHard{flp, lhsp, "andNot", opap}; + callp->statement(true); + callp->dtypeSetVoid(); + resultp = AstNode::addNext(resultp, callp); + } + + // Latch the active trigger flags under the NBA trigger flags + { + AstVarRef* const lhsp = new AstVarRef{flp, nbaTrigsp, VAccess::WRITE}; + AstVarRef* const argp = new AstVarRef{flp, actTrig.m_vscp, VAccess::READ}; + AstCMethodHard* const callp = new AstCMethodHard{flp, lhsp, "set", argp}; + callp->statement(true); + callp->dtypeSetVoid(); + resultp = AstNode::addNext(resultp, callp); + } + + // Invoke body function + return AstNode::addNext(resultp, new AstCCall{flp, actFuncp}); + }) + .second; + + // Create the NBA eval loop. This uses the Active eval loop in the trigger section. + AstNode* const nbaEvalLoopp + = makeEvalLoop( + netlistp, "nba", "NBA", nbaTrigsp, nbaDumpp, + [&]() { // Trigger + AstNode* resultp = nullptr; + + // Reset NBA triggers + { + AstVarRef* const refp = new AstVarRef{flp, nbaTrigsp, VAccess::WRITE}; + AstCMethodHard* const callp = new AstCMethodHard{flp, refp, "clear"}; + callp->statement(true); + callp->dtypeSetVoid(); + resultp = AstNode::addNext(resultp, callp); + } + + // Run the Active eval loop + return AstNode::addNext(resultp, activeEvalLoopp); + }, + [&]() { // Body + return new AstCCall{flp, nbaFuncp}; + }) + .second; + + // Add the NBA eval loop + funcp->addStmtsp(nbaEvalLoopp); +} + +} // namespace + +//============================================================================ +// Top level entry-point to scheduling + +void schedule(AstNetlist* netlistp) { + const auto addSizeStat = [](const string name, const LogicByScope& lbs) { + uint64_t size = 0; + lbs.foreachLogic([&](AstNode* nodep) { size += nodep->nodeCount(); }); + V3Stats::addStat("Scheduling, " + name, size); + }; + + // Step 1. Gather and classify all logic in the design + LogicClasses logicClasses = gatherLogicClasses(netlistp); + + if (v3Global.opt.stats()) { + V3Stats::statsStage("sched-gather"); + addSizeStat("size of class: static", logicClasses.m_static); + addSizeStat("size of class: initial", logicClasses.m_initial); + addSizeStat("size of class: final", logicClasses.m_final); + } + + // Step 2. Schedule static, initial and final logic classes in source order + createStatic(netlistp, logicClasses); + if (v3Global.opt.stats()) V3Stats::statsStage("sched-static"); + + AstCFunc* const initp = createInitial(netlistp, logicClasses); + if (v3Global.opt.stats()) V3Stats::statsStage("sched-initial"); + + createFinal(netlistp, logicClasses); + if (v3Global.opt.stats()) V3Stats::statsStage("sched-final"); + + // Step 3: Break combinational cycles by introducing hybrid logic + // Note: breakCycles also removes corresponding logic from logicClasses.m_comb; + logicClasses.m_hybrid = breakCycles(netlistp, logicClasses.m_comb); + if (v3Global.opt.stats()) { + addSizeStat("size of class: clocked", logicClasses.m_clocked); + addSizeStat("size of class: combinational", logicClasses.m_comb); + addSizeStat("size of class: hybrid", logicClasses.m_hybrid); + V3Stats::statsStage("sched-break-cycles"); + } + + // We pass around a single SenExprBuilder instance, as we only need one set of 'prev' variables + // for edge/change detection in sensitivity expressions, which this keeps track of. + SenExprBuilder senExprBuilder{netlistp, initp}; + + // Step 4: Create 'settle' region that restores the combinational invariant + createSettle(netlistp, senExprBuilder, logicClasses); + if (v3Global.opt.stats()) V3Stats::statsStage("sched-settle"); + + // Step 5: Partition the clocked and combinational (including hybrid) logic into pre/act/nba. + // All clocks (signals referenced in an AstSenTree) generated via a blocking assignment + // (including combinationally generated signals) are computed within the act region. + LogicRegions logicRegions + = partition(logicClasses.m_clocked, logicClasses.m_comb, logicClasses.m_hybrid); + if (v3Global.opt.stats()) { + addSizeStat("size of region: Active Pre", logicRegions.m_pre); + addSizeStat("size of region: Active", logicRegions.m_act); + addSizeStat("size of region: NBA", logicRegions.m_nba); + V3Stats::statsStage("sched-partition"); + } + + // Step 6: Replicate combinational logic + LogicReplicas logicReplicas = replicateLogic(logicRegions); + if (v3Global.opt.stats()) { + addSizeStat("size of replicated logic: Input", logicReplicas.m_ico); + addSizeStat("size of replicated logic: Active", logicReplicas.m_act); + addSizeStat("size of replicated logic: NBA", logicReplicas.m_nba); + V3Stats::statsStage("sched-replicate"); + } + + // Step 7: Create input combinational logic loop + AstNode* const icoLoopp = createInputCombLoop(netlistp, senExprBuilder, logicReplicas.m_ico); + if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-ico"); + + // Step 8: Create the pre/act/nba triggers + const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre, // + &logicRegions.m_act, // + &logicRegions.m_nba}); + const TriggerKit& actTrig = createTriggers(netlistp, senExprBuilder, senTreeps, "act", 0); + + AstTopScope* const topScopep = netlistp->topScopep(); + AstScope* const scopeTopp = topScopep->scopep(); + + AstVarScope* const actTrigVscp = actTrig.m_vscp; + AstVarScope* const preTrigVscp = scopeTopp->createTempLike("__VpreTriggered", actTrigVscp); + AstVarScope* const nbaTrigVscp = scopeTopp->createTempLike("__VnbaTriggered", actTrigVscp); + + const auto cloneMapWithNewTriggerReferences + = [=](std::unordered_map map, AstVarScope* vscp) { + // Copy map + auto newMap{map}; + VNDeleter deleter; + // Replace references in each mapped value with a reference to the given vscp + for (auto& pair : newMap) { + pair.second = pair.second->cloneTree(false); + pair.second->foreach([&](AstVarRef* refp) { + UASSERT_OBJ(refp->varScopep() == actTrigVscp, refp, "Unexpected reference"); + UASSERT_OBJ(refp->access() == VAccess::READ, refp, "Should be read ref"); + refp->replaceWith(new AstVarRef{refp->fileline(), vscp, VAccess::READ}); + deleter.pushDeletep(refp); + }); + topScopep->addSenTreep(pair.second); + } + return newMap; + }; + + const auto& actTrigMap = actTrig.m_map; + const auto preTrigMap = cloneMapWithNewTriggerReferences(actTrigMap, preTrigVscp); + const auto nbaTrigMap = cloneMapWithNewTriggerReferences(actTrigMap, nbaTrigVscp); + if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-triggers"); + + // Note: Experiments so far show that running the Act (or Ico) regions on + // multiple threads is always a net loss, so only use multi-threading for + // NBA for now. This can be revised if evidence is available that it would + // be beneficial + + // Step 9: Create the 'act' region evaluation function + + // Remap sensitivities of the input logic to the triggers + remapSensitivities(logicRegions.m_pre, preTrigMap); + remapSensitivities(logicRegions.m_act, actTrigMap); + remapSensitivities(logicReplicas.m_act, actTrigMap); + + // Create the inverse map from trigger ref AstSenTree to original AstSenTree + std::unordered_map trigToSenAct; + invertAndMergeSenTreeMap(trigToSenAct, preTrigMap); + invertAndMergeSenTreeMap(trigToSenAct, actTrigMap); + + AstCFunc* const actFuncp = V3Order::order( + netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct, + "act", false, false, [](const AstVarScope*) { return nullptr; }); + splitCheck(actFuncp); + if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-act"); + + // Step 10: Create the 'nba' region evaluation function + + // Remap sensitivities of the input logic to the triggers + remapSensitivities(logicRegions.m_nba, nbaTrigMap); + remapSensitivities(logicReplicas.m_nba, nbaTrigMap); + + // Create the inverse map from trigger ref AstSenTree to original AstSenTree + std::unordered_map trigToSenNba; + invertAndMergeSenTreeMap(trigToSenNba, nbaTrigMap); + + AstCFunc* const nbaFuncp = V3Order::order( + netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba, "nba", + v3Global.opt.mtasks(), false, [](const AstVarScope*) { return nullptr; }); + splitCheck(nbaFuncp); + netlistp->evalNbap(nbaFuncp); // Remember for V3LifePost + if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-nba"); + + // Step 11: Bolt it all together to create the '_eval' function + createEval(netlistp, icoLoopp, actTrig, preTrigVscp, nbaTrigVscp, actFuncp, nbaFuncp); + + splitCheck(initp); + + V3Global::dumpCheckGlobalTree("sched", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); +} + +} // namespace V3Sched diff --git a/src/V3Sched.h b/src/V3Sched.h new file mode 100644 index 000000000..43603c792 --- /dev/null +++ b/src/V3Sched.h @@ -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 +#include +#include + +//============================================================================ + +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> { + // 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 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 diff --git a/src/V3SchedAcyclic.cpp b/src/V3SchedAcyclic.cpp new file mode 100644 index 000000000..61a0c7a7a --- /dev/null +++ b/src/V3SchedAcyclic.cpp @@ -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 +#include +#include +#include + +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(vtxp)) { + AstNode* const logicp = lvtxp->logicp(); + std::cerr << logicp->fileline()->warnOther() + << " Example path: " << logicp->typeName() << endl; + } + if (VarVertex* const vvtxp = dynamic_cast(vtxp)) { + AstVarScope* const vscp = vvtxp->vscp(); + std::cerr << vscp->fileline()->warnOther() + << " Example path: " << vscp->prettyName() << endl; + } + } +}; + +//############################################################################## +// Algorithm implementation + +std::unique_ptr buildGraph(const LogicByScope& lbs) { + std::unique_ptr 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(); + }; + + 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* 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 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 findCutVertices(Graph* graphp) { + std::vector result; + const VNUser1InUse user1InUse; // bool: already added to result + for (V3GraphVertex* vtxp = graphp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) { + if (VarVertex* const vvtxp = dynamic_cast(vtxp)) { + if (!vvtxp->vscp()->user1SetOnce() && isCut(vvtxp)) result.push_back(vvtxp); + } + } + return result; +} + +void resetEdgeWeights(const std::vector& 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; + +// Gather all splitting candidates that are in the same SCC as the given vertex +void gatherSCCCandidates(V3GraphVertex* vtxp, std::vector& candidates) { + if (vtxp->user()) return; // Already done + vtxp->user(true); + + if (VarVertex* const vvtxp = dynamic_cast(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 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 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& 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& 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> lvtx2Cuts; + std::vector 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(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 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 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 diff --git a/src/V3SchedPartition.cpp b/src/V3SchedPartition.cpp new file mode 100644 index 000000000..2f3afc2f1 --- /dev/null +++ b/src/V3SchedPartition.cpp @@ -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 +#include +#include + +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(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, 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 m_readTriggersThisLogic; + + VL_DEBUG_FUNC; + + SchedVarVertex* getVarVertex(AstVarScope* vscp) const { + if (!vscp->user1p()) vscp->user1p(new SchedVarVertex{m_graphp, vscp}); + return vscp->user1u().to(); + } + + 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* 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(); + } + + 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* 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([=](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([=](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([](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 build(const LogicByScope& clockedLogic, + const LogicByScope& combinationalLogic, + const LogicByScope& hybridLogic) { + SchedGraphBuilder visitor{clockedLogic, combinationalLogic, hybridLogic}; + return std::unique_ptr{visitor.m_graphp}; + } +}; + +void colorActiveRegion(const V3Graph& graph) { + // Work queue for depth first traversal + std::vector queue{}; + + // Trace from all SchedSenVertex + for (V3GraphVertex* vtxp = graph.verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) { + if (const auto activeEventVtxp = dynamic_cast(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(&vtx)) { + for (V3GraphEdge* edgep = vtx.outBeginp(); edgep; edgep = edgep->outNextp()) { + UASSERT(dynamic_cast(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 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(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([](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([&](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 diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp new file mode 100644 index 000000000..694764f37 --- /dev/null +++ b/src/V3SchedReplicate.cpp @@ -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 + +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(m_drivingRegions | regions); + } + + // For graph dumping + string dotColor() const override { + switch (static_cast(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 buildGraph(const LogicRegions& logicRegions) { + std::unique_ptr 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(); + }; + + 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 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([](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* 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(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(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 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(vtxp)); + } + // Dump for debug + graphp->dumpDotFilePrefixed("sched-replicate-propagated"); + // Replicate the necessary logic + return replicate(graphp.get()); +} + +} // namespace V3Sched diff --git a/src/V3SenTree.h b/src/V3SenTree.h index c140580a0..864cea0a4 100644 --- a/src/V3SenTree.h +++ b/src/V3SenTree.h @@ -36,9 +36,20 @@ private: // STATE AstTopScope* const m_topScopep; // Top scope to add global SenTrees to std::unordered_set> m_trees; // Set of global SenTrees + AstSenTree* m_combop = nullptr; // The unique combinational domain SenTree + AstSenTree* m_initialp = nullptr; // The unique initial domain SenTree VL_UNCOPYABLE(SenTreeFinder); + template // + AstSenTree* makeUnique() { + FileLine* const fl = m_topScopep->fileline(); + AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, T_Domain{}}}; + AstSenTree* const restultp = getSenTree(senTreep); + VL_DO_DANGLING(senTreep->deleteTree(), senTreep); // getSenTree clones, so can delete + return restultp; + } + public: // CONSTRUCTORS SenTreeFinder() @@ -50,6 +61,8 @@ public: for (AstSenTree* senTreep = m_topScopep->senTreesp(); senTreep; senTreep = VN_AS(senTreep->nextp(), SenTree)) { m_trees.emplace(*senTreep); + if (senTreep->hasCombo()) m_combop = senTreep; + if (senTreep->hasInitial()) m_initialp = senTreep; } } @@ -72,11 +85,15 @@ public: // Return the global combinational AstSenTree. // If no such global SenTree exists create one and add it to the stored AstTopScope. AstSenTree* getComb() { - FileLine* const fl = m_topScopep->fileline(); - AstSenTree* const combp = new AstSenTree{fl, new AstSenItem{fl, AstSenItem::Combo()}}; - AstSenTree* const resultp = getSenTree(combp); - VL_DO_DANGLING(combp->deleteTree(), combp); // getSenTree clones, so can delete - return resultp; + if (!m_combop) m_combop = makeUnique(); + return m_combop; + } + + // Return the global initial AstSenTree. + // If no such global SenTree exists create one and add it to the stored AstTopScope. + AstSenTree* getInitial() { + if (!m_initialp) m_initialp = makeUnique(); + return m_initialp; } }; diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 0e150d31f..2122a59e2 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -1056,17 +1056,18 @@ private: } AstVarScope* makeDpiExporTrigger() { - AstVarScope* dpiExportTriggerp = v3Global.rootp()->dpiExportTriggerp(); + AstNetlist* const netlistp = v3Global.rootp(); + AstVarScope* dpiExportTriggerp = netlistp->dpiExportTriggerp(); if (!dpiExportTriggerp) { // Create the global DPI export trigger flag the first time we encounter a DPI export. // This flag is set any time a DPI export is invoked, and cleared at the end of eval. FileLine* const fl = m_topScopep->fileline(); - AstVar* const varp - = new AstVar{fl, VVarType::VAR, "__Vdpi_export_trigger", VFlagBitPacked{}, 1}; + const string name{"__Vdpi_export_trigger"}; + AstVar* const varp = new AstVar{fl, VVarType::VAR, name, VFlagBitPacked{}, 1}; m_topScopep->scopep()->modp()->addStmtp(varp); dpiExportTriggerp = new AstVarScope{fl, m_topScopep->scopep(), varp}; m_topScopep->scopep()->addVarp(dpiExportTriggerp); - v3Global.rootp()->dpiExportTriggerp(dpiExportTriggerp); + netlistp->dpiExportTriggerp(dpiExportTriggerp); } return dpiExportTriggerp; } @@ -1309,7 +1310,7 @@ private: AstAlways* const alwaysp = new AstAlways{ fl, VAlwaysKwd::ALWAYS, new AstSenTree{ - fl, new AstSenItem{fl, VEdgeType::ET_HIGHEDGE, + fl, new AstSenItem{fl, VEdgeType::ET_DPIEXPORT, new AstVarRef{fl, dpiExportTriggerp, VAccess::READ}}}, nullptr}; for (AstVarScope* const varScopep : writtenps) { diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 17e302ccc..93d59bb16 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2519,7 +2519,7 @@ private: methodCallClass(nodep, adtypep); } else if (AstUnpackArrayDType* const adtypep = VN_CAST(fromDtp, UnpackArrayDType)) { methodCallUnpack(nodep, adtypep); - } else if (basicp && basicp->isEventValue()) { + } else if (basicp && basicp->isEvent()) { methodCallEvent(nodep, basicp); } else if (basicp && basicp->isString()) { methodCallString(nodep, basicp); @@ -3119,10 +3119,12 @@ private: void methodCallEvent(AstMethodCall* nodep, AstBasicDType*) { // Method call on event if (nodep->name() == "triggered") { - // We represent events as numbers, so can just return number methodOkArguments(nodep, 0, 0); - AstNode* const newp = nodep->fromp()->unlinkFrBack(); - nodep->replaceWith(newp); + AstCMethodHard* const callp = new AstCMethodHard{ + nodep->fileline(), nodep->fromp()->unlinkFrBack(), "isTriggered"}; + callp->dtypeSetBit(); + callp->pure(true); + nodep->replaceWith(callp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } else { nodep->v3error("Unknown built-in event method " << nodep->prettyNameQ()); @@ -3979,7 +3981,7 @@ private: // if (debug()) nodep->dumpTree(cout, " AssignOut: "); } if (const AstBasicDType* const basicp = nodep->rhsp()->dtypep()->basicp()) { - if (basicp->isEventValue()) { + if (basicp->isEvent()) { // see t_event_copy.v for commentary on the mess involved nodep->v3warn(E_UNSUPPORTED, "Unsupported: assignment of event data type"); } @@ -4783,6 +4785,9 @@ private: userIterateChildren(nodep, nullptr); m_procedurep = nullptr; } + virtual void visit(AstSenItem* nodep) override { + userIterateChildren(nodep, WidthVP(SELF, BOTH).p()); + } virtual void visit(AstWith* nodep) override { // Should otherwise be underneath a method call AstNodeDType* const vdtypep = m_vup->dtypeNullSkipRefp(); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index e233a041c..cabbb37b2 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -29,7 +29,6 @@ #include "V3Case.h" #include "V3Cast.h" #include "V3Cdc.h" -#include "V3Changed.h" #include "V3Class.h" #include "V3Clean.h" #include "V3Clock.h" @@ -53,7 +52,6 @@ #include "V3File.h" #include "V3Force.h" #include "V3Gate.h" -#include "V3GenClk.h" #include "V3Graph.h" #include "V3HierBlock.h" #include "V3Inline.h" @@ -70,7 +68,6 @@ #include "V3Localize.h" #include "V3MergeCond.h" #include "V3Name.h" -#include "V3Order.h" #include "V3Os.h" #include "V3Param.h" #include "V3ParseSym.h" @@ -80,6 +77,7 @@ #include "V3ProtectLib.h" #include "V3Randomize.h" #include "V3Reloop.h" +#include "V3Sched.h" #include "V3Scope.h" #include "V3Scoreboard.h" #include "V3Slice.h" @@ -376,11 +374,8 @@ static void process() { if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "PreOrder"); - // Order the code; form SBLOCKs and BLOCKCALLs - V3Order::orderAll(v3Global.rootp()); - - // Change generated clocks to look at delayed signals - V3GenClk::genClkAll(v3Global.rootp()); + // Schedule the logic + V3Sched::schedule(v3Global.rootp()); // Convert sense lists into IF statements. V3Clock::clockAll(v3Global.rootp()); @@ -392,15 +387,13 @@ static void process() { V3Const::constifyAll(v3Global.rootp()); V3Life::lifeAll(v3Global.rootp()); } + if (v3Global.opt.oLifePost()) V3LifePost::lifepostAll(v3Global.rootp()); // Remove unused vars V3Const::constifyAll(v3Global.rootp()); V3Dead::deadifyAllScoped(v3Global.rootp()); - // Detect change loop - V3Changed::changedAll(v3Global.rootp()); - // Create tracing logic, since we ripped out some signals the user might want to trace // Note past this point, we presume traced variables won't move between CFuncs // (It's OK if untraced temporaries move around, or vars diff --git a/src/verilog.y b/src/verilog.y index 84c778b45..9d6363c05 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -1882,9 +1882,12 @@ data_typeNoRef: // ==IEEE: data_type, excluding class_ty { $$ = new AstDefImplicitDType{$1->fileline(), "__typeimpenum" + cvtToStr(GRAMMARP->s_modTypeImpNum++), SYMP, VFlagChildDType{}, $1}; } - | ySTRING { $$ = new AstBasicDType($1,VBasicDTypeKwd::STRING); } - | yCHANDLE { $$ = new AstBasicDType($1,VBasicDTypeKwd::CHANDLE); } - | yEVENT { $$ = new AstBasicDType($1,VBasicDTypeKwd::EVENTVALUE); } + | ySTRING + { $$ = new AstBasicDType{$1, VBasicDTypeKwd::STRING}; } + | yCHANDLE + { $$ = new AstBasicDType{$1, VBasicDTypeKwd::CHANDLE}; } + | yEVENT + { $$ = new AstBasicDType{$1, VBasicDTypeKwd::EVENT}; v3Global.setHasEvents(); } // // Rules overlap virtual_interface_declaration // // Parameters here are SV2009 // // IEEE has ['.' modport] but that will conflict with port @@ -2999,7 +3002,7 @@ senitem: // IEEE: part of event_expression, non-'OR' ',' ; senitemVar: - idClassSel { $$ = new AstSenItem($1->fileline(), VEdgeType::ET_ANYEDGE, $1); } + idClassSel { $$ = new AstSenItem{$1->fileline(), VEdgeType::ET_CHANGED, $1}; } ; senitemEdge: // IEEE: part of event_expression @@ -3221,13 +3224,11 @@ statement_item: // IEEE: statement_item | yDISABLE yFORK ';' { $$ = new AstDisableFork($1); } // // IEEE: event_trigger | yP_MINUSGT idDotted/*hierarchical_identifier-event*/ ';' - { // AssignDly because we don't have stratified queue, and need to - // read events, clear next event, THEN apply this set - $$ = new AstAssignDly($1, $2, new AstConst($1, AstConst::BitTrue())); } + { $$ = new AstFireEvent{$1, $2, false}; } //UNSUP yP_MINUSGTGT delay_or_event_controlE hierarchical_identifier/*event*/ ';' { UNSUP } // // IEEE remove below | yP_MINUSGTGT delayE idDotted/*hierarchical_identifier-event*/ ';' - { $$ = new AstAssignDly($1, $3, new AstConst($1, AstConst::BitTrue())); } + { $$ = new AstFireEvent{$1, $3, true}; } // // // IEEE: loop_statement | yFOREVER stmtBlock { $$ = new AstWhile($1,new AstConst($1, AstConst::BitTrue()), $2); } diff --git a/test_regress/t/t_alw_dly.pl b/test_regress/t/t_alw_dly.pl index 102ff669d..b46d46042 100755 --- a/test_regress/t/t_alw_dly.pl +++ b/test_regress/t/t_alw_dly.pl @@ -11,7 +11,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-Wno-CLKDATA"], ); execute( diff --git a/test_regress/t/t_case_huge.pl b/test_regress/t/t_case_huge.pl index 73bc861e6..79a6a7976 100755 --- a/test_regress/t/t_case_huge.pl +++ b/test_regress/t/t_case_huge.pl @@ -16,7 +16,7 @@ compile( if ($Self->{vlt_all}) { file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 10); - file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, 8); + file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, 0); } execute( diff --git a/test_regress/t/t_clk_condflop_nord.v b/test_regress/t/t_clk_condflop_nord.v deleted file mode 100644 index a28335121..000000000 --- a/test_regress/t/t_clk_condflop_nord.v +++ /dev/null @@ -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 diff --git a/test_regress/t/t_clk_dpulse.v b/test_regress/t/t_clk_dpulse.v index fdb1b2499..5ab490ffa 100644 --- a/test_regress/t/t_clk_dpulse.v +++ b/test_regress/t/t_clk_dpulse.v @@ -11,8 +11,6 @@ module t (/*AUTOARG*/ input clk; - // verilator lint_off GENCLK - reg [7:0] cyc; initial cyc = 0; reg genclk; // verilator lint_off MULTIDRIVEN diff --git a/test_regress/t/t_clk_dsp.v b/test_regress/t/t_clk_dsp.v index 0f656e3df..6e761b17b 100644 --- a/test_regress/t/t_clk_dsp.v +++ b/test_regress/t/t_clk_dsp.v @@ -11,8 +11,6 @@ module t (/*AUTOARG*/ input clk; - // verilator lint_off GENCLK - reg [7:0] cyc; initial cyc = 0; reg [7:0] padd; reg dsp_ph1, dsp_ph2, dsp_reset; diff --git a/test_regress/t/t_clk_first.v b/test_regress/t/t_clk_first.v index 03ae64841..b2de33e7c 100644 --- a/test_regress/t/t_clk_first.v +++ b/test_regress/t/t_clk_first.v @@ -52,10 +52,8 @@ module t_clk (/*AUTOARG*/ // verilator lint_on MULTIDRIVEN reg [7:0] int_clocks_copy; - // verilator lint_off GENCLK reg internal_clk; initial internal_clk = 0; reg reset_int_; - // verilator lint_on GENCLK always @ (posedge clk) begin `ifdef TEST_VERBOSE @@ -170,9 +168,7 @@ module t_clk_two (/*AUTOARG*/ ); input fastclk; input reset_l; - // verilator lint_off GENCLK reg clk2; - // verilator lint_on GENCLK reg [31:0] count; t_clk_twob tb (.*); diff --git a/test_regress/t/t_clk_gen.v b/test_regress/t/t_clk_gen.v index fcc149f68..34b4d2f7b 100644 --- a/test_regress/t/t_clk_gen.v +++ b/test_regress/t/t_clk_gen.v @@ -12,7 +12,6 @@ module t (/*AUTOARG*/ input clk; integer cyc; initial cyc=1; - // verilator lint_off GENCLK reg gendlyclk_r; reg [31:0] gendlydata_r; reg [31:0] dlydata_gr; diff --git a/test_regress/t/t_clk_latch.pl b/test_regress/t/t_clk_latch.pl index 1f8ac0132..ce1f8586f 100755 --- a/test_regress/t/t_clk_latch.pl +++ b/test_regress/t/t_clk_latch.pl @@ -10,14 +10,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -my $fail = $Self->{vlt_all}; - compile( ); execute( - check_finished => !$fail, - fails => $fail, + check_finished => 1 ); ok(1); diff --git a/test_regress/t/t_clk_latch_edgestyle.pl b/test_regress/t/t_clk_latch_edgestyle.pl index ca72a6975..52730f4f4 100755 --- a/test_regress/t/t_clk_latch_edgestyle.pl +++ b/test_regress/t/t_clk_latch_edgestyle.pl @@ -12,16 +12,14 @@ scenarios(simulator => 1); top_filename("t/t_clk_latch.v"); -my $fail = $Self->{vlt_all}; compile( v_flags2 => ['+define+EDGE_DETECT_STYLE'], - fails => $fail, ); execute( - check_finished => !$fail, - ) if !$fail; + check_finished => 1 + ); ok(1); 1; diff --git a/test_regress/t/t_clk_latchgate.v b/test_regress/t/t_clk_latchgate.v index c6ad11ca8..db9dae13d 100644 --- a/test_regress/t/t_clk_latchgate.v +++ b/test_regress/t/t_clk_latchgate.v @@ -19,7 +19,6 @@ `define GATED_CLK_TESTCASE 1 // A side effect of the problem is this warning, disabled by default -//verilator lint_on IMPERFECTSCH // Test Bench module t (/*AUTOARG*/ @@ -153,7 +152,7 @@ module Test (/*AUTOARG*/ output wire [7:0] entry_vld; wire [7:0] gclk_vld; - wire [7:0] ff_en_vld /*verilator clock_enable*/; + wire [7:0] ff_en_vld; reg [7:0] flop_en_vld; always @(posedge clk) flop_en_vld <= ff_en_e1; diff --git a/test_regress/t/t_clk_powerdn.v b/test_regress/t/t_clk_powerdn.v index 067ead2a4..e8dec908f 100644 --- a/test_regress/t/t_clk_powerdn.v +++ b/test_regress/t/t_clk_powerdn.v @@ -13,8 +13,6 @@ module t (/*AUTOARG*/ reg reset_l; - // verilator lint_off GENCLK - /*AUTOWIRE*/ // Beginning of automatic wires (for undeclared instantiated-module outputs) // End of automatics diff --git a/test_regress/t/t_clk_scope_bad.out b/test_regress/t/t_clk_scope_bad.out deleted file mode 100644 index 024049366..000000000 --- a/test_regress/t/t_clk_scope_bad.out +++ /dev/null @@ -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 diff --git a/test_regress/t/t_clk_scope_bad.pl b/test_regress/t/t_clk_scope_bad.pl index 35d749208..cc34dc85f 100755 --- a/test_regress/t/t_clk_scope_bad.pl +++ b/test_regress/t/t_clk_scope_bad.pl @@ -10,10 +10,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(linter => 1); -lint( - fails => 1, - expect_filename => $Self->{golden_filename}, - ); +lint(); ok(1); 1; diff --git a/test_regress/t/t_clocker.pl b/test_regress/t/t_clocker.pl index 46b824b58..1af79252d 100755 --- a/test_regress/t/t_clocker.pl +++ b/test_regress/t/t_clocker.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["--trace", "-Wno-CLKDATA"] + verilator_flags2 => ["--trace"] ); execute( diff --git a/test_regress/t/t_clocker.v b/test_regress/t/t_clocker.v index c71e22c68..7b84db7d8 100644 --- a/test_regress/t/t_clocker.v +++ b/test_regress/t/t_clocker.v @@ -39,9 +39,6 @@ module t (/*AUTOARG*/ assign clk_3 = {3{clk_1}}; assign clk_final = clk_3[0]; - // the following two assignment triggers the CLKDATA warning - // because on LHS there are a mix of signals both CLOCK and - // DATA assign res8 = {clk_3, 1'b0, clk_4}; assign res16 = {count, clk_3, clk_1, clk_4}; @@ -52,8 +49,6 @@ module t (/*AUTOARG*/ always @(posedge clk_final or negedge clk_final) begin count = count + 1; - // the following assignment should trigger the CLKDATA warning - // because CLOCK signal is used as DATA in sequential block res <= clk_final; if ( count == 8'hf) begin $write("*-* All Finished *-*\n"); diff --git a/test_regress/t/t_clocker_bad.out b/test_regress/t/t_clocker_bad.out deleted file mode 100644 index c32ce2551..000000000 --- a/test_regress/t/t_clocker_bad.out +++ /dev/null @@ -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 diff --git a/test_regress/t/t_comb_input_0.cpp b/test_regress/t/t_comb_input_0.cpp new file mode 100644 index 000000000..03b204a64 --- /dev/null +++ b/test_regress/t/t_comb_input_0.cpp @@ -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 + +#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 contextp{new VerilatedContext}; + contextp->commandArgs(argc, argv); + contextp->debug(0); + srand48(5); + + const std::unique_ptr 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; +} diff --git a/test_regress/t/t_comb_input_0.pl b/test_regress/t/t_comb_input_0.pl new file mode 100755 index 000000000..0bb6b8592 --- /dev/null +++ b/test_regress/t/t_comb_input_0.pl @@ -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; diff --git a/test_regress/t/t_comb_input_0.v b/test_regress/t/t_comb_input_0.v new file mode 100644 index 000000000..d66ba66c8 --- /dev/null +++ b/test_regress/t/t_comb_input_0.v @@ -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 diff --git a/test_regress/t/t_comb_input_1.cpp b/test_regress/t/t_comb_input_1.cpp new file mode 100644 index 000000000..c03331c7c --- /dev/null +++ b/test_regress/t/t_comb_input_1.cpp @@ -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 + +#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 contextp{new VerilatedContext}; + contextp->commandArgs(argc, argv); + contextp->debug(0); + srand48(5); + + const std::unique_ptr 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; +} diff --git a/test_regress/t/t_comb_input_1.pl b/test_regress/t/t_comb_input_1.pl new file mode 100755 index 000000000..0bb6b8592 --- /dev/null +++ b/test_regress/t/t_comb_input_1.pl @@ -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; diff --git a/test_regress/t/t_comb_input_1.v b/test_regress/t/t_comb_input_1.v new file mode 100644 index 000000000..0d057b28d --- /dev/null +++ b/test_regress/t/t_comb_input_1.v @@ -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 diff --git a/test_regress/t/t_comb_input_2.cpp b/test_regress/t/t_comb_input_2.cpp new file mode 100644 index 000000000..689991a7d --- /dev/null +++ b/test_regress/t/t_comb_input_2.cpp @@ -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 + +#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 contextp{new VerilatedContext}; + contextp->commandArgs(argc, argv); + contextp->debug(0); + srand48(5); + + const std::unique_ptr 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; +} diff --git a/test_regress/t/t_comb_input_2.pl b/test_regress/t/t_comb_input_2.pl new file mode 100755 index 000000000..0bb6b8592 --- /dev/null +++ b/test_regress/t/t_comb_input_2.pl @@ -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; diff --git a/test_regress/t/t_comb_input_2.v b/test_regress/t/t_comb_input_2.v new file mode 100644 index 000000000..f90d5bb9b --- /dev/null +++ b/test_regress/t/t_comb_input_2.v @@ -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 diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 75c4736a9..749f294de 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -163,7 +163,7 @@ module Vt_debug_emitv_t; $display("stmt"); end end - always @([any] in) begin + always @([changed] in) begin begin $display("stmt"); end diff --git a/test_regress/t/t_dedupe_clk_gate.pl b/test_regress/t/t_dedupe_clk_gate.pl index ccc0816ac..01b1afad8 100755 --- a/test_regress/t/t_dedupe_clk_gate.pl +++ b/test_regress/t/t_dedupe_clk_gate.pl @@ -13,11 +13,11 @@ scenarios(simulator => 1); my $out_filename = "$Self->{obj_dir}/V$Self->{name}.xml"; compile( - verilator_flags2 => ["--stats $Self->{t_dir}/t_dedupe_clk_gate.vlt"], + verilator_flags2 => ["--stats"], ); if ($Self->{vlt_all}) { - file_grep("$out_filename", qr/\/i); + file_grep("$out_filename", qr/\/i); file_grep($Self->{stats}, qr/Optimizations, Gate sigs deduped\s+(\d+)/i, 4); } diff --git a/test_regress/t/t_dedupe_clk_gate.v b/test_regress/t/t_dedupe_clk_gate.v index af6331196..6ba6ff174 100644 --- a/test_regress/t/t_dedupe_clk_gate.v +++ b/test_regress/t/t_dedupe_clk_gate.v @@ -53,7 +53,7 @@ endmodule module clock_gate_flop (gated_clk, clk, clken); output gated_clk; input clk, clken; - reg clken_r /*verilator clock_enable*/; + reg clken_r; assign gated_clk = clk & clken_r ; always @(negedge clk) diff --git a/test_regress/t/t_dedupe_clk_gate.vlt b/test_regress/t/t_dedupe_clk_gate.vlt deleted file mode 100644 index 6e2a77ef9..000000000 --- a/test_regress/t/t_dedupe_clk_gate.vlt +++ /dev/null @@ -1,3 +0,0 @@ -`verilator_config - -clock_enable -module "clock_gate_latch" -var "clken_latched" diff --git a/test_regress/t/t_detectarray_1.pl b/test_regress/t/t_detectarray_1.pl index 82fae48f0..973a951a0 100755 --- a/test_regress/t/t_detectarray_1.pl +++ b/test_regress/t/t_detectarray_1.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-Wno-CLKDATA", "-Wno-UNOPTFLAT"] + verilator_flags2 => ["-Wno-UNOPTFLAT"] ); execute( diff --git a/test_regress/t/t_detectarray_2.pl b/test_regress/t/t_detectarray_2.pl index 82fae48f0..973a951a0 100755 --- a/test_regress/t/t_detectarray_2.pl +++ b/test_regress/t/t_detectarray_2.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-Wno-CLKDATA", "-Wno-UNOPTFLAT"] + verilator_flags2 => ["-Wno-UNOPTFLAT"] ); execute( diff --git a/test_regress/t/t_event.v b/test_regress/t/t_event.v index 9bf376779..566657dd0 100644 --- a/test_regress/t/t_event.v +++ b/test_regress/t/t_event.v @@ -33,49 +33,44 @@ module t(/*AUTOARG*/ always @(e2) begin `WRITE_VERBOSE(("[%0t] e2\n", $time)); + if (!e2.triggered) $stop; last_event[2] = 1; end always @(posedge clk) begin `WRITE_VERBOSE(("[%0t] cyc=%0d last_event=%5b\n", $time, cyc, last_event)); cyc <= cyc + 1; - if (cyc == 1) begin - // Check no initial trigger - if (last_event != 0) $stop; - end - // - else if (cyc == 10) begin - last_event = 0; - -> e1; - end - else if (cyc == 12) begin - if (last_event != 32'b10) $stop; - last_event = 0; - end - else if (cyc == 13) begin - // Check not still triggering - if (last_event != 0) $stop; - last_event = 0; - end - // - else if (cyc == 10) begin - last_event = 0; - ->> e2; - end - else if (cyc == 12) begin - if (last_event != 32'b100) $stop; - last_event = 0; - end - else if (cyc == 13) begin - // Check not still triggering - if (last_event != 0) $stop; - last_event = 0; - end - // - else if (cyc == 99) begin - $write("*-* All Finished *-*\n"); - $finish; - end + case (cyc) + default: begin + // Check no initial or spurious trigger + if (last_event != 0) $stop; + end + // + 10: begin + if (last_event != 0) $stop; + -> e1; + if (!e1.triggered) $stop; + end + 11: begin + if (last_event != 32'b10) $stop; + last_event = 0; + end + // + 13: begin + if (last_event != 0) $stop; + ->> e2; + if (e2.triggered) $stop; + end + 14: begin + if (last_event != 32'b100) $stop; + last_event = 0; + end + // + 99: begin + $write("*-* All Finished *-*\n"); + $finish; + end + endcase end endmodule diff --git a/test_regress/t/t_flag_comp_limit_parens.pl b/test_regress/t/t_flag_comp_limit_parens.pl index 51fb7f932..d978e33fa 100755 --- a/test_regress/t/t_flag_comp_limit_parens.pl +++ b/test_regress/t/t_flag_comp_limit_parens.pl @@ -18,7 +18,8 @@ execute( check_finished => 1, ); -file_grep(glob_one("$Self->{obj_dir}/Vt_flag_comp_limit_parens___024root__DepSet_*__0__Slow.cpp"), qr/Vdeeptemp/x); +my @files = glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet*__Slow.cpp"); +file_grep_any(\@files, qr/Vdeeptemp/i); ok(1); 1; diff --git a/test_regress/t/t_flag_csplit_eval.pl b/test_regress/t/t_flag_csplit_eval.pl index 325f38067..3b44c3193 100755 --- a/test_regress/t/t_flag_csplit_eval.pl +++ b/test_regress/t/t_flag_csplit_eval.pl @@ -15,14 +15,14 @@ sub check_evals { local $/; undef $/; my $wholefile = <$fh>; - if ($wholefile =~ /___eval__[0-9]+\(.*\)\s*{/) { + if ($wholefile =~ /__eval_nba__[0-9]+\(.*\)\s*{/) { ++$got; } } $got >= 2 or error("Too few _eval functions found: $got"); } -scenarios(vlt_all => 1); +scenarios(vlt => 1); compile( v_flags2 => ["--output-split 1 --output-split-cfuncs 20"], diff --git a/test_regress/t/t_flag_xinitial_unique.pl b/test_regress/t/t_flag_xinitial_unique.pl index f2451bd3f..3f6077c00 100755 --- a/test_regress/t/t_flag_xinitial_unique.pl +++ b/test_regress/t/t_flag_xinitial_unique.pl @@ -18,7 +18,8 @@ execute( check_finished => 1, ); -file_grep(glob_one("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet_*__0__Slow.cpp"), qr/VL_RAND_RESET/); +my @files = glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet_*__Slow.cpp"); +file_grep_any(\@files, qr/VL_RAND_RESET/); ok(1); 1; diff --git a/test_regress/t/t_func_check.v b/test_regress/t/t_func_check.v index d0f2a06f2..c35a17347 100644 --- a/test_regress/t/t_func_check.v +++ b/test_regress/t/t_func_check.v @@ -56,6 +56,8 @@ module chk (input clk, input rst_l, input expr); wire noxs = ((expr ^ expr) == 1'b0); + // FIXMEV5: this test is dodgy, noxs can be proven constant, so this block + // should never relly trigger... reg hasx; always @ (noxs) begin if (noxs) begin diff --git a/test_regress/t/t_fuzz_always_bad.out b/test_regress/t/t_fuzz_always_bad.out index 63a43015a..e208e58ba 100644 --- a/test_regress/t/t_fuzz_always_bad.out +++ b/test_regress/t/t_fuzz_always_bad.out @@ -4,8 +4,4 @@ %Error: t/t_fuzz_always_bad.v:10:19: Can't find definition of task/function: 'h' 10 | always @ c.a c:h; | ^ -%Error-UNSUPPORTED: t/t_fuzz_always_bad.v:10:14: Unsupported: Complex statement in sensitivity list - 10 | always @ c.a c:h; - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_gen_forif.pl b/test_regress/t/t_gen_forif.pl index bc8663a80..7c4d3f6e5 100755 --- a/test_regress/t/t_gen_forif.pl +++ b/test_regress/t/t_gen_forif.pl @@ -10,6 +10,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); +# FIXMEV5: fix +skip("This test is racy, needs fixing"); + compile( nc_flags2 => ['+access+r'], ); diff --git a/test_regress/t/t_gen_intdot.pl b/test_regress/t/t_gen_intdot.pl index b46d46042..54e5c830d 100755 --- a/test_regress/t/t_gen_intdot.pl +++ b/test_regress/t/t_gen_intdot.pl @@ -10,6 +10,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); +# FIXMEV5: fix +skip("This test is racy, needs fixing"); + compile( ); diff --git a/test_regress/t/t_gen_upscope.out b/test_regress/t/t_gen_upscope.out index d20955fe2..3765ed4ca 100644 --- a/test_regress/t/t_gen_upscope.out +++ b/test_regress/t/t_gen_upscope.out @@ -1,6 +1,6 @@ +created tag with scope = top.t.tag created tag with scope = top.t.b.gen[0].tag created tag with scope = top.t.b.gen[1].tag -created tag with scope = top.t.tag mod a has scope = top.t mod a has tag = top.t.tag mod b has scope = top.t.b diff --git a/test_regress/t/t_hier_block.v b/test_regress/t/t_hier_block.v index 7c8ee9e15..6e1d37098 100644 --- a/test_regress/t/t_hier_block.v +++ b/test_regress/t/t_hier_block.v @@ -34,13 +34,13 @@ module t (/*AUTOARG*/ `ifdef PROTLIB_TOP secret i_secred(.clk(clk)); `else + /* verilator lint_off UNOPTFLAT */ wire [7:0] out0; wire [7:0] out1; wire [7:0] out2; - /* verilator lint_off UNOPTFLAT */ wire [7:0] out3; - wire [7:0] out3_2; /* verilator lint_on UNOPTFLAT */ + wire [7:0] out3_2; wire [7:0] out5; wire [7:0] out6; int count = 0; diff --git a/test_regress/t/t_inst_ccall.v b/test_regress/t/t_inst_ccall.v index 5741ed46c..63c48d8c9 100644 --- a/test_regress/t/t_inst_ccall.v +++ b/test_regress/t/t_inst_ccall.v @@ -12,7 +12,6 @@ module t (/*AUTOARG*/ input clk; integer cyc; initial cyc=1; - // verilator lint_on GENCLK reg [31:0] long; reg [63:0] quad; wire [31:0] longout; diff --git a/test_regress/t/t_inst_tree.v b/test_regress/t/t_inst_tree.v index 3533d8e76..092ff2c17 100644 --- a/test_regress/t/t_inst_tree.v +++ b/test_regress/t/t_inst_tree.v @@ -12,9 +12,9 @@ module t (/*AUTOARG*/ input clk; integer cyc; initial cyc=1; - // verilator lint_off GENCLK + reg printclk; - // verilator lint_on GENCLK + ps ps (printclk); reg [7:0] a; diff --git a/test_regress/t/t_inst_tree_inl0_pub1.pl b/test_regress/t/t_inst_tree_inl0_pub1.pl index b06051080..a462990c9 100755 --- a/test_regress/t/t_inst_tree_inl0_pub1.pl +++ b/test_regress/t/t_inst_tree_inl0_pub1.pl @@ -42,7 +42,7 @@ if ($Self->{vlt_all}) { # We expect to combine sequent functions across multiple instances of # l2, l3, l4, l5. If this number drops, please confirm this has not broken. file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, - ($Self->{vltmt} ? 84 : 52)); + ($Self->{vltmt} ? 70 : 52)); # Everything should use relative references checkRelativeRefs("t", 1); diff --git a/test_regress/t/t_lib_prot.v b/test_regress/t/t_lib_prot.v index 7da04b3ed..7fa639835 100644 --- a/test_regress/t/t_lib_prot.v +++ b/test_regress/t/t_lib_prot.v @@ -153,7 +153,7 @@ module t #(parameter GATED_CLK = 0) (/*AUTOARG*/ logic possibly_gated_clk; if (GATED_CLK != 0) begin: yes_gated_clock - logic clk_en_latch /*verilator clock_enable*/; + logic clk_en_latch; /* verilator lint_off COMBDLY */ /* verilator lint_off LATCH */ always_comb if (clk == '0) clk_en_latch <= clk_en; diff --git a/test_regress/t/t_lib_prot_secret.v b/test_regress/t/t_lib_prot_secret.v index 7eeb7efca..1ed4dae83 100644 --- a/test_regress/t/t_lib_prot_secret.v +++ b/test_regress/t/t_lib_prot_secret.v @@ -46,7 +46,7 @@ module secret #(parameter GATED_CLK = 0) logic the_clk; generate if (GATED_CLK != 0) begin: yes_gated_clock - logic clk_en_latch /*verilator clock_enable*/; + logic clk_en_latch; /* verilator lint_off COMBDLY */ /* verilator lint_off LATCH */ always_comb if (clk == '0) clk_en_latch <= clk_en; diff --git a/test_regress/t/t_lint_didnotconverge_bad.out b/test_regress/t/t_lint_didnotconverge_bad.out index 1a0bb785a..526a34911 100644 --- a/test_regress/t/t_lint_didnotconverge_bad.out +++ b/test_regress/t/t_lint_didnotconverge_bad.out @@ -1,6 +1,3 @@ --V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. --V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request --V{t#,#}+ Vt_lint_didnotconverge_bad___024root___change_request_1 --V{t#,#} CHANGE: t/t_lint_didnotconverge_bad.v:14: a -%Error: t/t_lint_didnotconverge_bad.v:7: Verilated model didn't converge +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] b) +%Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_lint_didnotconverge_bad.pl b/test_regress/t/t_lint_didnotconverge_bad.pl index 5281eac77..6c153faa9 100755 --- a/test_regress/t/t_lint_didnotconverge_bad.pl +++ b/test_regress/t/t_lint_didnotconverge_bad.pl @@ -27,7 +27,7 @@ extract( extract( in => $Self->{golden_filename}, out => "../docs/gen/ex_DIDNOTCONVERGE_msg.rst", - lines => "2-5"); + lines => "1-2"); ok(1); 1; diff --git a/test_regress/t/t_lint_didnotconverge_bad.v b/test_regress/t/t_lint_didnotconverge_bad.v index 315e91aa5..544db7390 100644 --- a/test_regress/t/t_lint_didnotconverge_bad.v +++ b/test_regress/t/t_lint_didnotconverge_bad.v @@ -9,7 +9,7 @@ module t (/*AUTOARG*/ a, b ); - // verilator lint_off UNOPT + // verilator lint_off UNOPTFLAT output logic a, b; diff --git a/test_regress/t/t_lint_didnotconverge_nodbg_bad.out b/test_regress/t/t_lint_didnotconverge_nodbg_bad.out index 1b59dad18..b6e6359ab 100644 --- a/test_regress/t/t_lint_didnotconverge_nodbg_bad.out +++ b/test_regress/t/t_lint_didnotconverge_nodbg_bad.out @@ -1,2 +1,2 @@ -%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. Aborting... diff --git a/test_regress/t/t_lint_latch_1.out b/test_regress/t/t_lint_latch_1.out deleted file mode 100644 index 22f8aceb5..000000000 --- a/test_regress/t/t_lint_latch_1.out +++ /dev/null @@ -1,9 +0,0 @@ -%Warning-COMBDLY: t/t_lint_latch_1.v:14:10: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 14 | o <= b; - | ^~ - ... For warning description see https://verilator.org/warn/COMBDLY?v=latest - ... Use "/* verilator lint_off COMBDLY */" and lint_on around source to disable this message. - *** See https://verilator.org/warn/COMBDLY before disabling this, - else you may end up with different sim results. -%Error: Exiting due to diff --git a/test_regress/t/t_lint_latch_1.pl b/test_regress/t/t_lint_latch_1.pl index 07964a1b5..629a44bbb 100755 --- a/test_regress/t/t_lint_latch_1.pl +++ b/test_regress/t/t_lint_latch_1.pl @@ -11,8 +11,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); lint( - fails => 1, - expect_filename => $Self->{golden_filename}, ); ok(1); diff --git a/test_regress/t/t_lint_latch_5.pl b/test_regress/t/t_lint_latch_5.pl index 07964a1b5..629a44bbb 100755 --- a/test_regress/t/t_lint_latch_5.pl +++ b/test_regress/t/t_lint_latch_5.pl @@ -11,8 +11,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); lint( - fails => 1, - expect_filename => $Self->{golden_filename}, ); ok(1); diff --git a/test_regress/t/t_lint_latch_5.v b/test_regress/t/t_lint_latch_5.v index 7cf29d05c..dcca92df8 100644 --- a/test_regress/t/t_lint_latch_5.v +++ b/test_regress/t/t_lint_latch_5.v @@ -10,10 +10,10 @@ module test always_latch if (e) - z[0] <= a[0]; + z[0] = a[0]; always_latch if (e) - z[1] <= a[1]; + z[1] = a[1]; endmodule diff --git a/test_regress/t/t_lint_latch_bad_2.out b/test_regress/t/t_lint_latch_bad_2.out index d5fb68aec..29e592071 100644 --- a/test_regress/t/t_lint_latch_bad_2.out +++ b/test_regress/t/t_lint_latch_bad_2.out @@ -1,13 +1,7 @@ -%Warning-COMBDLY: t/t_lint_latch_bad_2.v:13:10: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 13 | o <= b; - | ^~ - ... For warning description see https://verilator.org/warn/COMBDLY?v=latest - ... Use "/* verilator lint_off COMBDLY */" and lint_on around source to disable this message. - *** See https://verilator.org/warn/COMBDLY before disabling this, - else you may end up with different sim results. %Warning-LATCH: t/t_lint_latch_bad_2.v:11:4: Latch inferred for signal 'o' (not all control paths of combinational always assign a value) : ... Suggest use of always_latch for intentional latches - 11 | always @(a or b) - | ^~~~~~ + 11 | always_comb + | ^~~~~~~~~~~ + ... For warning description see https://verilator.org/warn/LATCH?v=latest + ... Use "/* verilator lint_off LATCH */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_lint_latch_bad_2.v b/test_regress/t/t_lint_latch_bad_2.v index e4719d0e0..e738dc23b 100644 --- a/test_regress/t/t_lint_latch_bad_2.v +++ b/test_regress/t/t_lint_latch_bad_2.v @@ -8,8 +8,8 @@ module t (/*AUTOARG*/ a, b, o); input b; output reg o; - always @(a or b) + always_comb if (a) - o <= b; + o = b; endmodule diff --git a/test_regress/t/t_lint_latch_bad_3.out b/test_regress/t/t_lint_latch_bad_3.out index b154019fa..cb89d481a 100644 --- a/test_regress/t/t_lint_latch_bad_3.out +++ b/test_regress/t/t_lint_latch_bad_3.out @@ -1,29 +1,7 @@ -%Warning-COMBDLY: t/t_lint_latch_bad_3.v:25:8: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 25 | o5 <= 1'b0; - | ^~ - ... For warning description see https://verilator.org/warn/COMBDLY?v=latest - ... Use "/* verilator lint_off COMBDLY */" and lint_on around source to disable this message. - *** See https://verilator.org/warn/COMBDLY before disabling this, - else you may end up with different sim results. -%Warning-COMBDLY: t/t_lint_latch_bad_3.v:37:16: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 37 | o5 <= 1'b1; - | ^~ -%Warning-COMBDLY: t/t_lint_latch_bad_3.v:42:16: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 42 | o5 <= a; - | ^~ -%Warning-COMBDLY: t/t_lint_latch_bad_3.v:63:16: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 63 | o5 <= ~b; - | ^~ -%Warning-COMBDLY: t/t_lint_latch_bad_3.v:70:12: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 70 | o4 <= 1'b0; - | ^~ %Warning-LATCH: t/t_lint_latch_bad_3.v:18:1: Latch inferred for signal 'o5' (not all control paths of combinational always assign a value) : ... Suggest use of always_latch for intentional latches - 18 | always @(reset or en or a or b) - | ^~~~~~ + 18 | always_comb + | ^~~~~~~~~~~ + ... For warning description see https://verilator.org/warn/LATCH?v=latest + ... Use "/* verilator lint_off LATCH */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_lint_latch_bad_3.v b/test_regress/t/t_lint_latch_bad_3.v index eb517e0de..83a87b16d 100644 --- a/test_regress/t/t_lint_latch_bad_3.v +++ b/test_regress/t/t_lint_latch_bad_3.v @@ -15,14 +15,14 @@ module t (/*AUTOARG*/ reset, a, b, c, en, o1, o2, o3, o4, o5); output reg o4; // " output reg o5; // Latch -always @(reset or en or a or b) +always_comb if (reset) begin o1 = 1'b0; o2 = 1'b0; o3 = 1'b0; o4 = 1'b0; - o5 <= 1'b0; // Do NOT expect Warning-COMBDLY + o5 = 1'b0; end else begin @@ -34,12 +34,12 @@ begin if (a) begin o3 = a; - o5 <= 1'b1; // Do NOT expect Warning-COMBDLY + o5 = 1'b1; end else begin o3 = ~a; - o5 <= a; // Do NOT expect Warning-COMBDLY + o5 = a; end // o3 is not assigned in either path of this if/else @@ -60,14 +60,14 @@ begin if (b) begin o3 = ~a | b; - o5 <= ~b; // Do NOT expect Warning-COMBDLY + o5 = ~b; end else begin o3 = a & ~b; // No assignment to o5, expect Warning-LATCH end - o4 <= 1'b0; // expect Warning-COMBDLY + o4 = 1'b0; end end diff --git a/test_regress/t/t_lint_nolatch_bad.out b/test_regress/t/t_lint_nolatch_bad.out index 764e77aaa..8a229bd07 100644 --- a/test_regress/t/t_lint_nolatch_bad.out +++ b/test_regress/t/t_lint_nolatch_bad.out @@ -1,12 +1,6 @@ -%Warning-COMBDLY: t/t_lint_nolatch_bad.v:13:10: Non-blocking assignment '<=' in combinational logic process - : ... This will be executed as a blocking assignment '='! - 13 | o <= b; - | ^~ - ... For warning description see https://verilator.org/warn/COMBDLY?v=latest - ... Use "/* verilator lint_off COMBDLY */" and lint_on around source to disable this message. - *** See https://verilator.org/warn/COMBDLY before disabling this, - else you may end up with different sim results. %Warning-NOLATCH: t/t_lint_nolatch_bad.v:11:4: No latches detected in always_latch block - 11 | always_latch @(a or b) + 11 | always_latch | ^~~~~~~~~~~~ + ... For warning description see https://verilator.org/warn/NOLATCH?v=latest + ... Use "/* verilator lint_off NOLATCH */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_lint_nolatch_bad.v b/test_regress/t/t_lint_nolatch_bad.v index 517f57f94..5f88a38c3 100644 --- a/test_regress/t/t_lint_nolatch_bad.v +++ b/test_regress/t/t_lint_nolatch_bad.v @@ -8,10 +8,10 @@ module t (/*AUTOARG*/ a, b, o); input b; output reg o; - always_latch @(a or b) + always_latch if (a) - o <= b; + o = b; else - o <= ~b; + o = ~b; endmodule diff --git a/test_regress/t/t_lint_unsup_mixed.v b/test_regress/t/t_lint_unsup_mixed.v index 9655b48b0..3681f56b3 100644 --- a/test_regress/t/t_lint_unsup_mixed.v +++ b/test_regress/t/t_lint_unsup_mixed.v @@ -31,4 +31,6 @@ module t qb = qb + 1; end + always @(posedge clk) $display("%d", qb); // So qb is not optimized away + endmodule diff --git a/test_regress/t/t_math_shift.pl b/test_regress/t/t_math_shift.pl index 757e26425..b46d46042 100755 --- a/test_regress/t/t_math_shift.pl +++ b/test_regress/t/t_math_shift.pl @@ -11,7 +11,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-Wno-CLKDATA"] ); execute( diff --git a/test_regress/t/t_math_shift_noexpand.pl b/test_regress/t/t_math_shift_noexpand.pl index e8a59556b..acf420f1a 100755 --- a/test_regress/t/t_math_shift_noexpand.pl +++ b/test_regress/t/t_math_shift_noexpand.pl @@ -13,7 +13,7 @@ scenarios(vlt => 1); top_filename("t/t_math_shift.v"); compile( - verilator_flags2 => ["-Wno-CLKDATA", '-Ox'], + verilator_flags2 => ['-Ox'], ); execute( diff --git a/test_regress/t/t_optm_if_cond.pl b/test_regress/t/t_optm_if_cond.pl index b67f09305..91aa0aae5 100755 --- a/test_regress/t/t_optm_if_cond.pl +++ b/test_regress/t/t_optm_if_cond.pl @@ -15,7 +15,7 @@ compile( ); if ($Self->{vlt_all}) { - file_grep($Self->{stats}, qr/Node count, IF +\d+ +\d+ +\d+ +\d+ +(\d+)/, 11); + file_grep($Self->{stats}, qr/Node count, IF +\d+ +\d+ +\d+ +\d+ +(\d+)/, 28); } ok(1); diff --git a/test_regress/t/t_order_clkinst.out b/test_regress/t/t_order_clkinst.out index 98e8e114b..9e56d1ba0 100644 --- a/test_regress/t/t_order_clkinst.out +++ b/test_regress/t/t_order_clkinst.out @@ -62,7 +62,7 @@ b00000010 $ b00000000000000000000000000000011 % b00000000000000000000000000000011 & 1' -b00000000000000000000000000000101 ( +b00000000000000000000000000000011 ( 1/ #25 0/ diff --git a/test_regress/t/t_order_clkinst.v b/test_regress/t/t_order_clkinst.v index 048f5851b..a54fa9397 100644 --- a/test_regress/t/t_order_clkinst.v +++ b/test_regress/t/t_order_clkinst.v @@ -15,6 +15,7 @@ module t (/*AUTOARG*/ // verilator lint_off UNOPT // verilator lint_off UNOPTFLAT // verilator lint_off BLKANDNBLK + // verilator lint_off MULTIDRIVEN reg c1_start; initial c1_start = 0; wire [31:0] c1_count; @@ -49,7 +50,7 @@ module t (/*AUTOARG*/ // should reach the normal '$finish' below on the next cycle. if (c1_count!=32'h3) $stop; if (s2_count!=32'h3) $stop; - if (c3_count!=32'h5) $stop; + if (c3_count!=32'h3) $stop; end 8'd03: begin $write("*-* All Finished *-*\n"); @@ -67,11 +68,12 @@ module comb_loop (/*AUTOARG*/ start ); input start; - output reg [31:0] count; initial count = 0; + output reg [31:0] count = 0; reg [31:0] runnerm1, runner; initial runner = 0; always @ (posedge start) begin + count = 0; runner = 3; end @@ -101,6 +103,7 @@ module seq_loop (/*AUTOARG*/ reg [31:0] runnerm1, runner; initial runner = 0; always @ (posedge start) begin + count = 0; runner <= 3; end diff --git a/test_regress/t/t_order_clkinst_bad.out b/test_regress/t/t_order_clkinst_bad.out deleted file mode 100644 index 843d81b45..000000000 --- a/test_regress/t/t_order_clkinst_bad.out +++ /dev/null @@ -1,24 +0,0 @@ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:19:16: Imperfect scheduling of variable: 't.c1_start' - 19 | reg c1_start; initial c1_start = 0; - | ^~~~~~~~ - ... For warning description see https://verilator.org/warn/IMPERFECTSCH?v=latest - ... Use "/* verilator lint_off IMPERFECTSCH */" and lint_on around source to disable this message. -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:20:16: Imperfect scheduling of variable: 't.c1_count' - 20 | wire [31:0] c1_count; - | ^~~~~~~~ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:24:16: Imperfect scheduling of variable: 't.s2_count' - 24 | wire [31:0] s2_count; - | ^~~~~~~~ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:28:16: Imperfect scheduling of variable: 't.c3_count' - 28 | wire [31:0] c3_count; - | ^~~~~~~~ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:72:28: Imperfect scheduling of variable: 't.c1.runner' - 72 | reg [31:0] runnerm1, runner; initial runner = 0; - | ^~~~~~ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:101:28: Imperfect scheduling of variable: 't.s2.runner' - 101 | reg [31:0] runnerm1, runner; initial runner = 0; - | ^~~~~~ -%Warning-IMPERFECTSCH: t/t_order_clkinst.v:72:28: Imperfect scheduling of variable: 't.c3.runner' - 72 | reg [31:0] runnerm1, runner; initial runner = 0; - | ^~~~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_order_doubleloop.pl b/test_regress/t/t_order_doubleloop.pl index 1f8ac0132..ce1f8586f 100755 --- a/test_regress/t/t_order_doubleloop.pl +++ b/test_regress/t/t_order_doubleloop.pl @@ -10,14 +10,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -my $fail = $Self->{vlt_all}; - compile( ); execute( - check_finished => !$fail, - fails => $fail, + check_finished => 1 ); ok(1); diff --git a/test_regress/t/t_order_loop_bad.pl b/test_regress/t/t_order_loop_bad.pl index 3d2c637a8..c35bc85ef 100755 --- a/test_regress/t/t_order_loop_bad.pl +++ b/test_regress/t/t_order_loop_bad.pl @@ -10,15 +10,10 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); -lint( - fails => 1, - # Can't use expect_filename here as unstable output - expect => -'%Error: Circular logic when ordering code .* - *t/t_order_loop_bad.v:\d+:\d+: + Example path: ALWAYS - *t/t_order_loop_bad.v:\d+:\d+: + Example path: t.ready - *t/t_order_loop_bad.v:\d+:\d+: + Example path: ACTIVE -.*', +compile(); + +execute( + check_finished => 1, ); ok(1); diff --git a/test_regress/t/t_order_wireloop.pl b/test_regress/t/t_order_wireloop.pl index ffee37678..9eef881b4 100755 --- a/test_regress/t/t_order_wireloop.pl +++ b/test_regress/t/t_order_wireloop.pl @@ -16,7 +16,7 @@ compile( # However we no longer gate optimize this # Can't use expect_filename here as unstable output expect => -'%Warning-UNOPT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Feedback to public clock or circular logic: \'bar\' +'%Warning-UNOPTFLAT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Circular combinational logic: \'t.foo\' ', ); diff --git a/test_regress/t/t_protect_ids_key.out b/test_regress/t/t_protect_ids_key.out index 45059c7d7..5f362ac43 100644 --- a/test_regress/t/t_protect_ids_key.out +++ b/test_regress/t/t_protect_ids_key.out @@ -11,7 +11,9 @@ - + + + @@ -20,17 +22,27 @@ + + + + + + + + + - + - - - + + + + diff --git a/test_regress/t/t_clk_condflop_nord.pl b/test_regress/t/t_scheduling_0.pl similarity index 63% rename from test_regress/t/t_clk_condflop_nord.pl rename to test_regress/t/t_scheduling_0.pl index 0c03448c5..07078dca2 100755 --- a/test_regress/t/t_clk_condflop_nord.pl +++ b/test_regress/t/t_scheduling_0.pl @@ -2,8 +2,8 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2003-2009 by Wilson Snyder. This program is free software; you -# can redistribute it and/or modify it under the terms of either the GNU +# 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 @@ -11,11 +11,10 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-no-order-clock-delay"], ); execute( - check_finished => 1 + check_finished => 1, ); ok(1); diff --git a/test_regress/t/t_scheduling_0.v b/test_regress/t/t_scheduling_0.v new file mode 100644 index 000000000..aa54a8aaa --- /dev/null +++ b/test_regress/t/t_scheduling_0.v @@ -0,0 +1,66 @@ +// 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 +); + + input clk; + + // Generate half speed 'clk_half', via non-blocking assignment + reg clk_half = 0; + always @(posedge clk) + clk_half <= ~clk_half; + + // 'clk_half_also' is the same as 'clk_half'. + wire clk_half_also = clk_half & `IMPURE_ONE; + + // Random data updated by full speed clock + reg q = 0; + always @(posedge clk) + q <= ($random % 2 == 1) ? 1'b1 : 1'b0; + + // Flop `q` via `clk_half` + reg a = 0; + always @(posedge clk_half) + a <= q; + + // Flop `q` via `clk_half_also` + reg b = 0; + always @(posedge clk_half_also) + b <= q; + + // Cycle count + reg [31:0] cyc = 0; + + // `a` should always equal `b`, no mater which value they actually capture + always @(posedge clk) begin + if (a !== b) begin + $display("tick %d: a is %1d, b is %1d (q is %1d)", cyc, a, b, q); + $stop; + end + end + + // Just stop condition + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_order_clkinst_bad.pl b/test_regress/t/t_scheduling_1.pl similarity index 56% rename from test_regress/t/t_order_clkinst_bad.pl rename to test_regress/t/t_scheduling_1.pl index 165ed807a..07078dca2 100755 --- a/test_regress/t/t_order_clkinst_bad.pl +++ b/test_regress/t/t_scheduling_1.pl @@ -2,20 +2,19 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2003-2009 by Wilson Snyder. This program is free software; you -# can redistribute it and/or modify it under the terms of either the GNU +# 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(simulator => 1); -top_filename("t/t_order_clkinst.v"); - compile( - v_flags2 => ["-Wwarn-IMPERFECTSCH"], - fails => 1, - expect_filename => $Self->{golden_filename}, + ); + +execute( + check_finished => 1, ); ok(1); diff --git a/test_regress/t/t_scheduling_1.v b/test_regress/t/t_scheduling_1.v new file mode 100644 index 000000000..510754caa --- /dev/null +++ b/test_regress/t/t_scheduling_1.v @@ -0,0 +1,47 @@ +// 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 +); + + input clk; + + // Generate half speed 'clk_half', via blocking assignment + reg clk_half = 0; + always @(posedge clk) + clk_half = ~clk_half; + + // Cycle count (+ stop condition) + reg [31:0] cyc = 0; + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + // Flop cycle count via `clk` + reg [31:0] a = 0; + always @(posedge clk) + a <= cyc; + + // Flop cycle count via `clk_half`, on both edges + reg [31:0] b = 0; + always @(posedge clk_half or negedge clk_half) + b <= cyc; + + // `a` should always equal `b`, no mater which value they actually capture + always @(posedge clk) begin + if (a !== b) begin + $display("tick %d: a is %x, b is %x", cyc, a, b); + $stop; + end + end +endmodule diff --git a/test_regress/t/t_clocker_bad.pl b/test_regress/t/t_scheduling_2.pl similarity index 55% rename from test_regress/t/t_clocker_bad.pl rename to test_regress/t/t_scheduling_2.pl index 0628b9d18..07078dca2 100755 --- a/test_regress/t/t_clocker_bad.pl +++ b/test_regress/t/t_scheduling_2.pl @@ -2,19 +2,19 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2004 by Wilson Snyder. This program is free software; you -# can redistribute it and/or modify it under the terms of either the GNU +# 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 => 1); +scenarios(simulator => 1); -top_filename("t/t_clocker.v"); +compile( + ); -lint( - fails => 1, - expect_filename => $Self->{golden_filename}, +execute( + check_finished => 1, ); ok(1); diff --git a/test_regress/t/t_scheduling_2.v b/test_regress/t/t_scheduling_2.v new file mode 100644 index 000000000..9ed55461c --- /dev/null +++ b/test_regress/t/t_scheduling_2.v @@ -0,0 +1,47 @@ +// 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 +); + + input clk; + + reg clk_half = 0; + + reg [31:0] cyc = 0; + reg [31:0] a, b, c; + + always @(posedge clk) begin + $display("tick %d: a: %d, b: %d, c: %d", cyc, a, b, c); + // Check invariant + if (a !== cyc + 1) $stop; + if (b !== cyc + 2) $stop; + if (c !== cyc + 2) $stop; + // End of test + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + cyc <= cyc + 1; + end + + always @(clk) a = cyc + `IMPURE_ONE; + always @(a) b = a + `IMPURE_ONE; + assign c = a + `IMPURE_ONE; + +endmodule diff --git a/test_regress/t/t_scheduling_3.pl b/test_regress/t/t_scheduling_3.pl new file mode 100755 index 000000000..07078dca2 --- /dev/null +++ b/test_regress/t/t_scheduling_3.pl @@ -0,0 +1,21 @@ +#!/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(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_scheduling_3.v b/test_regress/t/t_scheduling_3.v new file mode 100644 index 000000000..ad12dc8ab --- /dev/null +++ b/test_regress/t/t_scheduling_3.v @@ -0,0 +1,47 @@ +// 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 +); + + input clk; + + reg clk_half = 0; + + reg [31:0] cyc = 0; + reg [31:0] a, b, c; + + always @(posedge clk) begin + $display("tick %d: a: %d, b: %d, c: %d", cyc, a, b, c); + // Check invariant + if (a !== cyc + 1) $stop; + if (b !== cyc + 2) $stop; + if (c !== cyc + 2) $stop; + // End of test + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + cyc <= cyc + 1; + end + + always @(a) b = a + `IMPURE_ONE; + always @(cyc) a = cyc + `IMPURE_ONE; + assign c = a + `IMPURE_ONE; + +endmodule diff --git a/test_regress/t/t_scheduling_4.pl b/test_regress/t/t_scheduling_4.pl new file mode 100755 index 000000000..07078dca2 --- /dev/null +++ b/test_regress/t/t_scheduling_4.pl @@ -0,0 +1,21 @@ +#!/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(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_scheduling_4.v b/test_regress/t/t_scheduling_4.v new file mode 100644 index 000000000..6b77f6bb9 --- /dev/null +++ b/test_regress/t/t_scheduling_4.v @@ -0,0 +1,49 @@ +// 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 +); + + input clk; + + reg clk_half = 0; + + reg [31:0] cyc = 0; + reg [31:0] a = 1, b = 2, c = 2; + + always @(posedge clk) begin + $display("tick %d: a: %d, b: %d, c: %d", cyc, a, b, c); + // Check invariant + if (cyc > 0) begin + if (a !== cyc + 1) $stop; + if (b !== cyc + 2) $stop; + if (c !== cyc + 2) $stop; + end + // End of test + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + cyc <= cyc + 1; + end + + always @(edge cyc[0]) a = cyc + `IMPURE_ONE; + always @(edge a[0]) b = a + `IMPURE_ONE; + assign c = a + `IMPURE_ONE; + +endmodule diff --git a/test_regress/t/t_scheduling_5.pl b/test_regress/t/t_scheduling_5.pl new file mode 100755 index 000000000..c485f7c23 --- /dev/null +++ b/test_regress/t/t_scheduling_5.pl @@ -0,0 +1,22 @@ +#!/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(simulator => 1); + +compile( + verilator_flags2 => ["-Wno-MULTIDRIVEN"] + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_scheduling_5.v b/test_regress/t/t_scheduling_5.v new file mode 100644 index 000000000..6ca049d70 --- /dev/null +++ b/test_regress/t/t_scheduling_5.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2003 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + reg start = 0; + reg [31:0] count; + reg [31:0] runner = 0; + + always @ (posedge start) count = 0; + always @ (posedge start) runner = 3; + + always @ (runner) begin + if (runner > 0) begin + $display("count=%d runner=%d",count, runner); + count = count + 1; + runner = runner - 1;; + end + end + + reg [7:0] cyc = 0; + always @ (posedge clk) begin + cyc <= cyc + 8'd1; + case (cyc) + 8'd00: start <= 1'b0; + 8'd01: start <= 1'b1; + 8'd02: begin + $display("Final count=%d", count); + if (count!=32'h3) $stop; + end + default: begin + $write("*-* All Finished *-*\n"); + $finish; + end + endcase + end +endmodule diff --git a/test_regress/t/t_scheduling_6.v b/test_regress/t/t_scheduling_6.v new file mode 100644 index 000000000..cfb4f52e4 --- /dev/null +++ b/test_regress/t/t_scheduling_6.v @@ -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 +); + + input clk; + + reg clk_half = 0; + + reg [31:0] cyc = 0; + reg [31:0] a, b, c; + + always @(posedge clk) begin + $display("tick %d: a: %d, b: %d, c: %d", cyc, a, b, c); + // Check invariant + if (cyc + 1 !== a) $stop; + if (cyc + 2 !== b) $stop; + if (cyc + 2 !== c) $stop; + // End of test + if (cyc == 100) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + cyc <= cyc + 1; + end + + always @(posedge clk) a = cyc + $c(1); + always @(a) b = a + $c(1); + assign c = a + $c(1); + +endmodule diff --git a/test_regress/t/t_sys_rand_concat.pl b/test_regress/t/t_sys_rand_concat.pl index 62d68c9f8..7eb1032d5 100755 --- a/test_regress/t/t_sys_rand_concat.pl +++ b/test_regress/t/t_sys_rand_concat.pl @@ -17,7 +17,9 @@ execute( check_finished => 1, ); -file_grep_not(glob_one("$Self->{obj_dir}/Vt_sys_rand_concat___024root__DepSet_*__0__Slow.cpp"), qr/(<<|>>)/x); +for my $file (glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}___024root__DepSet*__Slow.cpp")) { + file_grep_not($file, qr/(<<|>>)/x); +} ok(1); 1; diff --git a/test_regress/t/t_trace_complex.out b/test_regress/t/t_trace_complex.out index 62c142c2b..0bff7fdc8 100644 --- a/test_regress/t/t_trace_complex.out +++ b/test_regress/t/t_trace_complex.out @@ -1,56 +1,56 @@ $version Generated by VerilatedVcd $end -$date Wed Aug 11 12:40:46 2021 $end +$date Sun May 8 19:00:11 2022 $end $timescale 1ps $end $scope module top $end - $var wire 1 = clk $end + $var wire 1 $ clk $end $scope module $unit $end $var wire 1 # global_bit $end $upscope $end $scope module t $end $var wire 1 G LONGSTART_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_LONGEND $end - $var wire 1 = clk $end - $var wire 32 $ cyc [31:0] $end + $var wire 1 $ clk $end + $var wire 32 % cyc [31:0] $end $var wire 8 E unpacked_array[-1] [7:0] $end $var wire 8 D unpacked_array[-2] [7:0] $end $var wire 8 F unpacked_array[0] [7:0] $end - $var real 64 1 v_arr_real[0] $end - $var real 64 3 v_arr_real[1] $end - $var wire 2 ( v_arrp [2:1] $end - $var wire 4 ) v_arrp_arrp [3:0] $end - $var wire 4 * v_arrp_strp [3:0] $end + $var real 64 2 v_arr_real[0] $end + $var real 64 4 v_arr_real[1] $end + $var wire 2 ) v_arrp [2:1] $end + $var wire 4 * v_arrp_arrp [3:0] $end + $var wire 4 + v_arrp_strp [3:0] $end $var wire 1 > v_arru[1] $end $var wire 1 ? v_arru[2] $end - $var wire 2 + v_arru_arrp[3] [2:1] $end - $var wire 2 , v_arru_arrp[4] [2:1] $end + $var wire 2 , v_arru_arrp[3] [2:1] $end + $var wire 2 - v_arru_arrp[4] [2:1] $end $var wire 1 @ v_arru_arru[3][1] $end $var wire 1 A v_arru_arru[3][2] $end $var wire 1 B v_arru_arru[4][1] $end $var wire 1 C v_arru_arru[4][2] $end - $var wire 2 - v_arru_strp[3] [1:0] $end - $var wire 2 . v_arru_strp[4] [1:0] $end - $var wire 3 9 v_enumb [2:0] $end - $var wire 6 : v_enumb2_str [5:0] $end - $var wire 32 7 v_enumed [31:0] $end - $var wire 32 8 v_enumed2 [31:0] $end - $var real 64 / v_real $end - $var wire 64 5 v_str32x2 [63:0] $end - $var wire 2 % v_strp [1:0] $end - $var wire 4 & v_strp_strp [3:0] $end - $var wire 2 ' v_unip_strp [1:0] $end + $var wire 2 . v_arru_strp[3] [1:0] $end + $var wire 2 / v_arru_strp[4] [1:0] $end + $var wire 3 : v_enumb [2:0] $end + $var wire 6 ; v_enumb2_str [5:0] $end + $var wire 32 8 v_enumed [31:0] $end + $var wire 32 9 v_enumed2 [31:0] $end + $var real 64 0 v_real $end + $var wire 64 6 v_str32x2 [63:0] $end + $var wire 2 & v_strp [1:0] $end + $var wire 4 ' v_strp_strp [3:0] $end + $var wire 2 ( v_unip_strp [1:0] $end $scope module a_module_instantiation_with_a_very_long_name_that_once_its_signals_get_concatenated_and_inlined_will_almost_certainly_result_in_them_getting_hashed $end - $var wire 32 J PARAM [31:0] $end - $upscope $end - $scope module p2 $end $var wire 32 H PARAM [31:0] $end $upscope $end - $scope module p3 $end + $scope module p2 $end $var wire 32 I PARAM [31:0] $end $upscope $end + $scope module p3 $end + $var wire 32 J PARAM [31:0] $end + $upscope $end $scope module unnamedblk1 $end - $var wire 32 ; b [31:0] $end + $var wire 32 < b [31:0] $end $scope module unnamedblk2 $end - $var wire 32 < a [31:0] $end + $var wire 32 = a [31:0] $end $upscope $end $upscope $end $upscope $end @@ -60,28 +60,28 @@ $enddefinitions $end #0 1# -b00000000000000000000000000000000 $ -b00 % -b0000 & -b00 ' +0$ +b00000000000000000000000000000000 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0 / -r0 1 -r0 3 -b0000000000000000000000000000000000000000000000000000000011111111 5 -b00000000000000000000000000000000 7 +b00 / +r0 0 +r0 2 +r0 4 +b0000000000000000000000000000000000000000000000000000000011111111 6 b00000000000000000000000000000000 8 -b000 9 -b000000 : -b00000000000000000000000000000000 ; +b00000000000000000000000000000000 9 +b000 : +b000000 ; b00000000000000000000000000000000 < -0= +b00000000000000000000000000000000 = 0> 0? 0@ @@ -92,143 +92,143 @@ b00000000 D b00000000 E b00000000 F 0G -b00000000000000000000000000000010 H -b00000000000000000000000000000011 I -b00000000000000000000000000000100 J +b00000000000000000000000000000100 H +b00000000000000000000000000000010 I +b00000000000000000000000000000011 J #10 -b00000000000000000000000000000001 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000001 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.1 / -r0.2 1 -r0.3 3 -b0000000000000000000000000000000100000000000000000000000011111110 5 -b00000000000000000000000000000001 7 -b00000000000000000000000000000010 8 -b111 9 -b00000000000000000000000000000101 ; +b11 / +r0.1 0 +r0.2 2 +r0.3 4 +b0000000000000000000000000000000100000000000000000000000011111110 6 +b00000000000000000000000000000001 8 +b00000000000000000000000000000010 9 +b111 : b00000000000000000000000000000101 < -1= +b00000000000000000000000000000101 = #15 -0= +0$ #20 -b00000000000000000000000000000010 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000010 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.2 / -r0.4 1 -r0.6 3 -b0000000000000000000000000000001000000000000000000000000011111101 5 -b00000000000000000000000000000010 7 -b00000000000000000000000000000100 8 -b110 9 -b111111 : -1= +b00 / +r0.2 0 +r0.4 2 +r0.6 4 +b0000000000000000000000000000001000000000000000000000000011111101 6 +b00000000000000000000000000000010 8 +b00000000000000000000000000000100 9 +b110 : +b111111 ; #25 -0= +0$ #30 -b00000000000000000000000000000011 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000011 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.3 / -r0.6000000000000001 1 -r0.8999999999999999 3 -b0000000000000000000000000000001100000000000000000000000011111100 5 -b00000000000000000000000000000011 7 -b00000000000000000000000000000110 8 -b101 9 -b110110 : -1= +b11 / +r0.3 0 +r0.6000000000000001 2 +r0.8999999999999999 4 +b0000000000000000000000000000001100000000000000000000000011111100 6 +b00000000000000000000000000000011 8 +b00000000000000000000000000000110 9 +b101 : +b110110 ; #35 -0= +0$ #40 -b00000000000000000000000000000100 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000100 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.4 / -r0.8 1 -r1.2 3 -b0000000000000000000000000000010000000000000000000000000011111011 5 -b00000000000000000000000000000100 7 -b00000000000000000000000000001000 8 -b100 9 -b101101 : -1= +b00 / +r0.4 0 +r0.8 2 +r1.2 4 +b0000000000000000000000000000010000000000000000000000000011111011 6 +b00000000000000000000000000000100 8 +b00000000000000000000000000001000 9 +b100 : +b101101 ; #45 -0= +0$ #50 -b00000000000000000000000000000101 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000101 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.5 / -r1 1 -r1.5 3 -b0000000000000000000000000000010100000000000000000000000011111010 5 -b00000000000000000000000000000101 7 -b00000000000000000000000000001010 8 -b011 9 -b100100 : -1= +b11 / +r0.5 0 +r1 2 +r1.5 4 +b0000000000000000000000000000010100000000000000000000000011111010 6 +b00000000000000000000000000000101 8 +b00000000000000000000000000001010 9 +b011 : +b100100 ; #55 -0= +0$ #60 -b00000000000000000000000000000110 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000110 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.6 / -r1.2 1 -r1.8 3 -b0000000000000000000000000000011000000000000000000000000011111001 5 -b00000000000000000000000000000110 7 -b00000000000000000000000000001100 8 -b010 9 -b011011 : -1= +b00 / +r0.6 0 +r1.2 2 +r1.8 4 +b0000000000000000000000000000011000000000000000000000000011111001 6 +b00000000000000000000000000000110 8 +b00000000000000000000000000001100 9 +b010 : +b011011 ; diff --git a/test_regress/t/t_trace_complex_params.out b/test_regress/t/t_trace_complex_params.out index 4fd754aed..96cca0314 100644 --- a/test_regress/t/t_trace_complex_params.out +++ b/test_regress/t/t_trace_complex_params.out @@ -1,56 +1,56 @@ $version Generated by VerilatedVcd $end -$date Wed Aug 11 12:41:11 2021 $end +$date Sun May 8 19:00:32 2022 $end $timescale 1ps $end $scope module top $end - $var wire 1 = clk $end + $var wire 1 $ clk $end $scope module $unit $end $var wire 1 # global_bit $end $upscope $end $scope module t $end $var wire 1 G LONGSTART_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_a_very_long_name_which_will_get_hashed_LONGEND $end - $var wire 1 = clk $end - $var wire 32 $ cyc [31:0] $end + $var wire 1 $ clk $end + $var wire 32 % cyc [31:0] $end $var wire 8 E unpacked_array[-1] [7:0] $end $var wire 8 D unpacked_array[-2] [7:0] $end $var wire 8 F unpacked_array[0] [7:0] $end - $var real 64 1 v_arr_real[0] $end - $var real 64 3 v_arr_real[1] $end - $var wire 2 ( v_arrp [2:1] $end - $var wire 4 ) v_arrp_arrp [3:0] $end - $var wire 4 * v_arrp_strp [3:0] $end + $var real 64 2 v_arr_real[0] $end + $var real 64 4 v_arr_real[1] $end + $var wire 2 ) v_arrp [2:1] $end + $var wire 4 * v_arrp_arrp [3:0] $end + $var wire 4 + v_arrp_strp [3:0] $end $var wire 1 > v_arru[1] $end $var wire 1 ? v_arru[2] $end - $var wire 2 + v_arru_arrp[3] [2:1] $end - $var wire 2 , v_arru_arrp[4] [2:1] $end + $var wire 2 , v_arru_arrp[3] [2:1] $end + $var wire 2 - v_arru_arrp[4] [2:1] $end $var wire 1 @ v_arru_arru[3][1] $end $var wire 1 A v_arru_arru[3][2] $end $var wire 1 B v_arru_arru[4][1] $end $var wire 1 C v_arru_arru[4][2] $end - $var wire 2 - v_arru_strp[3] [1:0] $end - $var wire 2 . v_arru_strp[4] [1:0] $end - $var wire 3 9 v_enumb [2:0] $end - $var wire 6 : v_enumb2_str [5:0] $end - $var wire 32 7 v_enumed [31:0] $end - $var wire 32 8 v_enumed2 [31:0] $end - $var real 64 / v_real $end - $var wire 64 5 v_str32x2 [63:0] $end - $var wire 2 % v_strp [1:0] $end - $var wire 4 & v_strp_strp [3:0] $end - $var wire 2 ' v_unip_strp [1:0] $end + $var wire 2 . v_arru_strp[3] [1:0] $end + $var wire 2 / v_arru_strp[4] [1:0] $end + $var wire 3 : v_enumb [2:0] $end + $var wire 6 ; v_enumb2_str [5:0] $end + $var wire 32 8 v_enumed [31:0] $end + $var wire 32 9 v_enumed2 [31:0] $end + $var real 64 0 v_real $end + $var wire 64 6 v_str32x2 [63:0] $end + $var wire 2 & v_strp [1:0] $end + $var wire 4 ' v_strp_strp [3:0] $end + $var wire 2 ( v_unip_strp [1:0] $end $scope module a_module_instantiation_with_a_very_long_name_that_once_its_signals_get_concatenated_and_inlined_will_almost_certainly_result_in_them_getting_hashed $end - $var wire 32 J PARAM [31:0] $end - $upscope $end - $scope module p2 $end $var wire 32 H PARAM [31:0] $end $upscope $end - $scope module p3 $end + $scope module p2 $end $var wire 32 I PARAM [31:0] $end $upscope $end + $scope module p3 $end + $var wire 32 J PARAM [31:0] $end + $upscope $end $scope module unnamedblk1 $end - $var wire 32 ; b [31:0] $end + $var wire 32 < b [31:0] $end $scope module unnamedblk2 $end - $var wire 32 < a [31:0] $end + $var wire 32 = a [31:0] $end $upscope $end $upscope $end $upscope $end @@ -60,28 +60,28 @@ $enddefinitions $end #0 1# -b00000000000000000000000000000000 $ -b00 % -b0000 & -b00 ' +0$ +b00000000000000000000000000000000 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0 / -r0 1 -r0 3 -b0000000000000000000000000000000000000000000000000000000011111111 5 -b00000000000000000000000000000000 7 +b00 / +r0 0 +r0 2 +r0 4 +b0000000000000000000000000000000000000000000000000000000011111111 6 b00000000000000000000000000000000 8 -b000 9 -b000000 : -b00000000000000000000000000000000 ; +b00000000000000000000000000000000 9 +b000 : +b000000 ; b00000000000000000000000000000000 < -0= +b00000000000000000000000000000000 = 0> 0? 0@ @@ -92,143 +92,143 @@ b00000000 D b00000000 E b00000000 F 0G -b00000000000000000000000000000010 H -b00000000000000000000000000000011 I -b00000000000000000000000000000100 J +b00000000000000000000000000000100 H +b00000000000000000000000000000010 I +b00000000000000000000000000000011 J #10 -b00000000000000000000000000000001 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000001 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.1 / -r0.2 1 -r0.3 3 -b0000000000000000000000000000000100000000000000000000000011111110 5 -b00000000000000000000000000000001 7 -b00000000000000000000000000000010 8 -b111 9 -b00000000000000000000000000000101 ; +b11 / +r0.1 0 +r0.2 2 +r0.3 4 +b0000000000000000000000000000000100000000000000000000000011111110 6 +b00000000000000000000000000000001 8 +b00000000000000000000000000000010 9 +b111 : b00000000000000000000000000000101 < -1= +b00000000000000000000000000000101 = #15 -0= +0$ #20 -b00000000000000000000000000000010 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000010 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.2 / -r0.4 1 -r0.6 3 -b0000000000000000000000000000001000000000000000000000000011111101 5 -b00000000000000000000000000000010 7 -b00000000000000000000000000000100 8 -b110 9 -b111111 : -1= +b00 / +r0.2 0 +r0.4 2 +r0.6 4 +b0000000000000000000000000000001000000000000000000000000011111101 6 +b00000000000000000000000000000010 8 +b00000000000000000000000000000100 9 +b110 : +b111111 ; #25 -0= +0$ #30 -b00000000000000000000000000000011 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000011 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.3 / -r0.6000000000000001 1 -r0.8999999999999999 3 -b0000000000000000000000000000001100000000000000000000000011111100 5 -b00000000000000000000000000000011 7 -b00000000000000000000000000000110 8 -b101 9 -b110110 : -1= +b11 / +r0.3 0 +r0.6000000000000001 2 +r0.8999999999999999 4 +b0000000000000000000000000000001100000000000000000000000011111100 6 +b00000000000000000000000000000011 8 +b00000000000000000000000000000110 9 +b101 : +b110110 ; #35 -0= +0$ #40 -b00000000000000000000000000000100 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000100 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.4 / -r0.8 1 -r1.2 3 -b0000000000000000000000000000010000000000000000000000000011111011 5 -b00000000000000000000000000000100 7 -b00000000000000000000000000001000 8 -b100 9 -b101101 : -1= +b00 / +r0.4 0 +r0.8 2 +r1.2 4 +b0000000000000000000000000000010000000000000000000000000011111011 6 +b00000000000000000000000000000100 8 +b00000000000000000000000000001000 9 +b100 : +b101101 ; #45 -0= +0$ #50 -b00000000000000000000000000000101 $ -b11 % -b1111 & -b11 ' +1$ +b00000000000000000000000000000101 % +b11 & +b1111 ' b11 ( -b1111 ) +b11 ) b1111 * -b11 + +b1111 + b11 , b11 - b11 . -r0.5 / -r1 1 -r1.5 3 -b0000000000000000000000000000010100000000000000000000000011111010 5 -b00000000000000000000000000000101 7 -b00000000000000000000000000001010 8 -b011 9 -b100100 : -1= +b11 / +r0.5 0 +r1 2 +r1.5 4 +b0000000000000000000000000000010100000000000000000000000011111010 6 +b00000000000000000000000000000101 8 +b00000000000000000000000000001010 9 +b011 : +b100100 ; #55 -0= +0$ #60 -b00000000000000000000000000000110 $ -b00 % -b0000 & -b00 ' +1$ +b00000000000000000000000000000110 % +b00 & +b0000 ' b00 ( -b0000 ) +b00 ) b0000 * -b00 + +b0000 + b00 , b00 - b00 . -r0.6 / -r1.2 1 -r1.8 3 -b0000000000000000000000000000011000000000000000000000000011111001 5 -b00000000000000000000000000000110 7 -b00000000000000000000000000001100 8 -b010 9 -b011011 : -1= +b00 / +r0.6 0 +r1.2 2 +r1.8 4 +b0000000000000000000000000000011000000000000000000000000011111001 6 +b00000000000000000000000000000110 8 +b00000000000000000000000000001100 9 +b010 : +b011011 ; diff --git a/test_regress/t/t_unopt_combo.vlt b/test_regress/t/t_unopt_combo.vlt index ffec0fb7a..21286681d 100644 --- a/test_regress/t/t_unopt_combo.vlt +++ b/test_regress/t/t_unopt_combo.vlt @@ -1,3 +1,3 @@ `verilator_config -lint_off -rule UNOPTFLAT -file "*t_unopt_combo.v" -match "Signal unoptimizable: Feedback to clock or circular logic: *" +lint_off -rule UNOPTFLAT -file "*t_unopt_combo.v" -match "Signal unoptimizable: Circular combinational logic: *" diff --git a/test_regress/t/t_unopt_combo_bad.out b/test_regress/t/t_unopt_combo_bad.out index 4e0c5f49b..8cd58f59f 100644 --- a/test_regress/t/t_unopt_combo_bad.out +++ b/test_regress/t/t_unopt_combo_bad.out @@ -1,11 +1,11 @@ -%Warning-UNOPTFLAT: t/t_unopt_combo.v:24:25: Signal unoptimizable: Feedback to clock or circular logic: 't.c' - 24 | wire [31:0] c; +%Warning-UNOPTFLAT: t/t_unopt_combo.v:23:25: Signal unoptimizable: Circular combinational logic: 't.b' + 23 | wire [31:0] b; | ^ ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest ... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message. - t/t_unopt_combo.v:24:25: Example path: t.c - t/t_unopt_combo.v:81:4: Example path: ALWAYS t/t_unopt_combo.v:23:25: Example path: t.b t/t_unopt_combo.v:124:4: Example path: ALWAYS t/t_unopt_combo.v:24:25: Example path: t.c + t/t_unopt_combo.v:81:4: Example path: ALWAYS + t/t_unopt_combo.v:23:25: Example path: t.b %Error: Exiting due to diff --git a/test_regress/t/t_unopt_converge_initial_run_bad.out b/test_regress/t/t_unopt_converge_initial_run_bad.out index a410304f6..8ea2e3603 100644 --- a/test_regress/t/t_unopt_converge_initial_run_bad.out +++ b/test_regress/t/t_unopt_converge_initial_run_bad.out @@ -1,5 +1,3 @@ --V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. --V{t#,#}+ Vt_unopt_converge_initial_run_bad___024root___change_request --V{t#,#} CHANGE: t/t_unopt_converge_initial.v:19: x -%Error: t/t_unopt_converge_initial.v:7: Verilated model didn't DC converge +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] x) +%Error: t/t_unopt_converge_initial.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_unopt_converge_ndbg_bad.out b/test_regress/t/t_unopt_converge_ndbg_bad.out index c54c40377..d3d5894d8 100644 --- a/test_regress/t/t_unopt_converge_ndbg_bad.out +++ b/test_regress/t/t_unopt_converge_ndbg_bad.out @@ -1,2 +1,2 @@ -%Error: t/t_unopt_converge.v:7: Verilated model didn't converge +%Error: t/t_unopt_converge.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_unopt_converge_print_bad.out b/test_regress/t/t_unopt_converge_print_bad.out index 800401d79..ba694a255 100644 --- a/test_regress/t/t_unopt_converge_print_bad.out +++ b/test_regress/t/t_unopt_converge_print_bad.out @@ -1,5 +1,3 @@ --V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. --V{t#,#}+ Vt_unopt_converge_print_bad___024root___change_request --V{t#,#} CHANGE: t/t_unopt_converge.v:19: x -%Error: t/t_unopt_converge.v:7: Verilated model didn't converge +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] x) +%Error: t/t_unopt_converge.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_unopt_converge_run_bad.out b/test_regress/t/t_unopt_converge_run_bad.out index 123a3e718..ba694a255 100644 --- a/test_regress/t/t_unopt_converge_run_bad.out +++ b/test_regress/t/t_unopt_converge_run_bad.out @@ -1,5 +1,3 @@ --V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. --V{t#,#}+ Vt_unopt_converge_run_bad___024root___change_request --V{t#,#} CHANGE: t/t_unopt_converge.v:19: x -%Error: t/t_unopt_converge.v:7: Verilated model didn't converge +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] x) +%Error: t/t_unopt_converge.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_unopt_converge_unopt_bad.out b/test_regress/t/t_unopt_converge_unopt_bad.out index 41c20b620..ad410cbb3 100644 --- a/test_regress/t/t_unopt_converge_unopt_bad.out +++ b/test_regress/t/t_unopt_converge_unopt_bad.out @@ -1,9 +1,9 @@ -%Warning-UNOPT: t/t_unopt_converge.v:19:11: Signal unoptimizable: Feedback to public clock or circular logic: 'x' +%Warning-UNOPTFLAT: t/t_unopt_converge.v:19:11: Signal unoptimizable: Circular combinational logic: 'x' 19 | output x; | ^ - ... For warning description see https://verilator.org/warn/UNOPT?v=latest - ... Use "/* verilator lint_off UNOPT */" and lint_on around source to disable this message. - t/t_unopt_converge.v:19:11: Example path: x - t/t_unopt_converge.v:22:4: Example path: ALWAYS - t/t_unopt_converge.v:19:11: Example path: x + ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest + ... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message. + t/t_unopt_converge.v:19:11: Example path: x + t/t_unopt_converge.v:22:4: Example path: ALWAYS + t/t_unopt_converge.v:19:11: Example path: x %Error: Exiting due to diff --git a/test_regress/t/t_unoptflat_simple_2_bad.out b/test_regress/t/t_unoptflat_simple_2_bad.out index 15d38a892..6ec899e69 100644 --- a/test_regress/t/t_unoptflat_simple_2_bad.out +++ b/test_regress/t/t_unoptflat_simple_2_bad.out @@ -1,4 +1,4 @@ -%Warning-UNOPTFLAT: t/t_unoptflat_simple_2.v:15:15: Signal unoptimizable: Feedback to clock or circular logic: 't.x' +%Warning-UNOPTFLAT: t/t_unoptflat_simple_2.v:15:15: Signal unoptimizable: Circular combinational logic: 't.x' 15 | wire [2:0] x; | ^ ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest @@ -6,9 +6,9 @@ t/t_unoptflat_simple_2.v:15:15: Example path: t.x t/t_unoptflat_simple_2.v:17:18: Example path: ASSIGNW t/t_unoptflat_simple_2.v:15:15: Example path: t.x - ... Widest candidate vars to split: - t/t_unoptflat_simple_2.v:15:15: t.x, width 3, fanout 10, can split_var - ... Most fanned out candidate vars to split: - t/t_unoptflat_simple_2.v:15:15: t.x, width 3, fanout 10, can split_var + ... Widest variables candidate to splitting: + t/t_unoptflat_simple_2.v:15:15: t.x, width 3, circular fanout 2, can split_var + ... Candidates with the highest fanout: + t/t_unoptflat_simple_2.v:15:15: t.x, width 3, circular fanout 2, can split_var ... Suggest add /*verilator split_var*/ to appropriate variables above. %Error: Exiting due to diff --git a/test_regress/t/t_var_pinsizes.v b/test_regress/t/t_var_pinsizes.v index d0e9e26bc..335f0b097 100644 --- a/test_regress/t/t_var_pinsizes.v +++ b/test_regress/t/t_var_pinsizes.v @@ -5,7 +5,6 @@ // SPDX-License-Identifier: CC0-1.0 // Also check that SystemC is ordering properly -// verilator lint_on IMPERFECTSCH module t (/*AUTOARG*/ // Outputs diff --git a/test_regress/t/t_verilated_debug.out b/test_regress/t/t_verilated_debug.out index 9cbfc96c8..4227ea526 100644 --- a/test_regress/t/t_verilated_debug.out +++ b/test_regress/t/t_verilated_debug.out @@ -7,18 +7,32 @@ internalsDump: -V{t#,#}+++++TOP Evaluate Vt_verilated_debug::eval_step -V{t#,#}+ Vt_verilated_debug___024root___eval_debug_assertions +-V{t#,#}+ Initial +-V{t#,#}+ Vt_verilated_debug___024root___eval_static -V{t#,#}+ Vt_verilated_debug___024root___eval_initial --V{t#,#}+ Vt_verilated_debug___024root___initial__TOP__0 +-V{t#,#}+ Vt_verilated_debug___024root___eval_initial__TOP Data: w96: 000000aa 000000bb 000000cc --V{t#,#}+ Initial loop -V{t#,#}+ Vt_verilated_debug___024root___eval_settle +-V{t#,#}+ Eval -V{t#,#}+ Vt_verilated_debug___024root___eval --V{t#,#}+ Clock loop --V{t#,#}+ Vt_verilated_debug___024root___eval +-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act +-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act +-V{t#,#} No triggers active -V{t#,#}+++++TOP Evaluate Vt_verilated_debug::eval_step -V{t#,#}+ Vt_verilated_debug___024root___eval_debug_assertions --V{t#,#}+ Clock loop +-V{t#,#}+ Eval -V{t#,#}+ Vt_verilated_debug___024root___eval --V{t#,#}+ Vt_verilated_debug___024root___sequent__TOP__0 +-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act +-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @(posedge clk) +-V{t#,#}+ Vt_verilated_debug___024root___eval_act +-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act +-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_verilated_debug___024root___eval_nba +-V{t#,#}+ Vt_verilated_debug___024root___nba_sequent__TOP__0 *-* All Finished *-* --V{t#,#}+ Vt_verilated_debug___024root___final +-V{t#,#}+ Vt_verilated_debug___024root___eval_triggers__act +-V{t#,#}+ Vt_verilated_debug___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_verilated_debug___024root___eval_final diff --git a/test_regress/t/t_wrapper_context_top0.out b/test_regress/t/t_wrapper_context_top0.out index 4affa5cef..20ca811c4 100644 --- a/test_regress/t/t_wrapper_context_top0.out +++ b/test_regress/t/t_wrapper_context_top0.out @@ -73,7 +73,7 @@ C 'ft/t_wrapper_context.vl33n7pagev_branch/topoifS33-34htop0.top' C 'ft/t_wrapper_context.vl33n8pagev_branch/topoelseS36htop0.top' 10 C 'ft/t_wrapper_context.vl38n4pagev_line/topoblockS38-39htop0.top' 1 C 'ft/t_wrapper_context.vl40n7pagev_branch/topoifS40htop0.top' 0 -C 'ft/t_wrapper_context.vl40n8pagev_branch/topoelseS46htop0.top' 24 +C 'ft/t_wrapper_context.vl40n8pagev_branch/topoelseS46htop0.top' 23 C 'ft/t_wrapper_context.vl41n11pagev_line/topoelsehtop0.top' 0 C 'ft/t_wrapper_context.vl47n10pagev_branch/topoifS47-49htop0.top' 1 -C 'ft/t_wrapper_context.vl47n11pagev_branch/topoelsehtop0.top' 23 +C 'ft/t_wrapper_context.vl47n11pagev_branch/topoelsehtop0.top' 33 diff --git a/test_regress/t/t_wrapper_context_top1.out b/test_regress/t/t_wrapper_context_top1.out index 84013beb4..889121356 100644 --- a/test_regress/t/t_wrapper_context_top1.out +++ b/test_regress/t/t_wrapper_context_top1.out @@ -72,8 +72,8 @@ C 'ft/t_wrapper_context.vl32n4pagev_line/topoblockS32htop1.top' 6 C 'ft/t_wrapper_context.vl33n7pagev_branch/topoifS33-34htop1.top' 1 C 'ft/t_wrapper_context.vl33n8pagev_branch/topoelseS36htop1.top' 5 C 'ft/t_wrapper_context.vl38n4pagev_line/topoblockS38-39htop1.top' 1 -C 'ft/t_wrapper_context.vl40n7pagev_branch/topoifS40htop1.top' 14 +C 'ft/t_wrapper_context.vl40n7pagev_branch/topoifS40htop1.top' 13 C 'ft/t_wrapper_context.vl40n8pagev_branch/topoelseS46htop1.top' 0 -C 'ft/t_wrapper_context.vl41n11pagev_line/topoelsehtop1.top' 13 +C 'ft/t_wrapper_context.vl41n11pagev_line/topoelsehtop1.top' 18 C 'ft/t_wrapper_context.vl47n10pagev_branch/topoifS47-49htop1.top' 0 C 'ft/t_wrapper_context.vl47n11pagev_branch/topoelsehtop1.top' 0 diff --git a/test_regress/t/t_xml_debugcheck.out b/test_regress/t/t_xml_debugcheck.out index 839942936..35a4d21a9 100644 --- a/test_regress/t/t_xml_debugcheck.out +++ b/test_regress/t/t_xml_debugcheck.out @@ -22,860 +22,62 @@ - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - + + - + - - - + + + + + + + + + + + + + + + - - + + - - - - + + + + - - + + - - - - + + + + @@ -886,17 +88,17 @@ - - + + - - - + + + - - - - + + + + @@ -905,17 +107,17 @@ - - + + - - - + + + - - - - + + + + @@ -928,17 +130,17 @@ - - + + - - - + + + - - - - + + + + @@ -947,17 +149,17 @@ - - + + - - - + + + - - - - + + + + @@ -970,21 +172,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -995,21 +197,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -1024,21 +226,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -1049,21 +251,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -1078,21 +280,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -1103,21 +305,21 @@ - - + + - - - + + + - - - + + + - - - - + + + + @@ -1132,26 +334,26 @@ - - - - - - - + + + + + + + - - - - - - - + + + + + + + @@ -1162,26 +364,26 @@ - - - - - - - + + + + + + + - - - - - - - + + + + + + + @@ -1192,17 +394,17 @@ - - - - - - - - - - - + + + + + + + + + + + @@ -1211,17 +413,17 @@ - - - - - - - - - - - + + + + + + + + + + + @@ -1234,17 +436,17 @@ - - - - - - - - - - - + + + + + + + + + + + @@ -1253,17 +455,17 @@ - - - - - - - - - - - + + + + + + + + + + + @@ -1276,150 +478,1161 @@ - - - - - - - + + + + + + + - - - - - - - + + + + + + + - + - - + + - - - + + + - - - + + + - - - + + + - - - - - - - - - + + + + + + + + + - + - - + + - - - - + + + + - + - - - + + + - - - - + + + + - + - - + + - - + + - - + + + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + @@ -1433,11 +1646,20 @@ - + + + + + + + + + + @@ -1445,39 +1667,39 @@ - + - + - + - + - + - + - + - + - + - + - + @@ -1487,10 +1709,10 @@ - + - + @@ -1509,46 +1731,50 @@ - + - - - + + + - - + + - - + + - - - + + + - - + + - + - - + + - + - - + + - + - - - - + + + + + + + + From 0e62cd11daadc2c2413f275c437675a5c359b722 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 16 May 2022 18:47:15 +0100 Subject: [PATCH 004/177] Don't issue DEPRECATED for now no-op clock_enable attribute Fixes #3421 --- src/V3LinkParse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 93222023e..429234eee 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -305,7 +305,7 @@ private: VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (nodep->attrType() == VAttrType::VAR_CLOCK_ENABLE) { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); - nodep->v3warn(DEPRECATED, "'clock_enable' attribute is deprecated and has no effect"); + // Accepted and silently ignored for backward compatibility, but has no effect VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (nodep->attrType() == VAttrType::VAR_FORCEABLE) { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); From 282887d9c6fbc0b1c01416963f4ca27bdae52dbf Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 16 May 2022 20:02:49 +0100 Subject: [PATCH 005/177] Fix code coverage holes Fixes #3422 --- src/V3Active.cpp | 7 +- src/V3Clock.cpp | 34 +- src/V3Order.cpp | 170 +++++----- src/V3OrderGraph.h | 352 ++++++++------------- src/V3Partition.cpp | 2 +- src/V3Sched.cpp | 3 +- src/V3SchedAcyclic.cpp | 17 +- src/V3SchedPartition.cpp | 23 +- src/V3SchedReplicate.cpp | 5 +- test_regress/t/t_do_not_convert_to_comb.pl | 21 ++ test_regress/t/t_do_not_convert_to_comb.v | 57 ++++ 11 files changed, 336 insertions(+), 355 deletions(-) create mode 100755 test_regress/t/t_do_not_convert_to_comb.pl create mode 100644 test_regress/t/t_do_not_convert_to_comb.v diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 45f7433f9..353b4606a 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -514,10 +514,9 @@ private: visitAlways(nodep, nodep->sensesp(), nodep->keyword()); } virtual void visit(AstAlwaysPostponed* nodep) override { - if (!nodep->bodysp()) { // Empty always. Remove it now. - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - return; - } + // Might be empty with later optimizations, so this assertion can be removed, + // but for now it is guaranteed to be not empty. + UASSERT_OBJ(nodep->bodysp(), nodep, "Should not be empty"); visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS); } virtual void visit(AstAlwaysPublic* nodep) override { diff --git a/src/V3Clock.cpp b/src/V3Clock.cpp index 180f087a0..ed4b1d729 100644 --- a/src/V3Clock.cpp +++ b/src/V3Clock.cpp @@ -119,30 +119,26 @@ private: pushDeletep(nodep); // Delete it later, AstActives still pointing to it } virtual void visit(AstActive* nodep) override { - // Careful if adding variables here, ACTIVES can be under other ACTIVES - // Need to save and restore any member state in AstUntilStable block + UASSERT_OBJ(nodep->hasClocked(), nodep, "Should have been converted by V3Sched"); + UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not have been created if empty"); + VNRelinker relinker; nodep->unlinkFrBack(&relinker); - UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not have been created if empty"); AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext(); - if (nodep->hasClocked()) { - // Create 'if' statement, if needed - if (!m_lastSenp || !nodep->sensesp()->sameTree(m_lastSenp)) { - clearLastSen(); - m_lastSenp = nodep->sensesp(); - // Make a new if statement - m_lastIfp = makeActiveIf(m_lastSenp); - relinker.relink(m_lastIfp); - } - // Move statements to if - m_lastIfp->addIfsp(stmtsp); - } else if (nodep->hasCombo()) { + + // Create 'if' statement, if needed + if (!m_lastSenp || !nodep->sensesp()->sameTree(m_lastSenp)) { clearLastSen(); - // Move statements to body - relinker.relink(stmtsp); - } else { - nodep->v3fatalSrc("Should have been removed by V3Sched::schedule"); + m_lastSenp = nodep->sensesp(); + // Make a new if statement + m_lastIfp = makeActiveIf(m_lastSenp); + relinker.relink(m_lastIfp); } + + // Move statements to if + m_lastIfp->addIfsp(stmtsp); + + // Dispose of the AstActive VL_DO_DANGLING(nodep->deleteTree(), nodep); } virtual void visit(AstExecGraph* nodep) override { diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 7eaaf466a..c1d49dfbf 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -112,8 +112,8 @@ void OrderGraph::loopsVertexCb(V3GraphVertex* vertexp) { << " Example path: " << vvertexp->nodep()->typeName() << endl; } if (OrderVarVertex* const vvertexp = dynamic_cast(vertexp)) { - std::cerr << vvertexp->varScp()->fileline()->warnOther() - << " Example path: " << vvertexp->varScp()->prettyName() << endl; + std::cerr << vvertexp->vscp()->fileline()->warnOther() + << " Example path: " << vvertexp->vscp()->prettyName() << endl; } } @@ -924,9 +924,6 @@ class OrderProcess final : VNDeleter { V3List m_pomReadyDomScope; // List of ready domain/scope pairs, by loopId std::map, unsigned> m_funcNums; // Function ordinals - // STATS - std::array m_statCut; // Count of each edge type cut - // METHODS VL_DEBUG_FUNC; // Declare debug() @@ -999,15 +996,7 @@ class OrderProcess final : VNDeleter { pushDeletep(m_deleteDomainp); } - ~OrderProcess() { - // Stats - for (int type = 0; type < OrderVEdgeType::_ENUM_END; type++) { - const double count = double(m_statCut[type]); - if (count != 0.0) { - V3Stats::addStat(string("Order, cut, ") + OrderVEdgeType(type).ascii(), count); - } - } - } + ~OrderProcess() = default; public: // Order the logic @@ -1071,16 +1060,18 @@ void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) { OrderEitherVertex* const fromVertexp = static_cast(edgep->fromp()); if (edgep->weight() && fromVertexp->domainMatters()) { AstSenTree* fromDomainp = fromVertexp->domainp(); + UASSERT(!fromDomainp->hasCombo(), "There should be no need for combinational domains"); + if (OrderVarVertex* const varVtxp = dynamic_cast(fromVertexp)) { - AstVarScope* const vscp = varVtxp->varScp(); + AstVarScope* const vscp = varVtxp->vscp(); if (AstSenTree* const externalDomainp = m_externalDomain(vscp)) { + UASSERT(!externalDomainp->hasCombo(), + "There should be no need for combinational domains"); fromDomainp = fromDomainp == m_deleteDomainp ? externalDomainp : combineDomains(fromDomainp, externalDomainp); } } - UINFO(9, " from d=" << cvtToHex(fromDomainp) << " " << fromVertexp << endl); - UASSERT(!fromDomainp->hasCombo(), "There should be no need for combinational domains"); // Irrelevant input vertex (never triggered) if (fromDomainp == m_deleteDomainp) continue; @@ -1088,29 +1079,22 @@ void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) { // First input to this vertex if (!domainp) domainp = fromDomainp; - // Once in combo, keep in combo; already as severe as we can get - if (domainp->hasCombo()) break; - // Make a domain that merges the two domains if (domainp != fromDomainp) domainp = combineDomains(domainp, fromDomainp); } } - // Default the domain - // This is a node which has only constant inputs, or is otherwise indeterminate. - // Presumably it has inputs which we never trigger, or nothing it's sensitive to, - // so we can rip it out. - if (!domainp && vertexp->scopep()) domainp = m_deleteDomainp; + // If nothing triggers this vertex, we can delete the corresponding logic + if (!domainp) domainp = m_deleteDomainp; - if (domainp) { - vertexp->domainp(domainp); - UINFO(5, " done d=" << cvtToHex(vertexp->domainp()) - << (domainp == m_deleteDomainp ? " [DEL]" - : vertexp->domainp()->hasCombo() ? " [COMB]" - : vertexp->domainp()->isMulti() ? " [MULT]" - : "") - << " " << vertexp << endl); - } + // Set the domain of the vertex + vertexp->domainp(domainp); + UINFO(5, " done d=" << cvtToHex(vertexp->domainp()) + << (domainp == m_deleteDomainp ? " [DEL]" + : vertexp->domainp()->hasCombo() ? " [COMB]" + : vertexp->domainp()->isMulti() ? " [MULT]" + : "") + << " " << vertexp << endl); } //###################################################################### @@ -1131,7 +1115,7 @@ void OrderProcess::processEdgeReport() { for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) { if (OrderVarVertex* const vvertexp = dynamic_cast(itp)) { - string name(vvertexp->varScp()->prettyName()); + string name(vvertexp->vscp()->prettyName()); if (dynamic_cast(itp)) { name += " {PRE}"; } else if (dynamic_cast(itp)) { @@ -1141,7 +1125,7 @@ void OrderProcess::processEdgeReport() { } std::ostringstream os; os.setf(std::ios::left); - os << " " << cvtToHex(vvertexp->varScp()) << " " << std::setw(50) << name << " "; + os << " " << cvtToHex(vvertexp->vscp()) << " " << std::setw(50) << name << " "; AstSenTree* const senTreep = vvertexp->domainp(); if (senTreep == m_deleteDomainp) { os << "DELETED"; @@ -1314,67 +1298,65 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, AstNode* nodep = lvertexp->nodep(); AstNodeModule* const modp = scopep->modp(); UASSERT(modp, "nullptr"); - if (VN_IS(nodep, Active)) { - // Just ignore sensitivities, we'll deal with them when we move statements that need them - } else { // Normal logic - // Move the logic into a CFunc + + // We are move the logic into a CFunc, so unlink it from the AstActive + nodep->unlinkFrBack(); + + // Process procedures per statement (unless profCFuncs), so we can split CFuncs within + // procedures. Everything else is handled in one go + if (AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure)) { + nodep = procp->bodysp(); + pushDeletep(procp); + } + + // When profCFuncs, create a new function for all logic block + if (v3Global.opt.profCFuncs()) newFuncpr = nullptr; + + while (nodep) { + // Split the CFunc if too large (but not when profCFuncs) + if (!v3Global.opt.profCFuncs() + && (v3Global.opt.outputSplitCFuncs() + && v3Global.opt.outputSplitCFuncs() < newStmtsr)) { + // Put every statement into a unique function to ease profiling or reduce function + // size + newFuncpr = nullptr; + } + if (!newFuncpr && domainp != m_deleteDomainp) { + const string name = cfuncName(modp, domainp, scopep, nodep); + newFuncpr = new AstCFunc(nodep->fileline(), name, scopep); + newFuncpr->isStatic(false); + newFuncpr->isLoose(true); + newFuncpr->slow(m_slow); + newStmtsr = 0; + scopep->addActivep(newFuncpr); + // Create top call to it + AstCCall* const callp = new AstCCall(nodep->fileline(), newFuncpr); + // Where will we be adding the call? + AstActive* const newActivep = new AstActive(nodep->fileline(), name, domainp); + newActivep->addStmtsp(callp); + if (!activep) { + activep = newActivep; + } else { + activep->addNext(newActivep); + } + UINFO(6, " New " << newFuncpr << endl); + } + + AstNode* const nextp = nodep->nextp(); + // When processing statements in a procedure, unlink the current statement if (nodep->backp()) nodep->unlinkFrBack(); - // Process procedures per statement (unless profCFuncs), so we can split CFuncs within - // procedures. Everything else is handled in one go - if (AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure)) { - nodep = procp->bodysp(); - pushDeletep(procp); + if (domainp == m_deleteDomainp) { + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } else { + newFuncpr->addStmtsp(nodep); + // Add in the number of nodes we're adding + if (v3Global.opt.outputSplitCFuncs()) newStmtsr += nodep->nodeCount(); } - // When profCFuncs, create a new function for all logic block - if (v3Global.opt.profCFuncs()) newFuncpr = nullptr; - - while (nodep) { - // Split the CFunc if too large (but not when profCFuncs) - if (!v3Global.opt.profCFuncs() - && (v3Global.opt.outputSplitCFuncs() - && v3Global.opt.outputSplitCFuncs() < newStmtsr)) { - // Put every statement into a unique function to ease profiling or reduce function - // size - newFuncpr = nullptr; - } - if (!newFuncpr && domainp != m_deleteDomainp) { - const string name = cfuncName(modp, domainp, scopep, nodep); - newFuncpr = new AstCFunc(nodep->fileline(), name, scopep); - newFuncpr->isStatic(false); - newFuncpr->isLoose(true); - newFuncpr->slow(m_slow); - newStmtsr = 0; - scopep->addActivep(newFuncpr); - // Create top call to it - AstCCall* const callp = new AstCCall(nodep->fileline(), newFuncpr); - // Where will we be adding the call? - AstActive* const newActivep = new AstActive(nodep->fileline(), name, domainp); - newActivep->addStmtsp(callp); - if (!activep) { - activep = newActivep; - } else { - activep->addNext(newActivep); - } - UINFO(6, " New " << newFuncpr << endl); - } - - AstNode* const nextp = nodep->nextp(); - // When processing statements in a procedure, unlink the current statement - if (nodep->backp()) nodep->unlinkFrBack(); - - if (domainp == m_deleteDomainp) { - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } else { - newFuncpr->addStmtsp(nodep); - // Add in the number of nodes we're adding - if (v3Global.opt.outputSplitCFuncs()) newStmtsr += nodep->nodeCount(); - } - - nodep = nextp; - } + nodep = nextp; } + return activep; } @@ -1430,7 +1412,7 @@ void OrderProcess::processMTasks() { const OrderVarVertex* const pre_varp = dynamic_cast(edgep->fromp()); if (!pre_varp) continue; - AstVar* const varp = pre_varp->varScp()->varp(); + AstVar* const varp = pre_varp->vscp()->varp(); // varp depends on logicp, so logicp produces varp, // and vice-versa below varp->addProducingMTaskId(mtaskId); @@ -1440,7 +1422,7 @@ void OrderProcess::processMTasks() { const OrderVarVertex* const post_varp = dynamic_cast(edgep->top()); if (!post_varp) continue; - AstVar* const varp = post_varp->varScp()->varp(); + AstVar* const varp = post_varp->vscp()->varp(); varp->addConsumingMTaskId(mtaskId); } // TODO? We ignore IO vars here, so those will have empty mtask diff --git a/src/V3OrderGraph.h b/src/V3OrderGraph.h index bf8da4954..33a22284d 100644 --- a/src/V3OrderGraph.h +++ b/src/V3OrderGraph.h @@ -43,7 +43,6 @@ #include -class OrderVisitor; class OrderMoveVertex; class OrderMoveVertexMaker; class OrderMoveDomScope; @@ -55,50 +54,8 @@ enum OrderWeights : uint8_t { WEIGHT_POST = 2, // Post-delayed used var WEIGHT_PRE = 3, // Breakable pre-delayed used var WEIGHT_MEDIUM = 8, // Medium weight just so dot graph looks nice - WEIGHT_NORMAL = 32 -}; // High weight just so dot graph looks nice - -struct OrderVEdgeType { - enum en : uint8_t { - VERTEX_UNKNOWN = 0, - VERTEX_LOGIC, - VERTEX_VARSTD, - VERTEX_VARPRE, - VERTEX_VARPOST, - VERTEX_VARPORD, - VERTEX_MOVE, - EDGE_STD, - EDGE_COMBOCUT, - EDGE_PRECUT, - EDGE_POSTCUT, - _ENUM_END - }; - const char* ascii() const { - static const char* const names[] - = {"%E-vedge", "VERTEX_LOGIC", "VERTEX_VARSTD", "VERTEX_VARPRE", - "VERTEX_VARPOST", "VERTEX_VARPORD", "VERTEX_MOVE", "EDGE_STD", - "EDGE_COMBOCUT", "EDGE_PRECUT", "EDGE_POSTCUT", "_ENUM_END"}; - return names[m_e]; - } - enum en m_e; - inline OrderVEdgeType() - : m_e{VERTEX_UNKNOWN} {} - // cppcheck-suppress noExplicitConstructor - inline OrderVEdgeType(en _e) - : m_e{_e} {} - explicit inline OrderVEdgeType(int _e) - : m_e(static_cast(_e)) {} // Need () or GCC 4.8 false warning - operator en() const { return m_e; } + WEIGHT_NORMAL = 32 // High weight just so dot graph looks nice }; -inline bool operator==(const OrderVEdgeType& lhs, const OrderVEdgeType& rhs) { - return lhs.m_e == rhs.m_e; -} -inline bool operator==(const OrderVEdgeType& lhs, OrderVEdgeType::en rhs) { - return lhs.m_e == rhs; -} -inline bool operator==(OrderVEdgeType::en lhs, const OrderVEdgeType& rhs) { - return lhs == rhs.m_e; -} //###################################################################### // Graph types @@ -107,7 +64,7 @@ class OrderGraph final : public V3Graph { public: OrderGraph() = default; virtual ~OrderGraph() override = default; - // Methods + // METHODS virtual void loopsVertexCb(V3GraphVertex* vertexp) override; }; @@ -117,156 +74,187 @@ public: class OrderEitherVertex VL_NOT_FINAL : public V3GraphVertex { AstScope* const m_scopep; // Scope the vertex is in AstSenTree* m_domainp; // Clock domain (nullptr = to be computed as we iterate) -protected: - OrderEitherVertex(V3Graph* graphp, const OrderEitherVertex& old) - : V3GraphVertex{graphp, old} - , m_scopep{old.m_scopep} - , m_domainp{old.m_domainp} {} -public: +protected: + // CONSTRUCTOR OrderEitherVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp) : V3GraphVertex{graphp} , m_scopep{scopep} - , m_domainp{domainp} {} + , m_domainp{domainp} { + UASSERT(scopep, "Must not be null"); + } virtual ~OrderEitherVertex() override = default; - virtual OrderEitherVertex* clone(V3Graph* graphp) const override = 0; - // Methods - virtual OrderVEdgeType type() const = 0; - virtual bool domainMatters() = 0; // Must be in same domain when cross edge to this vertex - virtual string dotName() const override { return cvtToHex(m_scopep) + "_"; } + +public: + // METHODS + virtual bool domainMatters() = 0; + // ACCESSORS + AstSenTree* domainp() const { return m_domainp; } void domainp(AstSenTree* domainp) { m_domainp = domainp; } AstScope* scopep() const { return m_scopep; } - AstSenTree* domainp() const { return m_domainp; } + + // LCOV_EXCL_START // Debug code + virtual string dotName() const override { return cvtToHex(m_scopep) + "_"; } + // LCOV_EXCL_STOP }; class OrderLogicVertex final : public OrderEitherVertex { AstNode* const m_nodep; AstSenTree* const m_hybridp; -protected: - OrderLogicVertex(V3Graph* graphp, const OrderLogicVertex& old) - : OrderEitherVertex{graphp, old} - , m_nodep{old.m_nodep} - , m_hybridp{old.m_hybridp} {} - public: + // CONSTRUCTOR OrderLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp, AstSenTree* hybridp, AstNode* nodep) : OrderEitherVertex{graphp, scopep, domainp} , m_nodep{nodep} , m_hybridp{hybridp} { - UASSERT_OBJ(!(domainp && hybridp), nodep, "Can't have bot domainp and hybridp set"); + UASSERT_OBJ(!(domainp && hybridp), nodep, "Cannot have bot domainp and hybridp set"); } virtual ~OrderLogicVertex() override = default; - virtual OrderLogicVertex* clone(V3Graph* graphp) const override { - return new OrderLogicVertex(graphp, *this); - } - virtual OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_LOGIC; } + + // METHODS virtual bool domainMatters() override { return true; } + // ACCESSORS + AstNode* nodep() const { return m_nodep; } + AstSenTree* hybridp() const { return m_hybridp; } + + // LCOV_EXCL_START // Debug code virtual string name() const override { return (cvtToHex(m_nodep) + "\\n " + cvtToStr(nodep()->typeName())); } - AstNode* nodep() const { return m_nodep; } - AstSenTree* hybridp() const { return m_hybridp; } virtual string dotShape() const override { return VN_IS(m_nodep, Active) ? "doubleoctagon" : "rect"; } + // LCOV_EXCL_STOP }; class OrderVarVertex VL_NOT_FINAL : public OrderEitherVertex { - AstVarScope* const m_varScp; - -protected: - OrderVarVertex(V3Graph* graphp, const OrderVarVertex& old) - : OrderEitherVertex{graphp, old} - , m_varScp{old.m_varScp} {} + AstVarScope* const m_vscp; public: - OrderVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp) + // CONSTRUCTOR + OrderVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* vscp) : OrderEitherVertex{graphp, scopep, nullptr} - , m_varScp{varScp} {} + , m_vscp{vscp} {} virtual ~OrderVarVertex() override = default; - virtual OrderVarVertex* clone(V3Graph* graphp) const override = 0; - virtual OrderVEdgeType type() const override = 0; - virtual FileLine* fileline() const override { return varScp()->fileline(); } + // ACCESSORS - AstVarScope* varScp() const { return m_varScp; } - virtual string dotShape() const override { return "ellipse"; } + AstVarScope* vscp() const { return m_vscp; } + + // LCOV_EXCL_START // Debug code + virtual string dotShape() const override final { return "ellipse"; } + virtual string nameSuffix() const = 0; + virtual string name() const override final { + return cvtToHex(m_vscp) + " " + nameSuffix() + "\\n " + m_vscp->name(); + } + // LCOV_EXCL_STOP }; class OrderVarStdVertex final : public OrderVarVertex { - OrderVarStdVertex(V3Graph* graphp, const OrderVarStdVertex& old) - : OrderVarVertex{graphp, old} {} - public: + // CONSTRUCTOR OrderVarStdVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp) : OrderVarVertex{graphp, scopep, varScp} {} virtual ~OrderVarStdVertex() override = default; - virtual OrderVarStdVertex* clone(V3Graph* graphp) const override { - return new OrderVarStdVertex(graphp, *this); - } - virtual OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_VARSTD; } - virtual string name() const override { - return (cvtToHex(varScp()) + "\\n " + varScp()->name()); - } - virtual string dotColor() const override { return "grey"; } - virtual bool domainMatters() override { return true; } -}; -class OrderVarPreVertex final : public OrderVarVertex { - OrderVarPreVertex(V3Graph* graphp, const OrderVarPreVertex& old) - : OrderVarVertex{graphp, old} {} + // METHODS + virtual bool domainMatters() override { return true; } + + // LCOV_EXCL_START // Debug code + virtual string nameSuffix() const override { return ""; } + virtual string dotColor() const override { return "grey"; } + // LCOV_EXCL_STOP +}; + +class OrderVarPreVertex final : public OrderVarVertex { public: + // CONSTRUCTOR OrderVarPreVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp) : OrderVarVertex{graphp, scopep, varScp} {} virtual ~OrderVarPreVertex() override = default; - virtual OrderVarPreVertex* clone(V3Graph* graphp) const override { - return new OrderVarPreVertex(graphp, *this); - } - virtual OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_VARPRE; } - virtual string name() const override { - return (cvtToHex(varScp()) + " PRE\\n " + varScp()->name()); - } - virtual string dotColor() const override { return "green"; } - virtual bool domainMatters() override { return false; } -}; -class OrderVarPostVertex final : public OrderVarVertex { - OrderVarPostVertex(V3Graph* graphp, const OrderVarPostVertex& old) - : OrderVarVertex{graphp, old} {} + // METHODS + virtual bool domainMatters() override { return false; } + + // LCOV_EXCL_START // Debug code + virtual string nameSuffix() const override { return "PRE"; } + virtual string dotColor() const override { return "green"; } + // LCOV_EXCL_STOP +}; + +class OrderVarPostVertex final : public OrderVarVertex { public: + // CONSTRUCTOR OrderVarPostVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp) : OrderVarVertex{graphp, scopep, varScp} {} - virtual OrderVarPostVertex* clone(V3Graph* graphp) const override { - return new OrderVarPostVertex(graphp, *this); - } - virtual OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_VARPOST; } virtual ~OrderVarPostVertex() override = default; - virtual string name() const override { - return (cvtToHex(varScp()) + " POST\\n " + varScp()->name()); - } - virtual string dotColor() const override { return "red"; } - virtual bool domainMatters() override { return false; } -}; -class OrderVarPordVertex final : public OrderVarVertex { - OrderVarPordVertex(V3Graph* graphp, const OrderVarPordVertex& old) - : OrderVarVertex{graphp, old} {} + // METHODS + virtual bool domainMatters() override { return false; } + + // LCOV_EXCL_START // Debug code + virtual string nameSuffix() const override { return "POST"; } + virtual string dotColor() const override { return "red"; } + // LCOV_EXCL_STOP +}; + +class OrderVarPordVertex final : public OrderVarVertex { public: + // CONSTRUCTOR OrderVarPordVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp) : OrderVarVertex{graphp, scopep, varScp} {} virtual ~OrderVarPordVertex() override = default; - virtual OrderVarPordVertex* clone(V3Graph* graphp) const override { - return new OrderVarPordVertex(graphp, *this); - } - virtual OrderVEdgeType type() const override { return OrderVEdgeType::VERTEX_VARPORD; } - virtual string name() const override { - return (cvtToHex(varScp()) + " PORD\\n " + varScp()->name()); - } - virtual string dotColor() const override { return "blue"; } + + // METHODS virtual bool domainMatters() override { return false; } + + // LCOV_EXCL_START // Debug code + virtual string nameSuffix() const override { return "PORD"; } + virtual string dotColor() const override { return "blue"; } + // LCOV_EXCL_STOP +}; + +//###################################################################### +// Edge types + +class OrderEdge VL_NOT_FINAL : public V3GraphEdge { +public: + // CONSTRUCTOR + OrderEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, int weight, + bool cutable = false) + : V3GraphEdge{graphp, fromp, top, weight, cutable} {} + virtual ~OrderEdge() override = default; +}; + +class OrderPostCutEdge final : public OrderEdge { + // Edge created from output of post assignment +public: + // CONSTRUCTOR + OrderPostCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) + : OrderEdge{graphp, fromp, top, WEIGHT_COMBO, CUTABLE} {} + virtual ~OrderPostCutEdge() override = default; + + // LCOV_EXCL_START // Debug code + virtual string dotColor() const override { return "palegreen"; } + // LCOV_EXCL_STOP +}; + +class OrderPreCutEdge final : public OrderEdge { + // Edge created from var_PREVAR->consuming logic vertex + // Always breakable, just results in performance loss + // in which case we can't optimize away the pre/post delayed assignments +public: + // CONSTRUCTOR + OrderPreCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) + : OrderEdge{graphp, fromp, top, WEIGHT_PRE, CUTABLE} {} + virtual ~OrderPreCutEdge() override = default; + + // LCOV_EXCL_START // Debug code + virtual string dotColor() const override { return "khaki"; } + // LCOV_EXCL_STOP }; //###################################################################### @@ -283,7 +271,7 @@ protected: friend class OrderProcess; friend class OrderMoveVertexMaker; // These only contain the "next" item, - // for the head of the list, see the same var name under OrderVisitor + // for the head of the list, see the same var name under OrderProcess V3ListEnt m_pomWaitingE; // List of nodes needing inputs to become ready V3ListEnt m_readyVerticesE; // List of ready under domain/scope public: @@ -294,12 +282,8 @@ public: , m_state{POM_WAIT} , m_domScopep{nullptr} {} virtual ~OrderMoveVertex() override = default; - virtual OrderMoveVertex* clone(V3Graph* graphp) const override { - v3fatalSrc("Unsupported"); - return nullptr; - } + // METHODS - virtual OrderVEdgeType type() const { return OrderVEdgeType::VERTEX_MOVE; } virtual string dotColor() const override { if (logicp()) { return logicp()->dotColor(); @@ -307,13 +291,7 @@ public: return ""; } } - virtual FileLine* fileline() const override { - if (logicp()) { - return logicp()->fileline(); - } else { - return nullptr; - } - } + virtual string name() const override { string nm; if (VL_UNCOVERABLE(!logicp())) { // Avoid crash when debugging @@ -350,9 +328,6 @@ class MTaskMoveVertex final : public V3GraphVertex { const AstScope* const m_scopep; const AstSenTree* const m_domainp; -protected: - friend class OrderVisitor; - public: MTaskMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp, const OrderEitherVertex* varp, const AstScope* scopep, const AstSenTree* domainp) @@ -364,11 +339,13 @@ public: UASSERT(!(logicp && varp), "MTaskMoveVertex: logicp and varp may not both be set!\n"); } virtual ~MTaskMoveVertex() override = default; - virtual MTaskMoveVertex* clone(V3Graph* graphp) const override { - v3fatalSrc("Unsupported"); - return nullptr; - } - virtual OrderVEdgeType type() const { return OrderVEdgeType::VERTEX_MOVE; } + + // ACCESSORS + OrderLogicVertex* logicp() const { return m_logicp; } + const OrderEitherVertex* varp() const { return m_varp; } + const AstScope* scopep() const { return m_scopep; } + const AstSenTree* domainp() const { return m_domainp; } + virtual string dotColor() const override { if (logicp()) { return logicp()->dotColor(); @@ -389,71 +366,6 @@ public: } return nm; } - // ACCESSORS - OrderLogicVertex* logicp() const { return m_logicp; } - const OrderEitherVertex* varp() const { return m_varp; } - const AstScope* scopep() const { return m_scopep; } - const AstSenTree* domainp() const { return m_domainp; } -}; - -//###################################################################### -// Edge types - -class OrderEdge VL_NOT_FINAL : public V3GraphEdge { -protected: - OrderEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, const OrderEdge& old) - : V3GraphEdge{graphp, fromp, top, old} {} - -public: - OrderEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, int weight, - bool cutable = false) - : V3GraphEdge{graphp, fromp, top, weight, cutable} {} - virtual ~OrderEdge() override = default; - virtual OrderVEdgeType type() const { return OrderVEdgeType::EDGE_STD; } - virtual OrderEdge* clone(V3Graph* graphp, V3GraphVertex* fromp, - V3GraphVertex* top) const override { - return new OrderEdge(graphp, fromp, top, *this); - } -}; - -class OrderPostCutEdge final : public OrderEdge { - // Edge created from output of post assignment - // Breakable if the output var feeds back to input combo logic or another clock pin - // in which case we'll need a change detect loop around this var. - OrderPostCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, - const OrderPostCutEdge& old) - : OrderEdge{graphp, fromp, top, old} {} - -public: - OrderPostCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) - : OrderEdge{graphp, fromp, top, WEIGHT_COMBO, CUTABLE} {} - virtual OrderVEdgeType type() const override { return OrderVEdgeType::EDGE_POSTCUT; } - virtual ~OrderPostCutEdge() override = default; - virtual OrderPostCutEdge* clone(V3Graph* graphp, V3GraphVertex* fromp, - V3GraphVertex* top) const override { - return new OrderPostCutEdge(graphp, fromp, top, *this); - } - virtual string dotColor() const override { return "PaleGreen"; } -}; - -class OrderPreCutEdge final : public OrderEdge { - // Edge created from var_PREVAR->consuming logic vertex - // Always breakable, just results in performance loss - // in which case we can't optimize away the pre/post delayed assignments - OrderPreCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, - const OrderPreCutEdge& old) - : OrderEdge{graphp, fromp, top, old} {} - -public: - OrderPreCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) - : OrderEdge{graphp, fromp, top, WEIGHT_PRE, CUTABLE} {} - virtual OrderVEdgeType type() const override { return OrderVEdgeType::EDGE_PRECUT; } - virtual OrderPreCutEdge* clone(V3Graph* graphp, V3GraphVertex* fromp, - V3GraphVertex* top) const override { - return new OrderPreCutEdge(graphp, fromp, top, *this); - } - virtual ~OrderPreCutEdge() override = default; - virtual string dotColor() const override { return "khaki"; } }; #endif // Guard diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index 793e971ff..8b8327889 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -1951,7 +1951,7 @@ public: const OrderVarStdVertex* const ovvp = dynamic_cast(edgep->top()); if (!ovvp) continue; - if (ovvp->varScp()->varp()->isSc()) { + if (ovvp->vscp()->varp()->isSc()) { ovvSetSystemC.insert(ovvp); } else { ovvSet.insert(ovvp); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 17c44ac01..a44fe75c1 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -128,7 +128,8 @@ void splitCheck(AstCFunc* ofuncp) { // 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()); + // Currently we do not use finalsp in V3Sched, if we do, it needs to be handled here + UASSERT_OBJ(!ofuncp->finalsp(), ofuncp, "Should not have any finalps"); while (tempp->stmtsp()) { AstNode* const itemp = tempp->stmtsp()->unlinkFrBack(); const int stmts = itemp->nodeCount(); diff --git a/src/V3SchedAcyclic.cpp b/src/V3SchedAcyclic.cpp index 61a0c7a7a..6160ec8d5 100644 --- a/src/V3SchedAcyclic.cpp +++ b/src/V3SchedAcyclic.cpp @@ -69,9 +69,10 @@ public: AstNode* logicp() const { return m_logicp; } AstScope* scopep() const { return m_scopep; } - // For graph dumping + // LCOV_EXCL_START // Debug code string name() const override { return m_logicp->fileline()->ascii(); }; string dotShape() const override { return "rectangle"; } + // LCOV_EXCL_STOP }; class VarVertex final : public V3GraphVertex { @@ -84,10 +85,11 @@ public: AstVarScope* vscp() const { return m_vscp; } AstVar* varp() const { return m_vscp->varp(); } - // For graph dumping + // LCOV_EXCL_START // Debug code string name() const override { return m_vscp->name(); } string dotShape() const override { return "ellipse"; } string dotColor() const override { return "blue"; } + // LCOV_EXCL_STOP }; class Graph final : public V3Graph { @@ -97,8 +99,9 @@ class Graph final : public V3Graph { AstNode* const logicp = lvtxp->logicp(); std::cerr << logicp->fileline()->warnOther() << " Example path: " << logicp->typeName() << endl; - } - if (VarVertex* const vvtxp = dynamic_cast(vtxp)) { + } else { + VarVertex* const vvtxp = dynamic_cast(vtxp); + UASSERT(vvtxp, "Cannot be anything else"); AstVarScope* const vscp = vvtxp->vscp(); std::cerr << vscp->fileline()->warnOther() << " Example path: " << vscp->prettyName() << endl; @@ -369,11 +372,7 @@ LogicByScope fixCuts(AstNetlist* netlistp, const std::vector& cutVer 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); - } + senItemsp = VN_AS(AstNode::addNext(senItemsp, nextp), SenItem); } AstSenTree* const senTree = new AstSenTree{flp, senItemsp}; // Add logic to result with new sensitivity diff --git a/src/V3SchedPartition.cpp b/src/V3SchedPartition.cpp index 2f3afc2f1..88125ee35 100644 --- a/src/V3SchedPartition.cpp +++ b/src/V3SchedPartition.cpp @@ -59,6 +59,8 @@ public: SchedSenVertex(V3Graph* graphp, const AstSenItem* senItemp) : V3GraphVertex{graphp} , m_senItemp{senItemp} {} + + // LCOV_EXCL_START // Debug code string name() const override { std::ostringstream os; V3EmitV::verilogForTree(const_cast(m_senItemp), os); @@ -66,6 +68,7 @@ public: } string dotShape() const override { return "doubleoctagon"; } string dotColor() const override { return "red"; } + // LCOV_EXCL_STOP }; class SchedLogicVertex final : public V3GraphVertex { @@ -83,10 +86,12 @@ public: AstSenTree* senTreep() const { return m_senTreep; } AstNode* logicp() const { return m_logicp; } + // LCOV_EXCL_START // Debug code string name() const override { return m_logicp->typeName() + ("\n" + m_logicp->fileline()->ascii()); }; string dotShape() const override { return "rectangle"; } + // LCOV_EXCL_STOP }; class SchedVarVertex final : public V3GraphVertex { @@ -96,6 +101,8 @@ public: SchedVarVertex(V3Graph* graphp, AstVarScope* vscp) : V3GraphVertex{graphp} , m_vscp{vscp} {} + + // LCOV_EXCL_START // Debug code string name() const override { return m_vscp->name(); } string dotShape() const override { return m_vscp->scopep()->isTop() && m_vscp->varp()->isNonOutput() ? "invhouse" : "ellipse"; @@ -103,6 +110,7 @@ public: string dotColor() const override { return m_vscp->scopep()->isTop() && m_vscp->varp()->isNonOutput() ? "green" : "black"; } + // LCOV_EXCL_STOP }; class SchedGraphBuilder final : public VNVisitor { @@ -229,18 +237,23 @@ class SchedGraphBuilder final : public VNVisitor { virtual void visit(AstAssignPost* nodep) override {} virtual void visit(AstAlwaysPost* nodep) override {} + // LCOV_EXCL_START // Ignore - virtual void visit(AstInitialStatic* nodep) override { // LCOV_EXCL_START + virtual void visit(AstInitialStatic* nodep) override { nodep->v3fatalSrc("Should not need ordering"); } - virtual void visit(AstInitial* nodep) override { + virtual void visit(AstInitial* nodep) override { // nodep->v3fatalSrc("Should not need ordering"); } - virtual void visit(AstFinal* nodep) override { + virtual void visit(AstFinal* nodep) override { // nodep->v3fatalSrc("Should not need ordering"); - } // LCOV_EXCL_STOP + } - virtual void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } + // Default - Any other AstActive content not handled above will hit this + virtual void visit(AstNode* nodep) override { // + nodep->v3fatalSrc("Should behandled above"); + } + // LCOV_EXCL_STOP SchedGraphBuilder(const LogicByScope& clockedLogic, const LogicByScope& combinationalLogic, const LogicByScope& hybridLogic) { diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 694764f37..78a548ea9 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -70,7 +70,7 @@ public: m_drivingRegions = static_cast(m_drivingRegions | regions); } - // For graph dumping + // LCOV_EXCL_START // Debug code string dotColor() const override { switch (static_cast(m_drivingRegions)) { case NONE: return "black"; @@ -81,9 +81,10 @@ public: 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 + default: v3fatal("There are only 3 region bits"); return ""; } } + // LCOV_EXCL_STOP }; class LogicVertex final : public Vertex { diff --git a/test_regress/t/t_do_not_convert_to_comb.pl b/test_regress/t/t_do_not_convert_to_comb.pl new file mode 100755 index 000000000..c6db81d38 --- /dev/null +++ b/test_regress/t/t_do_not_convert_to_comb.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vlt_all => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +# We must not convert these blocks into combinational blocks +file_grep($Self->{stats}, qr/Scheduling, size of class: combinational\s+(\d+)/i, 0); + +ok(1); +1; diff --git a/test_regress/t/t_do_not_convert_to_comb.v b/test_regress/t/t_do_not_convert_to_comb.v new file mode 100644 index 000000000..f80ce544b --- /dev/null +++ b/test_regress/t/t_do_not_convert_to_comb.v @@ -0,0 +1,57 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +module t ( + clk, + input wire i, + output reg o_0, + output reg o_1, + output reg o_2, + output reg o_3, + output reg o_4, + output reg o_5 + ); + + input clk; + + reg a = 0; + reg b = 0; + + event e; + + // We must not convert these blocks into combinational blocks + + always @(i) begin + a <= ~a; + o_0 = i; + end + + always @(i) begin + force b = 1; + o_1 = i; + end + + always @(i) begin + release b; + o_2 = i; + end + + always @(i) begin + -> e; + o_3 = i; + end + + always @(i) begin + ->> e; + o_4 = i; + end + + always @(i) begin + $display("Hello"); + o_5 = i; + end + +endmodule From f0a2c983767835ce74e042f08a60c7982951bcd1 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Fri, 20 May 2022 08:34:32 -0400 Subject: [PATCH 006/177] Commentary --- docs/guide/contributors.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/guide/contributors.rst b/docs/guide/contributors.rst index 68b17d671..1c7733c9e 100644 --- a/docs/guide/contributors.rst +++ b/docs/guide/contributors.rst @@ -151,6 +151,9 @@ In 2018, Verilator 4.000 was released with multithreaded support. In 2019, Verilator joined the `CHIPS Alliance `_. +In 2022, Verilator 5.000 was released with IEEE scheduling semantics +and other improvements. + Currently, various language features and performance enhancements are added as the need arises. Verilator is now about 3x faster than in 2002, and is faster than most (if not every) other simulator. From 3a310f19f0d2b3949fc1e1ae06e4ed27283398da Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Wed, 25 May 2022 12:59:21 +0200 Subject: [PATCH 007/177] Adjust loop conditions in VlTriggerVec functions This change is not a functional one; it is only meant to appease the compiler with respect to warnings such as GCC's `-Wtype-limits`. Signed-off-by: Krzysztof Bieganski --- include/verilated_types.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/verilated_types.h b/include/verilated_types.h index 47a231570..47cd12d9b 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -76,6 +76,7 @@ extern std::string VL_TO_STRING_W(int words, const WDataInP obj); template // class VlTriggerVec final { + // TODO: static assert T_size > 0, and don't generate when empty private: // MEMBERS std::array m_flags; // State of the assoc array @@ -95,19 +96,19 @@ public: // Return true iff at least one element is set bool any() const { - for (size_t i = 0; i < T_size; ++i) + for (size_t i = 0; i < m_flags.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& other) { - for (size_t i = 0; i < T_size; ++i) m_flags[i] |= other.m_flags[i]; + for (size_t i = 0; i < m_flags.size(); ++i) m_flags[i] |= other.m_flags[i]; } // Set elements of 'this' to 'a & !b' element-wise void andNot(const VlTriggerVec& a, const VlTriggerVec& b) { - for (size_t i = 0; i < T_size; ++i) m_flags[i] = a.m_flags[i] & !b.m_flags[i]; + for (size_t i = 0; i < m_flags.size(); ++i) m_flags[i] = a.m_flags[i] & !b.m_flags[i]; } }; From 160f3ee4a7cbd24007cb5c1c7fc8a2f0e70bcd37 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 25 May 2022 19:04:47 +0100 Subject: [PATCH 008/177] Remove dead code, no functional change --- src/V3AstNodes.cpp | 1 - src/V3AstNodes.h | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 407e6bc71..470b53500 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1738,7 +1738,6 @@ void AstVoidDType::dumpSmall(std::ostream& str) const { } void AstVarScope::dump(std::ostream& str) const { this->AstNode::dump(str); - if (isCircular()) str << " [CIRC]"; if (isTrace()) str << " [T]"; if (scopep()) str << " [scopep=" << reinterpret_cast(scopep()) << "]"; if (varp()) { diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index fbfb51c1d..b200d121b 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -2415,7 +2415,6 @@ class AstVarScope final : public AstNode { private: AstScope* m_scopep; // Scope variable is underneath AstVar* m_varp; // [AfterLink] Pointer to variable itself - bool m_circular : 1; // Used in circular ordering dependency, need change detect bool m_trace : 1; // Tracing is turned on for this scope public: AstVarScope(FileLine* fl, AstScope* scopep, AstVar* varp) @@ -2424,7 +2423,6 @@ public: , m_varp{varp} { UASSERT_OBJ(scopep, fl, "Scope must be non-null"); UASSERT_OBJ(varp, fl, "Var must be non-null"); - m_circular = false; m_trace = true; dtypeFrom(varp); } @@ -2451,8 +2449,6 @@ public: // op1 = Calculation of value of variable, nullptr=complicated AstNode* valuep() const { return op1p(); } void valuep(AstNode* valuep) { addOp1p(valuep); } - bool isCircular() const { return m_circular; } - void circular(bool flag) { m_circular = flag; } bool isTrace() const { return m_trace; } void trace(bool flag) { m_trace = flag; } }; From 3af5e7e8da96984f7041a6d8026ef1673cf8eac8 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 25 May 2022 20:16:19 +0100 Subject: [PATCH 009/177] Remove scope pointer from OrderEitherVertex. For ordering, only the scope of logic vertices should be relevant, so remove the scope pointer from OrderEitherVertex and move it into OrderLogicVertex. This does not change single-threaded scheduling at all. Theoretically, multi-threaded scheduling should not be affected either though due to some implementation quirk depending on vertex order in a graph the MT schedule is perturbed by this change, but the performance effect of this is negligible on all benchmarks I have access to. No functional change intended. Fixes #3442 --- src/V3Order.cpp | 42 ++++++++++++++++------------------------- src/V3OrderGraph.h | 47 ++++++++++++++++++++-------------------------- 2 files changed, 36 insertions(+), 53 deletions(-) diff --git a/src/V3Order.cpp b/src/V3Order.cpp index c1d49dfbf..291cc1a68 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -138,24 +138,15 @@ private: public: // METHODS - OrderVarVertex* getVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varscp, - VarVertexType type) { + OrderVarVertex* getVarVertex(V3Graph* graphp, AstVarScope* varscp, VarVertexType type) { const unsigned idx = static_cast(type); OrderVarVertex* vertexp = m_vertexps[idx]; if (!vertexp) { switch (type) { - case VarVertexType::STD: - vertexp = new OrderVarStdVertex(graphp, scopep, varscp); - break; - case VarVertexType::PRE: - vertexp = new OrderVarPreVertex(graphp, scopep, varscp); - break; - case VarVertexType::PORD: - vertexp = new OrderVarPordVertex(graphp, scopep, varscp); - break; - case VarVertexType::POST: - vertexp = new OrderVarPostVertex(graphp, scopep, varscp); - break; + case VarVertexType::STD: vertexp = new OrderVarStdVertex{graphp, varscp}; break; + case VarVertexType::PRE: vertexp = new OrderVarPreVertex{graphp, varscp}; break; + case VarVertexType::PORD: vertexp = new OrderVarPordVertex{graphp, varscp}; break; + case VarVertexType::POST: vertexp = new OrderVarPostVertex{graphp, varscp}; break; } m_vertexps[idx] = vertexp; } @@ -228,7 +219,7 @@ class OrderBuildVisitor final : public VNVisitor { } OrderVarVertex* getVarVertex(AstVarScope* varscp, VarVertexType type) { - return m_orderUser(varscp).getVarVertex(m_graphp, m_scopep, varscp, type); + return m_orderUser(varscp).getVarVertex(m_graphp, varscp, type); } // VISITORS @@ -631,9 +622,9 @@ public: // Clients of ProcessMoveBuildGraph must supply MoveVertexMaker // which creates new T_MoveVertex's. Each new vertex wraps lvertexp // (which may be nullptr.) - virtual T_MoveVertex* makeVertexp( // - OrderLogicVertex* lvertexp, const OrderEitherVertex* varVertexp, - const AstScope* scopep, const AstSenTree* domainp) + virtual T_MoveVertex* makeVertexp(OrderLogicVertex* lvertexp, + const OrderEitherVertex* varVertexp, + const AstSenTree* domainp) = 0; virtual void freeVertexp(T_MoveVertex* freeMep) = 0; }; @@ -680,8 +671,8 @@ public: // For each logic node, make a T_MoveVertex for (V3GraphVertex* itp = m_graphp->verticesBeginp(); itp; itp = itp->verticesNextp()) { if (OrderLogicVertex* const lvertexp = dynamic_cast(itp)) { - T_MoveVertex* const moveVxp = m_vxMakerp->makeVertexp( - lvertexp, nullptr, lvertexp->scopep(), lvertexp->domainp()); + T_MoveVertex* const moveVxp + = m_vxMakerp->makeVertexp(lvertexp, nullptr, lvertexp->domainp()); if (moveVxp) { // Cross link so we can find it later m_logic2move[lvertexp] = moveVxp; @@ -782,10 +773,10 @@ private: const V3GraphVertex* nonLogicVxp = edgep->top(); const VxDomPair key(nonLogicVxp, domainp); if (!m_var2move[key]) { - const OrderEitherVertex* const eithp - = dynamic_cast(nonLogicVxp); + const OrderVarVertex* const eithp + = static_cast(nonLogicVxp); T_MoveVertex* const newMoveVxp - = m_vxMakerp->makeVertexp(nullptr, eithp, eithp->scopep(), domainp); + = m_vxMakerp->makeVertexp(nullptr, eithp, domainp); m_var2move[key] = newMoveVxp; // Find downstream logics that depend on (var, domain) @@ -824,9 +815,9 @@ public: , m_pomWaitingp{pomWaitingp} {} // METHODS virtual OrderMoveVertex* makeVertexp(OrderLogicVertex* lvertexp, const OrderEitherVertex*, - const AstScope* scopep, const AstSenTree* domainp) override { OrderMoveVertex* const resultp = new OrderMoveVertex(m_pomGraphp, lvertexp); + AstScope* const scopep = lvertexp ? lvertexp->scopep() : nullptr; resultp->domScopep(OrderMoveDomScope::findCreate(domainp, scopep)); resultp->m_pomWaitingE.pushBack(*m_pomWaitingp, resultp); return resultp; @@ -849,9 +840,8 @@ public: : m_pomGraphp{pomGraphp} {} virtual MTaskMoveVertex* makeVertexp(OrderLogicVertex* lvertexp, const OrderEitherVertex* varVertexp, - const AstScope* scopep, const AstSenTree* domainp) override { - return new MTaskMoveVertex(m_pomGraphp, lvertexp, varVertexp, scopep, domainp); + return new MTaskMoveVertex(m_pomGraphp, lvertexp, varVertexp, domainp); } virtual void freeVertexp(MTaskMoveVertex* freeMep) override { freeMep->unlinkDelete(m_pomGraphp); diff --git a/src/V3OrderGraph.h b/src/V3OrderGraph.h index 33a22284d..070e38c54 100644 --- a/src/V3OrderGraph.h +++ b/src/V3OrderGraph.h @@ -72,17 +72,13 @@ public: // Vertex types class OrderEitherVertex VL_NOT_FINAL : public V3GraphVertex { - AstScope* const m_scopep; // Scope the vertex is in AstSenTree* m_domainp; // Clock domain (nullptr = to be computed as we iterate) protected: // CONSTRUCTOR - OrderEitherVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp) + OrderEitherVertex(V3Graph* graphp, AstSenTree* domainp) : V3GraphVertex{graphp} - , m_scopep{scopep} - , m_domainp{domainp} { - UASSERT(scopep, "Must not be null"); - } + , m_domainp{domainp} {} virtual ~OrderEitherVertex() override = default; public: @@ -92,24 +88,22 @@ public: // ACCESSORS AstSenTree* domainp() const { return m_domainp; } void domainp(AstSenTree* domainp) { m_domainp = domainp; } - AstScope* scopep() const { return m_scopep; } - - // LCOV_EXCL_START // Debug code - virtual string dotName() const override { return cvtToHex(m_scopep) + "_"; } - // LCOV_EXCL_STOP }; class OrderLogicVertex final : public OrderEitherVertex { - AstNode* const m_nodep; + AstNode* const m_nodep; // The logic this vertex represents + AstScope* const m_scopep; // Scope the logic is under AstSenTree* const m_hybridp; public: // CONSTRUCTOR OrderLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp, AstSenTree* hybridp, AstNode* nodep) - : OrderEitherVertex{graphp, scopep, domainp} + : OrderEitherVertex{graphp, domainp} , m_nodep{nodep} + , m_scopep{scopep} , m_hybridp{hybridp} { + UASSERT_OBJ(scopep, nodep, "Must not be null"); UASSERT_OBJ(!(domainp && hybridp), nodep, "Cannot have bot domainp and hybridp set"); } virtual ~OrderLogicVertex() override = default; @@ -119,6 +113,7 @@ public: // ACCESSORS AstNode* nodep() const { return m_nodep; } + AstScope* scopep() const { return m_scopep; } AstSenTree* hybridp() const { return m_hybridp; } // LCOV_EXCL_START // Debug code @@ -136,8 +131,8 @@ class OrderVarVertex VL_NOT_FINAL : public OrderEitherVertex { public: // CONSTRUCTOR - OrderVarVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* vscp) - : OrderEitherVertex{graphp, scopep, nullptr} + OrderVarVertex(V3Graph* graphp, AstVarScope* vscp) + : OrderEitherVertex{graphp, nullptr} , m_vscp{vscp} {} virtual ~OrderVarVertex() override = default; @@ -156,8 +151,8 @@ public: class OrderVarStdVertex final : public OrderVarVertex { public: // CONSTRUCTOR - OrderVarStdVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp) - : OrderVarVertex{graphp, scopep, varScp} {} + OrderVarStdVertex(V3Graph* graphp, AstVarScope* varScp) + : OrderVarVertex{graphp, varScp} {} virtual ~OrderVarStdVertex() override = default; // METHODS @@ -172,8 +167,8 @@ public: class OrderVarPreVertex final : public OrderVarVertex { public: // CONSTRUCTOR - OrderVarPreVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp) - : OrderVarVertex{graphp, scopep, varScp} {} + OrderVarPreVertex(V3Graph* graphp, AstVarScope* varScp) + : OrderVarVertex{graphp, varScp} {} virtual ~OrderVarPreVertex() override = default; // METHODS @@ -188,8 +183,8 @@ public: class OrderVarPostVertex final : public OrderVarVertex { public: // CONSTRUCTOR - OrderVarPostVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp) - : OrderVarVertex{graphp, scopep, varScp} {} + OrderVarPostVertex(V3Graph* graphp, AstVarScope* varScp) + : OrderVarVertex{graphp, varScp} {} virtual ~OrderVarPostVertex() override = default; // METHODS @@ -204,8 +199,8 @@ public: class OrderVarPordVertex final : public OrderVarVertex { public: // CONSTRUCTOR - OrderVarPordVertex(V3Graph* graphp, AstScope* scopep, AstVarScope* varScp) - : OrderVarVertex{graphp, scopep, varScp} {} + OrderVarPordVertex(V3Graph* graphp, AstVarScope* varScp) + : OrderVarVertex{graphp, varScp} {} virtual ~OrderVarPordVertex() override = default; // METHODS @@ -325,16 +320,14 @@ class MTaskMoveVertex final : public V3GraphVertex { // or a var node, it can't be both. OrderLogicVertex* const m_logicp; // Logic represented by this vertex const OrderEitherVertex* const m_varp; // Var represented by this vertex - const AstScope* const m_scopep; const AstSenTree* const m_domainp; public: MTaskMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp, const OrderEitherVertex* varp, - const AstScope* scopep, const AstSenTree* domainp) + const AstSenTree* domainp) : V3GraphVertex{graphp} , m_logicp{logicp} , m_varp{varp} - , m_scopep{scopep} , m_domainp{domainp} { UASSERT(!(logicp && varp), "MTaskMoveVertex: logicp and varp may not both be set!\n"); } @@ -343,7 +336,7 @@ public: // ACCESSORS OrderLogicVertex* logicp() const { return m_logicp; } const OrderEitherVertex* varp() const { return m_varp; } - const AstScope* scopep() const { return m_scopep; } + const AstScope* scopep() const { return m_logicp ? m_logicp->scopep() : nullptr; } const AstSenTree* domainp() const { return m_domainp; } virtual string dotColor() const override { From e2525d84404fffd30acc7041754a446cfb57e26c Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 4 Jun 2022 12:22:11 -0400 Subject: [PATCH 010/177] Tests: Fix racy tests for develop-v5. --- test_regress/t/t_gen_forif.pl | 3 --- test_regress/t/t_gen_intdot.pl | 3 --- 2 files changed, 6 deletions(-) diff --git a/test_regress/t/t_gen_forif.pl b/test_regress/t/t_gen_forif.pl index 7c4d3f6e5..bc8663a80 100755 --- a/test_regress/t/t_gen_forif.pl +++ b/test_regress/t/t_gen_forif.pl @@ -10,9 +10,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -# FIXMEV5: fix -skip("This test is racy, needs fixing"); - compile( nc_flags2 => ['+access+r'], ); diff --git a/test_regress/t/t_gen_intdot.pl b/test_regress/t/t_gen_intdot.pl index 54e5c830d..b46d46042 100755 --- a/test_regress/t/t_gen_intdot.pl +++ b/test_regress/t/t_gen_intdot.pl @@ -10,9 +10,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -# FIXMEV5: fix -skip("This test is racy, needs fixing"); - compile( ); From 09f3f404624151d62b1dd1ea183586d1450cef54 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 4 Jun 2022 12:27:44 -0400 Subject: [PATCH 011/177] Fix clang-discovered missing comma. --- src/V3Ast.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/V3Ast.h b/src/V3Ast.h index 77a97407b..ee162ad1b 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -316,7 +316,7 @@ public: "POS", "NEG", "EVENT", - "DPIEXPORT" + "DPIEXPORT", "TRUE", "COMBO", "HYBRID", From aca9fd3bed12db3becb051f04c06d913e1c78de6 Mon Sep 17 00:00:00 2001 From: github action Date: Sat, 4 Jun 2022 16:30:41 +0000 Subject: [PATCH 012/177] Apply 'make format' --- src/V3Ast.h | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/V3Ast.h b/src/V3Ast.h index ee162ad1b..d5e1c6d31 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -310,20 +310,9 @@ public: return VEdgeType::ET_ILLEGAL; } const char* ascii() const { - static const char* const names[] = {"%E-edge", - "CHANGED", - "BOTH", - "POS", - "NEG", - "EVENT", - "DPIEXPORT", - "TRUE", - "COMBO", - "HYBRID", - "STATIC", - "INITIAL", - "FINAL", - "NEVER"}; + static const char* const names[] + = {"%E-edge", "CHANGED", "BOTH", "POS", "NEG", "EVENT", "DPIEXPORT", + "TRUE", "COMBO", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; return names[m_e]; } const char* verilogKwd() const { From ff1b9930fc8d8835ca4868f9d627e35c5b7b56c0 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 14 Jul 2022 11:06:20 +0100 Subject: [PATCH 013/177] Handle multiple external domains in V3Order Make the external domains provider of ordering populate an output vector, which then allows us to add multiple external sensitivities to combinational logic. --- src/V3Order.cpp | 35 ++++++++++++++++++++--------------- src/V3Order.h | 18 +++++++++++------- src/V3Sched.cpp | 25 ++++++++++++++----------- 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 291cc1a68..8a7f69be0 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -895,10 +895,10 @@ class OrderProcess final : VNDeleter { // Map from Trigger reference AstSenItem to the original AstSenTree const std::unordered_map& m_trigToSen; - // This is a function provided by the invoker of the ordering that can and provide additional + // This is a function provided by the invoker of the ordering that can provide additional // sensitivity expression that when triggered indicates the passed AstVarScope might have // changed external to the code being ordered. - const std::function m_externalDomain; + const V3Order::ExternalDomainsProvider m_externalDomains; SenTreeFinder m_finder; // Global AstSenTree manager AstSenTree* const m_deleteDomainp; // Dummy AstSenTree indicating needs deletion @@ -957,6 +957,8 @@ class OrderProcess final : VNDeleter { // Make a domain that merges the two domains AstSenTree* combineDomains(AstSenTree* ap, AstSenTree* bp) { + if (ap == m_deleteDomainp) return bp; + UASSERT_OBJ(bp != m_deleteDomainp, bp, "Should not be delete domain"); AstSenTree* const senTreep = ap->cloneTree(false); senTreep->addSensesp(bp->sensesp()->cloneTree(true)); V3Const::constifyExpensiveEdit(senTreep); // Remove duplicates @@ -974,11 +976,10 @@ class OrderProcess final : VNDeleter { // CONSTRUCTOR OrderProcess(AstNetlist* netlistp, OrderGraph& graph, const std::unordered_map& trigToSen, - const string& tag, bool slow, - std::function externalDomain) + const string& tag, bool slow, V3Order::ExternalDomainsProvider externalDomains) : m_graph{graph} , m_trigToSen{trigToSen} - , m_externalDomain{externalDomain} + , m_externalDomains{externalDomains} , m_finder{netlistp} , m_deleteDomainp{makeDeleteDomainSenTree(netlistp->fileline())} , m_tag{tag} @@ -994,8 +995,8 @@ public: main(AstNetlist* netlistp, OrderGraph& graph, const std::unordered_map& trigToSen, const string& tag, bool parallel, bool slow, - std::function externalDomain) { - OrderProcess visitor{netlistp, graph, trigToSen, tag, slow, externalDomain}; + V3Order::ExternalDomainsProvider externalDomains) { + OrderProcess visitor{netlistp, graph, trigToSen, tag, slow, externalDomains}; visitor.process(parallel); return std::move(visitor.m_result); } @@ -1046,6 +1047,9 @@ void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) { if (OrderLogicVertex* const lvtxp = dynamic_cast(vertexp)) { domainp = lvtxp->hybridp(); } + + std::vector externalDomainps; + for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { OrderEitherVertex* const fromVertexp = static_cast(edgep->fromp()); if (edgep->weight() && fromVertexp->domainMatters()) { @@ -1054,12 +1058,13 @@ void OrderProcess::processDomainsIterate(OrderEitherVertex* vertexp) { if (OrderVarVertex* const varVtxp = dynamic_cast(fromVertexp)) { AstVarScope* const vscp = varVtxp->vscp(); - if (AstSenTree* const externalDomainp = m_externalDomain(vscp)) { - UASSERT(!externalDomainp->hasCombo(), - "There should be no need for combinational domains"); - fromDomainp = fromDomainp == m_deleteDomainp - ? externalDomainp - : combineDomains(fromDomainp, externalDomainp); + // Add in any external domains + externalDomainps.clear(); + m_externalDomains(vscp, externalDomainps); + for (AstSenTree* const externalDomainp : externalDomainps) { + UASSERT_OBJ(!externalDomainp->hasCombo(), vscp, + "There should be no need for combinational domains"); + fromDomainp = combineDomains(fromDomainp, externalDomainp); } } @@ -1538,12 +1543,12 @@ AstCFunc* order(AstNetlist* netlistp, // const string& tag, // bool parallel, // bool slow, // - std::function externalDomain) { + ExternalDomainsProvider externalDomains) { // Order the code const std::unique_ptr graph = OrderBuildVisitor::process(netlistp, logic, trigToSen); const auto& nodeps = OrderProcess::main(netlistp, *graph.get(), trigToSen, tag, parallel, slow, - externalDomain); + externalDomains); // Create the result function AstScope* const scopeTopp = netlistp->topScopep()->scopep(); diff --git a/src/V3Order.h b/src/V3Order.h index 12ef3d2eb..9dc6a0fdf 100644 --- a/src/V3Order.h +++ b/src/V3Order.h @@ -38,13 +38,17 @@ struct LogicByScope; namespace V3Order { -AstCFunc* order(AstNetlist* netlistp, // - const std::vector& logic, // - const std::unordered_map& trigToSen, - const string& tag, // - bool parallel, // - bool slow, // - std::function externalDomain); +using ExternalDomainsProvider = std::function&)>; + +AstCFunc* order( + AstNetlist* netlistp, // + const std::vector& logic, // + const std::unordered_map& trigToSen, + const string& tag, // + bool parallel, // + bool slow, // + ExternalDomainsProvider externalDomains + = [](const AstVarScope*, std::vector&) {}); }; // namespace V3Order diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index a44fe75c1..129761da9 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -699,9 +699,9 @@ void createSettle(AstNetlist* netlistp, SenExprBuilder& senExprBulider, AstSenTree* const inputChanged = trig.createTriggerSenTree(netlistp, firstIterationTrigger); // Create and the body function - AstCFunc* const stlFuncp - = V3Order::order(netlistp, {&comb, &hybrid}, trigToSen, "stl", false, true, - [=](const AstVarScope*) { return inputChanged; }); + AstCFunc* const stlFuncp = V3Order::order( + netlistp, {&comb, &hybrid}, trigToSen, "stl", false, true, + [=](const AstVarScope*, std::vector& out) { out.push_back(inputChanged); }); splitCheck(stlFuncp); // Create the eval loop @@ -763,10 +763,13 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde AstSenTree* const inputChanged = trig.createTriggerSenTree(netlistp, firstIterationTrigger); // Create and Order the body function - AstCFunc* const icoFuncp = V3Order::order( - netlistp, {&logic}, trigToSen, "ico", false, false, [=](const AstVarScope* vscp) { - return vscp->scopep()->isTop() && vscp->varp()->isNonOutput() ? inputChanged : nullptr; - }); + AstCFunc* const icoFuncp + = V3Order::order(netlistp, {&logic}, trigToSen, "ico", false, false, + [=](const AstVarScope* vscp, std::vector& out) { + if (vscp->scopep()->isTop() && vscp->varp()->isNonOutput()) { + out.push_back(inputChanged); + } + }); splitCheck(icoFuncp); // Create the eval loop @@ -1016,7 +1019,7 @@ void schedule(AstNetlist* netlistp) { AstCFunc* const actFuncp = V3Order::order( netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct, - "act", false, false, [](const AstVarScope*) { return nullptr; }); + "act", false, false); splitCheck(actFuncp); if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-act"); @@ -1030,9 +1033,9 @@ void schedule(AstNetlist* netlistp) { std::unordered_map trigToSenNba; invertAndMergeSenTreeMap(trigToSenNba, nbaTrigMap); - AstCFunc* const nbaFuncp = V3Order::order( - netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba, "nba", - v3Global.opt.mtasks(), false, [](const AstVarScope*) { return nullptr; }); + AstCFunc* const nbaFuncp + = V3Order::order(netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba, + "nba", v3Global.opt.mtasks(), false); splitCheck(nbaFuncp); netlistp->evalNbap(nbaFuncp); // Remember for V3LifePost if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-nba"); From 6a7bda6910079451cb19afc1b527ec1073322316 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 14 Jul 2022 12:35:44 +0100 Subject: [PATCH 014/177] Correctly schedule combinational logic driven from DPI exports. Fixes #3429. --- src/V3Ast.h | 12 ++-- src/V3AstNodes.h | 15 ++--- src/V3Dead.cpp | 5 ++ src/V3Descope.cpp | 4 -- src/V3Order.cpp | 31 +--------- src/V3Sched.cpp | 79 +++++++++++++++++++------ src/V3SchedPartition.cpp | 29 +++++---- src/V3SchedReplicate.cpp | 3 +- src/V3Task.cpp | 59 +++++++----------- test_regress/t/t_order_dpi_export_6.cpp | 38 ++++++++++++ test_regress/t/t_order_dpi_export_6.pl | 24 ++++++++ test_regress/t/t_order_dpi_export_6.v | 48 +++++++++++++++ test_regress/t/t_order_dpi_export_7.cpp | 37 ++++++++++++ test_regress/t/t_order_dpi_export_7.pl | 24 ++++++++ test_regress/t/t_order_dpi_export_7.v | 39 ++++++++++++ 15 files changed, 330 insertions(+), 117 deletions(-) create mode 100644 test_regress/t/t_order_dpi_export_6.cpp create mode 100755 test_regress/t/t_order_dpi_export_6.pl create mode 100644 test_regress/t/t_order_dpi_export_6.v create mode 100644 test_regress/t/t_order_dpi_export_7.cpp create mode 100755 test_regress/t/t_order_dpi_export_7.pl create mode 100644 test_regress/t/t_order_dpi_export_7.v diff --git a/src/V3Ast.h b/src/V3Ast.h index 9fb5f55e0..3b98ec47b 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -266,7 +266,6 @@ public: ET_POSEDGE, ET_NEGEDGE, ET_EVENT, // VlEvent::isFired - ET_DPIEXPORT, // Used exclusively to check the AstNetlist::dpiExportTriggerp() // Involving an expression ET_TRUE, // @@ -287,7 +286,6 @@ public: true, // ET_POSEDGE true, // ET_NEGEDGE true, // ET_EVENT - true, // ET_DPIEXPORT true, // ET_TRUE false, // ET_COMBO @@ -311,14 +309,14 @@ public: } const char* ascii() const { static const char* const names[] - = {"%E-edge", "CHANGED", "BOTH", "POS", "NEG", "EVENT", "DPIEXPORT", - "TRUE", "COMBO", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; + = {"%E-edge", "CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", + "COMBO", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; return names[m_e]; } const char* verilogKwd() const { - static const char* const names[] = { - "%E-edge", "[changed]", "edge", "posedge", "negedge", "[event]", "[dpiexport]", - "[true]", "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; + static const char* const names[] + = {"%E-edge", "[changed]", "edge", "posedge", "negedge", "[event]", "[true]", + "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; return names[m_e]; } // Return true iff this and the other have mutually exclusive transitions diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index 40dfe5e3e..ec215b54c 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -2001,6 +2001,7 @@ private: bool m_trace : 1; // Trace this variable bool m_isLatched : 1; // Not assigned in all control paths of combo always bool m_isForceable : 1; // May be forced/released externally from user C code + bool m_isWrittenByDpi : 1; // This variable can be written by a DPI Export void init() { m_ansi = false; @@ -2040,6 +2041,7 @@ private: m_trace = false; m_isLatched = false; m_isForceable = false; + m_isWrittenByDpi = false; m_attrClocker = VVarAttrClocker::CLOCKER_UNKNOWN; } @@ -2205,6 +2207,8 @@ public: void isLatched(bool flag) { m_isLatched = flag; } bool isForceable() const { return m_isForceable; } void setForceable() { m_isForceable = true; } + bool isWrittenByDpi() const { return m_isWrittenByDpi; } + void setWrittenByDpi() { m_isWrittenByDpi = true; } // METHODS virtual void name(const string& name) override { m_name = name; } virtual void tag(const string& text) override { m_tag = text; } @@ -3638,17 +3642,6 @@ public: virtual bool brokeLhsMustBeLvalue() const override { return true; } }; -class AstDpiExportUpdated final : public AstNodeStmt { - // Denotes that the referenced variable may have been updated via a DPI Export -public: - AstDpiExportUpdated(FileLine* fl, AstVarScope* varScopep) - : ASTGEN_SUPER_DpiExportUpdated(fl) { - addOp1p(new AstVarRef{fl, varScopep, VAccess::WRITE}); - } - ASTNODE_NODE_FUNCS(DpiExportUpdated) - AstVarScope* varScopep() const { return VN_AS(op1p(), VarRef)->varScopep(); } -}; - class AstExprStmt final : public AstNodeMath { // Perform a statement, often assignment inside an expression/math node, // the parent gets passed the 'resultp()'. diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index e0485ae99..243c580bf 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -433,6 +433,11 @@ public: // Operate on whole netlist iterate(nodep); + if (AstVarScope* const vscp = nodep->dpiExportTriggerp()) { + vscp->user1Inc(); + vscp->varp()->user1Inc(); + } + deadCheckVar(); // We only eliminate scopes when in a flattened structure // Otherwise we have no easy way to know if a scope is used diff --git a/src/V3Descope.cpp b/src/V3Descope.cpp index c43c0352f..b3bf25b35 100644 --- a/src/V3Descope.cpp +++ b/src/V3Descope.cpp @@ -184,10 +184,6 @@ private: } // VISITORS - virtual void visit(AstNetlist* nodep) override { - nodep->dpiExportTriggerp(nullptr); - iterateChildren(nodep); - } virtual void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); { diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 8a7f69be0..314bf1ea7 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -177,15 +177,8 @@ class OrderBuildVisitor final : public VNVisitor { AstUser1Allocator m_orderUser; // STATE - // The ordering graph built by this visitor - OrderGraph* const m_graphp = new OrderGraph; - // Singleton DPI Export trigger event vertex - OrderVarVertex* const m_dpiExportTriggerVxp - = v3Global.rootp()->dpiExportTriggerp() - ? getVarVertex(v3Global.rootp()->dpiExportTriggerp(), VarVertexType::STD) - : nullptr; - - OrderLogicVertex* m_logicVxp = nullptr; // Current loic block being analyzed + OrderGraph* const m_graphp = new OrderGraph; // The ordering graph built by this visitor + OrderLogicVertex* m_logicVxp = nullptr; // Current logic block being analyzed // Map from Trigger reference AstSenItem to the original AstSenTree const std::unordered_map& m_trigToSen; @@ -411,25 +404,7 @@ class OrderBuildVisitor final : public VNVisitor { } } } - virtual void visit(AstDpiExportUpdated* nodep) override { - // This is under a logic block (AstAlways) sensitive to a change in the DPI export trigger. - // We just need to add an edge to the enclosing logic vertex (the vertex for the - // AstAlways). - OrderVarVertex* const varVxp = getVarVertex(nodep->varScopep(), VarVertexType::STD); - new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); - // Only used for ordering, so we can get rid of it here - nodep->unlinkFrBack(); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } - virtual void visit(AstCCall* nodep) override { - // Calls to 'context' imported DPI function may call DPI exported functions - if (m_dpiExportTriggerVxp && nodep->funcp()->dpiImportWrapper() - && nodep->funcp()->dpiContext()) { - UASSERT_OBJ(m_logicVxp, nodep, "Call not under logic"); - new OrderEdge(m_graphp, m_logicVxp, m_dpiExportTriggerVxp, WEIGHT_NORMAL); - } - iterateChildren(nodep); - } + virtual void visit(AstCCall* nodep) override { iterateChildren(nodep); } //--- Logic akin to SystemVerilog Processes (AstNodeProcedure) virtual void visit(AstInitial* nodep) override { // LCOV_EXCL_START diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 129761da9..44fbd6745 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -264,7 +264,6 @@ class SenExprBuilder final { // STATE AstCFunc* const m_initp; // The initialization function AstScope* const m_scopeTopp; // Top level scope - AstVarScope* const m_dpiExportTriggerp; // The DPI export trigger variable std::vector m_updates; // Update assignments @@ -362,13 +361,6 @@ class SenExprBuilder final { callp->dtypeSetBit(); return {callp, false}; } - case VEdgeType::ET_DPIEXPORT: { - // If the DPI export trigger is checked, always clear it after trigger computation - UASSERT_OBJ(m_dpiExportTriggerp, senItemp, "ET_DPIEXPORT without trigger variable"); - AstVarRef* const refp = new AstVarRef{flp, m_dpiExportTriggerp, VAccess::WRITE}; - m_updates.push_back(new AstAssign{flp, refp, new AstConst{flp, AstConst::BitFalse{}}}); - return {currp(), false}; - } default: // LCOV_EXCL_START senItemp->v3fatalSrc("Unknown edge type"); return {nullptr, false}; @@ -401,8 +393,7 @@ public: // CONSTRUCTOR SenExprBuilder(AstNetlist* netlistp, AstCFunc* initp) : m_initp{initp} - , m_scopeTopp{netlistp->topScopep()->scopep()} - , m_dpiExportTriggerp{netlistp->dpiExportTriggerp()} {} + , m_scopeTopp{netlistp->topScopep()->scopep()} {} }; //============================================================================ @@ -446,6 +437,21 @@ struct TriggerKit { flp, callp, new AstEq{flp, new AstVarRef{flp, counterp, VAccess::READ}, new AstConst{flp, 0}}}); } + + // Utility to set then clear the dpiExportTrigger trigger + void addDpiExportTriggerAssignment(AstVarScope* dpiExportTriggerVscp, uint32_t index) const { + FileLine* const flp = dpiExportTriggerVscp->fileline(); + AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE}; + AstCMethodHard* const callp + = new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, index}}; + callp->dtypeSetBit(); + callp->pure(true); + AstNode* stmtp + = new AstAssign{flp, callp, new AstVarRef{flp, dpiExportTriggerVscp, VAccess::READ}}; + stmtp->addNext(new AstAssign{flp, new AstVarRef{flp, dpiExportTriggerVscp, VAccess::WRITE}, + new AstConst{flp, AstConst::BitFalse{}}}); + m_funcp->stmtsp()->addHereThisAsNext(stmtp); + } }; //============================================================================ @@ -743,15 +749,23 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde }); } - // We have an extra trigger denoting this is the first iteration of the ico loop - constexpr unsigned firstIterationTrigger = 0; - constexpr unsigned extraTriggers = firstIterationTrigger + 1; + // We have some extra trigger denoting external conditions + AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp(); + + unsigned extraTriggers = 0; + const unsigned firstIterationTrigger = extraTriggers++; + const unsigned dpiExportTriggerIndex + = dpiExportTriggerVscp ? extraTriggers++ : std::numeric_limits::max(); // Gather the relevant sensitivity expressions and create the trigger kit const auto& senTreeps = getSenTreesUsedBy({&logic}); const TriggerKit& trig = createTriggers(netlistp, senExprBuilder, senTreeps, "ico", extraTriggers); + if (dpiExportTriggerVscp) { + trig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex); + } + // Remap sensitivities remapSensitivities(logic, trig.m_map); @@ -759,9 +773,13 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde std::unordered_map trigToSen; invertAndMergeSenTreeMap(trigToSen, trig.m_map); - // First trigger is for pure combinational triggers (first iteration) + // The trigger top level inputs (first iteration) AstSenTree* const inputChanged = trig.createTriggerSenTree(netlistp, firstIterationTrigger); + // The DPI Export trigger + AstSenTree* const dpiExportTriggered + = trig.createTriggerSenTree(netlistp, dpiExportTriggerIndex); + // Create and Order the body function AstCFunc* const icoFuncp = V3Order::order(netlistp, {&logic}, trigToSen, "ico", false, false, @@ -769,6 +787,7 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde if (vscp->scopep()->isTop() && vscp->varp()->isNonOutput()) { out.push_back(inputChanged); } + if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered); }); splitCheck(icoFuncp); @@ -964,10 +983,22 @@ void schedule(AstNetlist* netlistp) { if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-ico"); // Step 8: Create the pre/act/nba triggers + AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp(); + + unsigned extraTriggers = 0; + // We may have an extra trigger for variable updated in DPI exports + const unsigned dpiExportTriggerIndex + = dpiExportTriggerVscp ? extraTriggers++ : std::numeric_limits::max(); + const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre, // &logicRegions.m_act, // &logicRegions.m_nba}); - const TriggerKit& actTrig = createTriggers(netlistp, senExprBuilder, senTreeps, "act", 0); + const TriggerKit& actTrig + = createTriggers(netlistp, senExprBuilder, senTreeps, "act", extraTriggers); + + if (dpiExportTriggerVscp) { + actTrig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex); + } AstTopScope* const topScopep = netlistp->topScopep(); AstScope* const scopeTopp = topScopep->scopep(); @@ -1017,9 +1048,15 @@ void schedule(AstNetlist* netlistp) { invertAndMergeSenTreeMap(trigToSenAct, preTrigMap); invertAndMergeSenTreeMap(trigToSenAct, actTrigMap); + // The DPI Export trigger AstSenTree + AstSenTree* const dpiExportTriggered + = actTrig.createTriggerSenTree(netlistp, dpiExportTriggerIndex); + AstCFunc* const actFuncp = V3Order::order( netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct, - "act", false, false); + "act", false, false, [=](const AstVarScope* vscp, std::vector& out) { + if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered); + }); splitCheck(actFuncp); if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-act"); @@ -1033,9 +1070,11 @@ void schedule(AstNetlist* netlistp) { std::unordered_map trigToSenNba; invertAndMergeSenTreeMap(trigToSenNba, nbaTrigMap); - AstCFunc* const nbaFuncp - = V3Order::order(netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba, - "nba", v3Global.opt.mtasks(), false); + AstCFunc* const nbaFuncp = V3Order::order( + netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba, "nba", + v3Global.opt.mtasks(), false, [=](const AstVarScope* vscp, std::vector& out) { + if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered); + }); splitCheck(nbaFuncp); netlistp->evalNbap(nbaFuncp); // Remember for V3LifePost if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-nba"); @@ -1045,6 +1084,8 @@ void schedule(AstNetlist* netlistp) { splitCheck(initp); + netlistp->dpiExportTriggerp(nullptr); + V3Global::dumpCheckGlobalTree("sched", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); } diff --git a/src/V3SchedPartition.cpp b/src/V3SchedPartition.cpp index 88125ee35..29fab6ec0 100644 --- a/src/V3SchedPartition.cpp +++ b/src/V3SchedPartition.cpp @@ -130,11 +130,22 @@ class SchedGraphBuilder final : public VNVisitor { AstSenTree* m_senTreep = nullptr; // AstSenTree of the current AstActive // Predicate for whether a read of the given variable triggers this block std::function m_readTriggersThisLogic; + // The DPI export trigger variable, if any + AstVarScope* const m_dpiExportTriggerp = v3Global.rootp()->dpiExportTriggerp(); VL_DEBUG_FUNC; SchedVarVertex* getVarVertex(AstVarScope* vscp) const { - if (!vscp->user1p()) vscp->user1p(new SchedVarVertex{m_graphp, vscp}); + if (!vscp->user1p()) { + SchedVarVertex* const vtxp = new SchedVarVertex{m_graphp, vscp}; + // If this variable can be written via a DPI export, add a source edge from the + // DPI export trigger vertex. This ensures calls to DPI exports that might write a + // clock end up in the 'act' region. + if (vscp->varp()->isWrittenByDpi()) { + new V3GraphEdge{m_graphp, getVarVertex(m_dpiExportTriggerp), vtxp, 1}; + } + vscp->user1p(vtxp); + } return vscp->user1u().to(); } @@ -154,8 +165,7 @@ class SchedGraphBuilder final : public VNVisitor { // Connect up the variable references senItemp->sensp()->foreach([&](AstVarRef* refp) { - SchedVarVertex* const varVtxp = getVarVertex(refp->varScopep()); - new V3GraphEdge{m_graphp, varVtxp, vtxp, 1}; + new V3GraphEdge{m_graphp, getVarVertex(refp->varScopep()), vtxp, 1}; }); // Store back to hash map so we can find it next time @@ -188,21 +198,20 @@ class SchedGraphBuilder final : public VNVisitor { // Add edges based on references nodep->foreach([=](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}; + new V3GraphEdge{m_graphp, getVarVertex(vscp), logicVtxp, 10}; } if (vrefp->access().isWriteOrRW()) { - new V3GraphEdge{m_graphp, logicVtxp, varVtxp, 10}; + new V3GraphEdge{m_graphp, logicVtxp, getVarVertex(vscp), 10}; } }); - // If the logic calls a DPI import, it might fire the DPI Export trigger - if (AstVarScope* const dpiExporTrigger = v3Global.rootp()->dpiExportTriggerp()) { + // If the logic calls a 'context' DPI import, it might fire the DPI Export trigger + if (m_dpiExportTriggerp) { nodep->foreach([=](const AstCCall* callp) { if (!callp->funcp()->dpiImportWrapper()) return; - SchedVarVertex* const varVtxp = getVarVertex(dpiExporTrigger); - new V3GraphEdge{m_graphp, logicVtxp, varVtxp, 10}; + if (!callp->funcp()->dpiContext()) return; + new V3GraphEdge{m_graphp, logicVtxp, getVarVertex(m_dpiExportTriggerp), 10}; }); } } diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 78a548ea9..8a8c74d68 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -120,7 +120,8 @@ public: VarVertex(V3Graph* graphp, AstVarScope* vscp) : Vertex{graphp} , m_vscp{vscp} { - if (isTopInput()) addDrivingRegions(INPUT); + // Top level inputs are + if (isTopInput() || varp()->isWrittenByDpi()) addDrivingRegions(INPUT); } AstVarScope* vscp() const { return m_vscp; } AstVar* varp() const { return m_vscp->varp(); } diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 2122a59e2..d4307ab3c 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -1055,7 +1055,7 @@ private: } } - AstVarScope* makeDpiExporTrigger() { + AstVarScope* getDpiExporTrigger() { AstNetlist* const netlistp = v3Global.rootp(); AstVarScope* dpiExportTriggerp = netlistp->dpiExportTriggerp(); if (!dpiExportTriggerp) { @@ -1271,52 +1271,37 @@ private: // Mark all non-local variables written by the DPI exported function as being updated // by DPI exports. This ensures correct ordering and change detection later. - // Gather non-local variables written by the exported function - std::vector writtenps; - { - const VNUser5InUse user5InUse; // AstVarScope::user5 -> Already added variable - cfuncp->foreach([&writtenps](AstVarRef* refp) { - if (refp->access().isReadOnly()) return; // Ignore read reference - AstVarScope* const varScopep = refp->varScopep(); - if (varScopep->user5()) return; // Ignore already added variable - varScopep->user5(true); // Mark as already added - // Note: We are ignoring function locals as they should not be referenced - // anywhere outside of the enclosing AstCFunc, and therefore they are - // irrelevant for code ordering. This is an optimization to avoid adding - // useless nodes to the ordering graph in V3Order. - if (varScopep->varp()->isFuncLocal()) return; - writtenps.push_back(varScopep); - }); - } + // Mark non-local variables written by the exported function + bool writesNonLocals = false; + cfuncp->foreach([&writesNonLocals](AstVarRef* refp) { + if (refp->access().isReadOnly()) return; // Ignore read reference + AstVar* const varp = refp->varScopep()->varp(); + // We are ignoring function locals as they should not be referenced anywhere + // outside the enclosing AstCFunc, hence they are irrelevant for code ordering. + if (varp->isFuncLocal()) return; + // Mark it as written by DPI export + varp->setWrittenByDpi(); + // Remember we had some + writesNonLocals = true; + }); - if (!writtenps.empty()) { - AstVarScope* const dpiExportTriggerp = makeDpiExporTrigger(); - FileLine* const fl = cfuncp->fileline(); + // If this DPI export writes some non-local variables, set the DPI Export Trigger flag + // in the function. + if (writesNonLocals) { + AstVarScope* const dpiExportTriggerp = getDpiExporTrigger(); + FileLine* const flp = cfuncp->fileline(); // Set DPI export trigger flag every time the DPI export is called. AstAssign* const assignp - = new AstAssign{fl, new AstVarRef{fl, dpiExportTriggerp, VAccess::WRITE}, - new AstConst{fl, AstConst::BitTrue{}}}; + = new AstAssign{flp, new AstVarRef{flp, dpiExportTriggerp, VAccess::WRITE}, + new AstConst{flp, AstConst::BitTrue{}}}; + // Add as first statement (to avoid issues with early returns) to exported function if (cfuncp->stmtsp()) { cfuncp->stmtsp()->addHereThisAsNext(assignp); } else { cfuncp->addStmtsp(assignp); } - - // Add an always block sensitive to the DPI export trigger flag, and add an - // AstDpiExportUpdated node under it for each variable that are writen by the - // exported function. - AstAlways* const alwaysp = new AstAlways{ - fl, VAlwaysKwd::ALWAYS, - new AstSenTree{ - fl, new AstSenItem{fl, VEdgeType::ET_DPIEXPORT, - new AstVarRef{fl, dpiExportTriggerp, VAccess::READ}}}, - nullptr}; - for (AstVarScope* const varScopep : writtenps) { - alwaysp->addStmtp(new AstDpiExportUpdated{fl, varScopep}); - } - m_scopep->addActivep(alwaysp); } } diff --git a/test_regress/t/t_order_dpi_export_6.cpp b/test_regress/t/t_order_dpi_export_6.cpp new file mode 100644 index 000000000..a24c6986f --- /dev/null +++ b/test_regress/t/t_order_dpi_export_6.cpp @@ -0,0 +1,38 @@ +// -*- 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 + +#include +#include + +void toggle_other_clk(svBit val) { set_other_clk(val); } + +int main(int argc, char* argv[]) { + Vt_order_dpi_export_6* const tb = new Vt_order_dpi_export_6; + tb->contextp()->commandArgs(argc, argv); + bool clk = true; + + while (!tb->contextp()->gotFinish()) { + // Timeout + if (tb->contextp()->time() > 100000) break; + // Toggle and set main clock + clk = !clk; + tb->clk = clk; + // Eval + tb->eval(); + // Advance time + tb->contextp()->timeInc(500); + } + + delete tb; + return 0; +} diff --git a/test_regress/t/t_order_dpi_export_6.pl b/test_regress/t/t_order_dpi_export_6.pl new file mode 100755 index 000000000..e76a9afcd --- /dev/null +++ b/test_regress/t/t_order_dpi_export_6.pl @@ -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 2003 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 + +scenarios(vlt_all => 1); + +compile( + make_top_shell => 0, + make_main => 0, + verilator_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_order_dpi_export_6.v b/test_regress/t/t_order_dpi_export_6.v new file mode 100644 index 000000000..36a33d141 --- /dev/null +++ b/test_regress/t/t_order_dpi_export_6.v @@ -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 + +module testbench( + /*AUTOARG*/ + // Inputs + clk + ); + + input clk; // Top level input clock + logic other_clk; // Dependent clock set via DPI + + export "DPI-C" function set_other_clk; + function void set_other_clk(bit val); + other_clk = val; + endfunction; + + bit even_other = 1; + bit current_even_other = 1; + import "DPI-C" context function void toggle_other_clk(bit val); + always @(posedge clk) begin + even_other <= ~even_other; + current_even_other = even_other; + toggle_other_clk(even_other); + end + + int n = 0; + + always @(edge other_clk) begin + // This always block needs to evaluate before the NBA to even_other + // above is committed, as setting clocks via the set_other_clk uses + // blocking assignment. + if (even_other !== current_even_other) $stop; + $display("t=%t n=%d", $time, n); + if ($time != (2*n+1) * 500) $stop; + if (n == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + n += 1; + end + +endmodule diff --git a/test_regress/t/t_order_dpi_export_7.cpp b/test_regress/t/t_order_dpi_export_7.cpp new file mode 100644 index 000000000..a6f08f644 --- /dev/null +++ b/test_regress/t/t_order_dpi_export_7.cpp @@ -0,0 +1,37 @@ +// -*- 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 + +#include +#include + +int main(int argc, char* argv[]) { + Vt_order_dpi_export_7* const tb = new Vt_order_dpi_export_7; + tb->contextp()->commandArgs(argc, argv); + bool clk = true; + + while (!tb->contextp()->gotFinish()) { + // Timeout + if (tb->contextp()->time() > 100000) break; + // Toggle and set clock + svSetScope(svGetScopeFromName("TOP.testbench")); + clk = !clk; + set_inputs(clk); + // Eval + tb->eval(); + // Advance time + tb->contextp()->timeInc(500); + } + + delete tb; + return 0; +} diff --git a/test_regress/t/t_order_dpi_export_7.pl b/test_regress/t/t_order_dpi_export_7.pl new file mode 100755 index 000000000..e76a9afcd --- /dev/null +++ b/test_regress/t/t_order_dpi_export_7.pl @@ -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 2003 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 + +scenarios(vlt_all => 1); + +compile( + make_top_shell => 0, + make_main => 0, + verilator_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_order_dpi_export_7.v b/test_regress/t/t_order_dpi_export_7.v new file mode 100644 index 000000000..575b88b50 --- /dev/null +++ b/test_regress/t/t_order_dpi_export_7.v @@ -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 testbench; + + logic clk; + logic data; + + export "DPI-C" function set_inputs; + function void set_inputs(bit val); + clk = val; + data = val; + endfunction; + + // This needs to be in the 'ico' region. Written with $c1 to prevent + // gate optimization. + wire invdata = $c1(1) ^ data; + + int n = 0; + + always @(edge clk) begin + // The combinational update needs to have take effect (in the 'ico' + // region), before this always block is executed + if (invdata != ~data) $stop; + $display("t=%t n=%d", $time, n); + if ($time != (1*n+1) * 500) $stop; + if (n == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + n += 1; + end + +endmodule From f37cc2353d03b3f812c24fd0687c4baf462f2639 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 14 Jul 2022 15:49:00 +0100 Subject: [PATCH 015/177] Fix standard library incldues --- src/V3Sched.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 44fbd6745..c5cb2d4d9 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -46,8 +46,8 @@ #include "V3Stats.h" #include "V3UniqueNames.h" -#include "unordered_map" -#include "unordered_set" +#include +#include namespace V3Sched { From 3f19ba15549c62d5a0000d160337f32ee30f1828 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 14 Jul 2022 16:06:15 +0100 Subject: [PATCH 016/177] Improve handling of extra trigges in V3Sched. Add utility class for allocation, and add human readable text to debug code. --- src/V3Sched.cpp | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index c5cb2d4d9..476453434 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -454,19 +454,37 @@ struct TriggerKit { } }; +//============================================================================ +// Utility for extra trigger allocation + +class ExtraTriggers final { + std::vector m_descriptions; // Human readable descirption of extra triggers + +public: + ExtraTriggers() = default; + + size_t allocate(const string& description) { + const size_t index = m_descriptions.size(); + m_descriptions.push_back(description); + return index; + } + size_t size() const { return m_descriptions.size(); } + const string& description(size_t index) const { return m_descriptions[index]; } +}; + //============================================================================ // Create a TRIGGERVEC and the related TriggerKit for the given AstSenTree vector const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBuilder, std::vector senTreeps, const string& name, - unsigned extra, bool slow = false) { + const ExtraTriggers& extraTriggers, bool slow = false) { AstTopScope* const topScopep = netlistp->topScopep(); AstScope* const scopeTopp = topScopep->scopep(); FileLine* const flp = scopeTopp->fileline(); std::unordered_map map; - const uint32_t nTriggers = senTreeps.size() + extra; + const uint32_t nTriggers = senTreeps.size() + extraTriggers.size(); // Create the TRIGGERVEC variable AstBasicDType* const tDtypep = new AstBasicDType(flp, VBasicDTypeKwd::TRIGGERVEC, @@ -517,10 +535,12 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui }; // Add a print for each of the extra triggers - for (unsigned i = 0; i < extra; ++i) addDebug(i); + for (unsigned i = 0; i < extraTriggers.size(); ++i) { + addDebug(i, "Internal '" + name + "' trigger - " + extraTriggers.description(i)); + } // Add trigger computation - uint32_t triggerNumber = extra; + uint32_t triggerNumber = extraTriggers.size(); AstNode* initialTrigsp = nullptr; for (const AstSenTree* const senTreep : senTreeps) { UASSERT_OBJ(senTreep->hasClocked() || senTreep->hasHybrid(), senTreep, @@ -686,8 +706,8 @@ void createSettle(AstNetlist* netlistp, SenExprBuilder& senExprBulider, if (comb.empty() && hybrid.empty()) return; // We have an extra trigger denoting this is the first iteration of the settle loop - constexpr unsigned firstIterationTrigger = 0; - constexpr unsigned extraTriggers = firstIterationTrigger + 1; + ExtraTriggers extraTriggers; + const size_t firstIterationTrigger = extraTriggers.allocate("first iteration"); // Gather the relevant sensitivity expressions and create the trigger kit const auto& senTreeps = getSenTreesUsedBy({&comb, &hybrid}); @@ -752,10 +772,11 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde // We have some extra trigger denoting external conditions AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp(); - unsigned extraTriggers = 0; - const unsigned firstIterationTrigger = extraTriggers++; - const unsigned dpiExportTriggerIndex - = dpiExportTriggerVscp ? extraTriggers++ : std::numeric_limits::max(); + ExtraTriggers extraTriggers; + const size_t firstIterationTrigger = extraTriggers.allocate("first iteration"); + const size_t dpiExportTriggerIndex = dpiExportTriggerVscp + ? extraTriggers.allocate("DPI export trigger") + : std::numeric_limits::max(); // Gather the relevant sensitivity expressions and create the trigger kit const auto& senTreeps = getSenTreesUsedBy({&logic}); @@ -985,10 +1006,11 @@ void schedule(AstNetlist* netlistp) { // Step 8: Create the pre/act/nba triggers AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp(); - unsigned extraTriggers = 0; // We may have an extra trigger for variable updated in DPI exports - const unsigned dpiExportTriggerIndex - = dpiExportTriggerVscp ? extraTriggers++ : std::numeric_limits::max(); + ExtraTriggers extraTriggers; + const size_t dpiExportTriggerIndex = dpiExportTriggerVscp + ? extraTriggers.allocate("DPI export trigger") + : std::numeric_limits::max(); const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre, // &logicRegions.m_act, // From 00c1f67c57f786b57096f058424acb8f8de622b8 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 14 Jul 2022 16:28:09 +0100 Subject: [PATCH 017/177] Make trigger dumping functions always Slow code --- src/V3Sched.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 476453434..2633ed8f4 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -495,8 +495,8 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui // Create the trigger computation function AstCFunc* const funcp = makeSubFunction(netlistp, "_eval_triggers__" + name, slow); - // Create the trigger dump function (for debugging) - AstCFunc* const dumpp = makeSubFunction(netlistp, "_dump_triggers__" + name, slow); + // Create the trigger dump function (for debugging, always 'slow') + AstCFunc* const dumpp = makeSubFunction(netlistp, "_dump_triggers__" + name, true); dumpp->ifdef("VL_DEBUG"); // Add a print to the dumping function if there are no triggers pending From 3773e2ef9595d7d4b0a467a2b3c0150e5d440e09 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 15 Jul 2022 16:18:41 +0100 Subject: [PATCH 018/177] Simplify primary input checks --- src/V3Sched.cpp | 7 +++---- src/V3SchedReplicate.cpp | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 2633ed8f4..5c7b43f15 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -805,10 +805,9 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde AstCFunc* const icoFuncp = V3Order::order(netlistp, {&logic}, trigToSen, "ico", false, false, [=](const AstVarScope* vscp, std::vector& out) { - if (vscp->scopep()->isTop() && vscp->varp()->isNonOutput()) { - out.push_back(inputChanged); - } - if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered); + AstVar* const varp = vscp->varp(); + if (varp->isPrimaryInish()) out.push_back(inputChanged); + if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered); }); splitCheck(icoFuncp); diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 8a8c74d68..d047045f8 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -121,16 +121,15 @@ public: : Vertex{graphp} , m_vscp{vscp} { // Top level inputs are - if (isTopInput() || varp()->isWrittenByDpi()) addDrivingRegions(INPUT); + if (varp()->isPrimaryInish() || varp()->isWrittenByDpi()) 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"; } + string dotShape() const override { return varp()->isPrimaryInish() ? "invhouse" : "ellipse"; } }; class Graph final : public V3Graph {}; From 5a1f1796d73030a285250d9c0ddc4029a2c69577 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 15 Jul 2022 16:16:43 +0100 Subject: [PATCH 019/177] Fix t/t_public_{clk,src}.pl after merge of master --- src/V3Sched.cpp | 4 +++- src/V3SchedReplicate.cpp | 4 +++- test_regress/t/t_inst_tree_inl0_pub1.pl | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 5c7b43f15..c5e44719b 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -806,7 +806,9 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde = V3Order::order(netlistp, {&logic}, trigToSen, "ico", false, false, [=](const AstVarScope* vscp, std::vector& out) { AstVar* const varp = vscp->varp(); - if (varp->isPrimaryInish()) out.push_back(inputChanged); + if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) { + out.push_back(inputChanged); + } if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered); }); splitCheck(icoFuncp); diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index d047045f8..76c3c564b 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -121,7 +121,9 @@ public: : Vertex{graphp} , m_vscp{vscp} { // Top level inputs are - if (varp()->isPrimaryInish() || varp()->isWrittenByDpi()) addDrivingRegions(INPUT); + if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi()) { + addDrivingRegions(INPUT); + } } AstVarScope* vscp() const { return m_vscp; } AstVar* varp() const { return m_vscp->varp(); } diff --git a/test_regress/t/t_inst_tree_inl0_pub1.pl b/test_regress/t/t_inst_tree_inl0_pub1.pl index 3bd8daa93..107b1e56d 100755 --- a/test_regress/t/t_inst_tree_inl0_pub1.pl +++ b/test_regress/t/t_inst_tree_inl0_pub1.pl @@ -42,7 +42,7 @@ if ($Self->{vlt_all}) { # We expect to combine sequent functions across multiple instances of # l2, l3, l4, l5. If this number drops, please confirm this has not broken. file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, - ($Self->{vltmt} ? 70 : 52)); + ($Self->{vltmt} ? 85 : 67)); # Everything should use relative references checkRelativeRefs("t", 1); From c28bf9ce24312e028b67c5673ffb27ba97a89c9c Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 18 Jul 2022 11:56:45 +0100 Subject: [PATCH 020/177] Fix change detection over unpacked arrays. --- include/verilated_types.h | 25 ++++++++++++---- src/V3Sched.cpp | 5 ++++ .../t/t_comb_loop_through_unpacked_array.pl | 18 +++++++++++ .../t/t_comb_loop_through_unpacked_array.v | 30 +++++++++++++++++++ 4 files changed, 72 insertions(+), 6 deletions(-) create mode 100755 test_regress/t/t_comb_loop_through_unpacked_array.pl create mode 100644 test_regress/t/t_comb_loop_through_unpacked_array.v diff --git a/include/verilated_types.h b/include/verilated_types.h index 47cd12d9b..bb2ba2186 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -923,12 +923,9 @@ template struct VlUnpacked final { T_Value& operator[](size_t index) { return m_storage[index]; } const T_Value& operator[](size_t index) const { return m_storage[index]; } - bool operator!=(const VlUnpacked& that) const { - for (int i = 0; i < T_Depth; ++i) { - if (m_storage[i] != that.m_storage[i]) return true; - } - return false; - } + // *this != that, which might be used for change detection/trigger computation, but avoid + // operator overloading in VlUnpacked for safety in other contexts. + inline bool neq(const VlUnpacked& that) const { return neq(*this, that); } // Dumping. Verilog: str = $sformatf("%p", assoc) std::string to_string() const { @@ -940,6 +937,22 @@ template struct VlUnpacked final { } return out + "} "; } + +private: + template + static bool neq(const VlUnpacked& a, const VlUnpacked& b) { + for (int i = 0; i < T_Dep; ++i) { + // Recursive 'neq', in case T_Val is also a VlUnpacked<_, _> + if (neq(a.m_storage[i], b.m_storage[i])) return true; + } + return false; + } + + template // + inline static bool neq(const T_Other& a, const T_Other& b) { + // Base case (T_Other is not VlUnpacked<_, _>), fall back on != + return a != b; + } }; template diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index c5e44719b..a65cdd008 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -325,6 +325,11 @@ class SenExprBuilder final { return {nullptr, false}; // We already warn for this in V3LinkResolve case VEdgeType::ET_CHANGED: case VEdgeType::ET_HYBRID: // + if (VN_IS(senp->dtypep(), UnpackArrayDType)) { + AstCMethodHard* const resultp = new AstCMethodHard{flp, currp(), "neq", prevp()}; + resultp->dtypeSetBit(); + return {resultp, true}; + } return {new AstNeq(flp, currp(), prevp()), true}; case VEdgeType::ET_BOTHEDGE: // return {lsb(new AstXor{flp, currp(), prevp()}), false}; diff --git a/test_regress/t/t_comb_loop_through_unpacked_array.pl b/test_regress/t/t_comb_loop_through_unpacked_array.pl new file mode 100755 index 000000000..c7f63144c --- /dev/null +++ b/test_regress/t/t_comb_loop_through_unpacked_array.pl @@ -0,0 +1,18 @@ +#!/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( + verilator_flags2 => ["-Wno-UNOPTFLAT"] + ); + +ok(1); +1; diff --git a/test_regress/t/t_comb_loop_through_unpacked_array.v b/test_regress/t/t_comb_loop_through_unpacked_array.v new file mode 100644 index 000000000..1e2d26052 --- /dev/null +++ b/test_regress/t/t_comb_loop_through_unpacked_array.v @@ -0,0 +1,30 @@ +// 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( + input wire a, + input wire b, + output wire o +); + + logic [255:0] array [1:0]; + logic [255:0] tmp [1:0]; + + // Nonsensical, but needs to compile. (In some real designs we can end up + // with combinational loops via unpacked arrays) + always_comb begin + tmp[0] = array[a]; + end + + always_comb begin + array[b] = tmp[0]; + end + + assign o = array[0][0]; + +endmodule From 31abe537a0c34d4e7605bcf3a460928e7afd40e6 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 21 Jul 2022 17:34:12 +0100 Subject: [PATCH 021/177] Fix DPI export trigger sensitivity in 'nba' Fixes #3508 --- src/V3Sched.cpp | 48 +++++++++++++------------ test_regress/t/t_order_dpi_export_8.cpp | 16 +++++++++ test_regress/t/t_order_dpi_export_8.pl | 22 ++++++++++++ test_regress/t/t_order_dpi_export_8.v | 48 +++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 22 deletions(-) create mode 100644 test_regress/t/t_order_dpi_export_8.cpp create mode 100755 test_regress/t/t_order_dpi_export_8.pl create mode 100644 test_regress/t/t_order_dpi_export_8.v diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index a65cdd008..c795426fd 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -416,21 +416,6 @@ struct TriggerKit { VL_UNCOPYABLE(TriggerKit); - // Create an AstSenTree that is sensitive to the given trigger index. Must not exist yet! - AstSenTree* createTriggerSenTree(AstNetlist* netlistp, uint32_t index) const { - AstTopScope* const topScopep = netlistp->topScopep(); - FileLine* const flp = topScopep->fileline(); - AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::READ}; - AstCMethodHard* const callp - = new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, index}}; - callp->dtypeSetBit(); - callp->pure(true); - AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, callp}; - AstSenTree* const resultp = new AstSenTree{flp, senItemp}; - topScopep->addSenTreep(resultp); - return resultp; - } - // Utility that assigns the given index trigger to fire when the given variable is zero void addFirstIterationTriggerAssignment(AstVarScope* counterp, uint32_t index) const { FileLine* const flp = counterp->fileline(); @@ -459,6 +444,20 @@ struct TriggerKit { } }; +// Create an AstSenTree that is sensitive to the given trigger index. Must not exist yet! +AstSenTree* createTriggerSenTree(AstNetlist* netlistp, AstVarScope* const vscp, uint32_t index) { + AstTopScope* const topScopep = netlistp->topScopep(); + FileLine* const flp = topScopep->fileline(); + AstVarRef* const vrefp = new AstVarRef{flp, vscp, VAccess::READ}; + AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, index}}; + callp->dtypeSetBit(); + callp->pure(true); + AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, callp}; + AstSenTree* const resultp = new AstSenTree{flp, senItemp}; + topScopep->addSenTreep(resultp); + return resultp; +} + //============================================================================ // Utility for extra trigger allocation @@ -727,7 +726,8 @@ void createSettle(AstNetlist* netlistp, SenExprBuilder& senExprBulider, invertAndMergeSenTreeMap(trigToSen, trig.m_map); // First trigger is for pure combinational triggers (first iteration) - AstSenTree* const inputChanged = trig.createTriggerSenTree(netlistp, firstIterationTrigger); + AstSenTree* const inputChanged + = createTriggerSenTree(netlistp, trig.m_vscp, firstIterationTrigger); // Create and the body function AstCFunc* const stlFuncp = V3Order::order( @@ -800,11 +800,12 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde invertAndMergeSenTreeMap(trigToSen, trig.m_map); // The trigger top level inputs (first iteration) - AstSenTree* const inputChanged = trig.createTriggerSenTree(netlistp, firstIterationTrigger); + AstSenTree* const inputChanged + = createTriggerSenTree(netlistp, trig.m_vscp, firstIterationTrigger); // The DPI Export trigger AstSenTree* const dpiExportTriggered - = trig.createTriggerSenTree(netlistp, dpiExportTriggerIndex); + = createTriggerSenTree(netlistp, trig.m_vscp, dpiExportTriggerIndex); // Create and Order the body function AstCFunc* const icoFuncp @@ -1077,13 +1078,13 @@ void schedule(AstNetlist* netlistp) { invertAndMergeSenTreeMap(trigToSenAct, actTrigMap); // The DPI Export trigger AstSenTree - AstSenTree* const dpiExportTriggered - = actTrig.createTriggerSenTree(netlistp, dpiExportTriggerIndex); + AstSenTree* const dpiExportTriggeredAct + = createTriggerSenTree(netlistp, actTrig.m_vscp, dpiExportTriggerIndex); AstCFunc* const actFuncp = V3Order::order( netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct, "act", false, false, [=](const AstVarScope* vscp, std::vector& out) { - if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered); + if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredAct); }); splitCheck(actFuncp); if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-act"); @@ -1098,10 +1099,13 @@ void schedule(AstNetlist* netlistp) { std::unordered_map trigToSenNba; invertAndMergeSenTreeMap(trigToSenNba, nbaTrigMap); + AstSenTree* const dpiExportTriggeredNba + = createTriggerSenTree(netlistp, nbaTrigVscp, dpiExportTriggerIndex); + AstCFunc* const nbaFuncp = V3Order::order( netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba, "nba", v3Global.opt.mtasks(), false, [=](const AstVarScope* vscp, std::vector& out) { - if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered); + if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredNba); }); splitCheck(nbaFuncp); netlistp->evalNbap(nbaFuncp); // Remember for V3LifePost diff --git a/test_regress/t/t_order_dpi_export_8.cpp b/test_regress/t/t_order_dpi_export_8.cpp new file mode 100644 index 000000000..9cf6361c7 --- /dev/null +++ b/test_regress/t/t_order_dpi_export_8.cpp @@ -0,0 +1,16 @@ +// -*- 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 + +#include + +void call_set_x(svBit val) { set_x(val); } diff --git a/test_regress/t/t_order_dpi_export_8.pl b/test_regress/t/t_order_dpi_export_8.pl new file mode 100755 index 000000000..2c9b6e9f0 --- /dev/null +++ b/test_regress/t/t_order_dpi_export_8.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vlt_all => 1); + +compile( + verilator_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_order_dpi_export_8.v b/test_regress/t/t_order_dpi_export_8.v new file mode 100644 index 000000000..2df1a3e9b --- /dev/null +++ b/test_regress/t/t_order_dpi_export_8.v @@ -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 + +module testbench( + /*AUTOARG*/ + // Inputs + clk + ); + + input clk; // Top level input clock + + bit x = 0; + + wire y = x & $c(1); + + export "DPI-C" function set_x; + function void set_x(bit val); + x = val; + endfunction; + + import "DPI-C" context function void call_set_x(bit val); + + bit q = 0; + always @(posedge clk) q <= ~q; + + always @(edge q) call_set_x(q); + + int n = 0; + + always @(edge clk) begin + // This always block needs to evaluate before the NBA to even_other + // above is committed, as setting clocks via the set_other_clk uses + // blocking assignment. + $display("t=%t q=%d x=%d y=%d", $time, q, x, y); + if (y !== q) $stop; + if (n == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + n += 1; + end + +endmodule From 12925cd8b02bf5b7b3136f54faa6515136618db4 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 30 Jul 2022 12:49:30 -0400 Subject: [PATCH 022/177] Internals: clang-tidy cleanups. No functional change intended. --- src/V3Active.cpp | 6 ++++-- src/V3Options.cpp | 2 +- src/V3Order.cpp | 6 +++--- src/V3Order.h | 2 +- src/V3OrderGraph.h | 4 ++-- src/V3Sched.cpp | 18 ++++++++++-------- src/V3Sched.h | 2 +- src/V3SchedAcyclic.cpp | 2 +- src/V3SchedReplicate.cpp | 2 +- 9 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 20e185d86..7a83c3a2e 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -468,8 +468,10 @@ private: wantactivep->addStmtsp(nodep); // Warn and convert any delayed assignments - ActiveDlyVisitor{nodep, - m_clockedProcess ? ActiveDlyVisitor::CT_SEQ : ActiveDlyVisitor::CT_COMB}; + { + ActiveDlyVisitor{nodep, m_clockedProcess ? ActiveDlyVisitor::CT_SEQ + : ActiveDlyVisitor::CT_COMB}; + } // check combinational processes for latches if (!m_clockedProcess || kwd == VAlwaysKwd::ALWAYS_LATCH) { diff --git a/src/V3Options.cpp b/src/V3Options.cpp index cb9720fbd..49d1b2f9a 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1224,7 +1224,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char } }); DECL_OPTION("-o", Set, &m_exeName); - DECL_OPTION("-order-clock-delay", CbOnOff, [fl](bool flag) { + 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); diff --git a/src/V3Order.cpp b/src/V3Order.cpp index d60f21dbc..0ce70e37f 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -479,7 +479,7 @@ class OrderBuildVisitor final : public VNVisitor { virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } // CONSTRUCTOR - OrderBuildVisitor(AstNetlist* nodep, const std::vector& coll, + OrderBuildVisitor(AstNetlist* /*nodep*/, const std::vector& coll, const std::unordered_map& trigToSen) : m_trigToSen{trigToSen} { @@ -1522,8 +1522,8 @@ AstCFunc* order(AstNetlist* netlistp, // // Order the code const std::unique_ptr graph = OrderBuildVisitor::process(netlistp, logic, trigToSen); - const auto& nodeps = OrderProcess::main(netlistp, *graph.get(), trigToSen, tag, parallel, slow, - externalDomains); + const auto& nodeps + = OrderProcess::main(netlistp, *graph, trigToSen, tag, parallel, slow, externalDomains); // Create the result function AstScope* const scopeTopp = netlistp->topScopep()->scopep(); diff --git a/src/V3Order.h b/src/V3Order.h index 9dc6a0fdf..edd7db707 100644 --- a/src/V3Order.h +++ b/src/V3Order.h @@ -32,7 +32,7 @@ class AstVarScope; namespace V3Sched { struct LogicByScope; -}; +}; // namespace V3Sched //============================================================================ diff --git a/src/V3OrderGraph.h b/src/V3OrderGraph.h index 070e38c54..b08c0ff48 100644 --- a/src/V3OrderGraph.h +++ b/src/V3OrderGraph.h @@ -140,9 +140,9 @@ public: AstVarScope* vscp() const { return m_vscp; } // LCOV_EXCL_START // Debug code - virtual string dotShape() const override final { return "ellipse"; } + string dotShape() const override final { return "ellipse"; } virtual string nameSuffix() const = 0; - virtual string name() const override final { + string name() const override final { return cvtToHex(m_vscp) + " " + nameSuffix() + "\\n " + m_vscp->name(); } // LCOV_EXCL_STOP diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index c795426fd..3f7369d8a 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -75,7 +75,7 @@ AstCFunc* makeTopFunction(AstNetlist* netlistp, const string& name, bool slow) { return funcp; } -std::vector getSenTreesUsedBy(std::vector lbsps) { +std::vector getSenTreesUsedBy(const std::vector& lbsps) { const VNUser1InUse user1InUse; std::vector result; for (const LogicByScope* const lbsp : lbsps) { @@ -106,8 +106,9 @@ void remapSensitivities(LogicByScope& lbs, } } -void invertAndMergeSenTreeMap(std::unordered_map& result, - std::unordered_map senTreeMap) { +void invertAndMergeSenTreeMap( + std::unordered_map& result, + const std::unordered_map& senTreeMap) { for (const auto& pair : senTreeMap) { UASSERT_OBJ(!pair.second->sensesp()->nextp(), pair.second, "Should be single AstSenIem"); result.emplace(pair.second->sensesp(), pair.first); @@ -417,7 +418,7 @@ struct TriggerKit { VL_UNCOPYABLE(TriggerKit); // Utility that assigns the given index trigger to fire when the given variable is zero - void addFirstIterationTriggerAssignment(AstVarScope* counterp, uint32_t index) const { + void addFirstIterationTriggerAssignment(AstVarScope* counterp, uint32_t /*index*/) const { FileLine* const flp = counterp->fileline(); AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE}; AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "at", new AstConst{flp, 0}}; @@ -480,8 +481,9 @@ public: // Create a TRIGGERVEC and the related TriggerKit for the given AstSenTree vector const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBuilder, - std::vector senTreeps, const string& name, - const ExtraTriggers& extraTriggers, bool slow = false) { + const std::vector& senTreeps, + const string& name, const ExtraTriggers& extraTriggers, + bool slow = false) { AstTopScope* const topScopep = netlistp->topScopep(); AstScope* const scopeTopp = topScopep->scopep(); FileLine* const flp = scopeTopp->fileline(); @@ -610,7 +612,7 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui // Helpers to construct an evaluation loop. AstNode* buildLoop(AstNetlist* netlistp, const string& name, - std::function build) // + const std::function& build) // { AstTopScope* const topScopep = netlistp->topScopep(); AstScope* const scopeTopp = topScopep->scopep(); @@ -941,7 +943,7 @@ void createEval(AstNetlist* netlistp, // // Top level entry-point to scheduling void schedule(AstNetlist* netlistp) { - const auto addSizeStat = [](const string name, const LogicByScope& lbs) { + const auto addSizeStat = [](const string& name, const LogicByScope& lbs) { uint64_t size = 0; lbs.foreachLogic([&](AstNode* nodep) { size += nodep->nodeCount(); }); V3Stats::addStat("Scheduling, " + name, size); diff --git a/src/V3Sched.h b/src/V3Sched.h index 43603c792..b733425ba 100644 --- a/src/V3Sched.h +++ b/src/V3Sched.h @@ -63,7 +63,7 @@ struct LogicByScope final : public std::vector> clear(); }; - void foreachLogic(std::function f) const { + void foreachLogic(const std::function& f) const { for (const auto& pair : *this) { for (AstNode* nodep = pair.second->stmtsp(); nodep; nodep = nodep->nextp()) f(nodep); } diff --git a/src/V3SchedAcyclic.cpp b/src/V3SchedAcyclic.cpp index 6160ec8d5..583238bfb 100644 --- a/src/V3SchedAcyclic.cpp +++ b/src/V3SchedAcyclic.cpp @@ -93,7 +93,7 @@ public: }; class Graph final : public V3Graph { - void loopsVertexCb(V3GraphVertex* vtxp) { + void loopsVertexCb(V3GraphVertex* vtxp) override { // TODO: 'typeName' is an internal thing. This should be more human readable. if (LogicVertex* const lvtxp = dynamic_cast(vtxp)) { AstNode* const logicp = lvtxp->logicp(); diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 76c3c564b..82dae0e61 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -63,7 +63,7 @@ class Vertex VL_NOT_FINAL : public V3GraphVertex { RegionFlags m_drivingRegions{NONE}; // The regions driving this vertex public: - Vertex(V3Graph* graphp) + explicit Vertex(V3Graph* graphp) : V3GraphVertex{graphp} {} uint8_t drivingRegions() const { return m_drivingRegions; } void addDrivingRegions(uint8_t regions) { From 39d1a62f9ed622e16f0442477345cb49d742f330 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 2 Aug 2022 12:21:02 +0100 Subject: [PATCH 023/177] Fix change detection on unpacked arrays Expand array assignment when creating the trigger, as V3Expand might mangle it otherwise. --- include/verilated_types.h | 2 ++ src/V3Sched.cpp | 30 +++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/include/verilated_types.h b/include/verilated_types.h index 731a65017..8e93dd4be 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -926,6 +926,8 @@ template struct VlUnpacked final { // *this != that, which might be used for change detection/trigger computation, but avoid // operator overloading in VlUnpacked for safety in other contexts. inline bool neq(const VlUnpacked& that) const { return neq(*this, that); } + // Similar to 'neq' above, *this = that used for change detection + void assign(const VlUnpacked& that) { *this = that; } // Dumping. Verilog: str = $sformatf("%p", assoc) std::string to_string() const { diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 3f7369d8a..3644dbf94 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -274,6 +274,15 @@ class SenExprBuilder final { V3UniqueNames m_uniqueNames{"__Vtrigprev__expression"}; // For generating unique signal names + static bool isSupportedDType(AstNodeDType* dtypep) { + dtypep = dtypep->skipRefp(); + if (VN_IS(dtypep, BasicDType)) return true; + if (VN_IS(dtypep, PackArrayDType)) return true; + if (VN_IS(dtypep, UnpackArrayDType)) return isSupportedDType(dtypep->subDTypep()); + if (VN_IS(dtypep, NodeUOrStructDType)) return true; // All are packed at the moment + return false; + } + // METHODS AstVarScope* getPrev(AstNode* currp) { FileLine* const flp = currp->fileline(); @@ -303,10 +312,25 @@ class SenExprBuilder final { AstVarScope* const prevp = it->second; - // Add update if it does not exist yet in this round + const auto wrPrev = [=]() { return new AstVarRef{flp, prevp, VAccess::WRITE}; }; + + // Add update if it does not exist yet if (m_hasUpdate.emplace(*currp).second) { - m_updates.push_back( - new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE}, rdCurr()}); + if (!isSupportedDType(currp->dtypep())) { + currp->v3warn(E_UNSUPPORTED, + "Unsupported: Cannot detect changes on expression of complex type" + " (see combinational cycles reported by UNOPTFLAT)"); + return prevp; + } + + if (AstUnpackArrayDType* const dtypep = VN_CAST(currp->dtypep(), UnpackArrayDType)) { + AstCMethodHard* const cmhp = new AstCMethodHard{flp, wrPrev(), "assign", rdCurr()}; + cmhp->dtypeSetVoid(); + cmhp->statement(true); + m_updates.push_back(cmhp); + } else { + m_updates.push_back(new AstAssign{flp, wrPrev(), rdCurr()}); + } } return prevp; From 6fc25dae9e0c488de8b54a13f9a646d68e7b5ef5 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 2 Aug 2022 15:47:14 +0100 Subject: [PATCH 024/177] Fix clang-tidy warnings (#3522) --- src/V3Order.cpp | 7 ++++--- src/V3Order.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 0ce70e37f..938e91232 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -951,7 +951,8 @@ class OrderProcess final : VNDeleter { // CONSTRUCTOR OrderProcess(AstNetlist* netlistp, OrderGraph& graph, const std::unordered_map& trigToSen, - const string& tag, bool slow, V3Order::ExternalDomainsProvider externalDomains) + const string& tag, bool slow, + const V3Order::ExternalDomainsProvider& externalDomains) : m_graph{graph} , m_trigToSen{trigToSen} , m_externalDomains{externalDomains} @@ -970,7 +971,7 @@ public: main(AstNetlist* netlistp, OrderGraph& graph, const std::unordered_map& trigToSen, const string& tag, bool parallel, bool slow, - V3Order::ExternalDomainsProvider externalDomains) { + const V3Order::ExternalDomainsProvider& externalDomains) { OrderProcess visitor{netlistp, graph, trigToSen, tag, slow, externalDomains}; visitor.process(parallel); return std::move(visitor.m_result); @@ -1518,7 +1519,7 @@ AstCFunc* order(AstNetlist* netlistp, // const string& tag, // bool parallel, // bool slow, // - ExternalDomainsProvider externalDomains) { + const ExternalDomainsProvider& externalDomains) { // Order the code const std::unique_ptr graph = OrderBuildVisitor::process(netlistp, logic, trigToSen); diff --git a/src/V3Order.h b/src/V3Order.h index edd7db707..6beac4031 100644 --- a/src/V3Order.h +++ b/src/V3Order.h @@ -47,7 +47,7 @@ AstCFunc* order( const string& tag, // bool parallel, // bool slow, // - ExternalDomainsProvider externalDomains + const ExternalDomainsProvider& externalDomains = [](const AstVarScope*, std::vector&) {}); }; // namespace V3Order From a4fd6d38fb8ad219cbcca5b935f4526241a7777e Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 7 Aug 2022 13:13:18 +0100 Subject: [PATCH 025/177] Add operator != to VlWide This is required by VlUnpacked::neq --- include/verilated_types.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/include/verilated_types.h b/include/verilated_types.h index 05bc34c2c..76126e3a1 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -209,6 +209,12 @@ struct VlWide final { // Default copy assignment operators are used. operator WDataOutP() { return &m_storage[0]; } // This also allows [] operator WDataInP() const { return &m_storage[0]; } // This also allows [] + bool operator!=(const VlWide& that) const { + for (size_t i = 0; i < T_Words; ++i) { + if (m_storage[i] != that.m_storage[i]) return true; + } + return false; + } // METHODS const EData& at(size_t index) const { return m_storage[index]; } @@ -974,7 +980,7 @@ struct VlUnpacked final { private: template static bool neq(const VlUnpacked& a, const VlUnpacked& b) { - for (int i = 0; i < T_Dep; ++i) { + for (size_t i = 0; i < T_Dep; ++i) { // Recursive 'neq', in case T_Val is also a VlUnpacked<_, _> if (neq(a.m_storage[i], b.m_storage[i])) return true; } From 10cf492946a79354e4791f00dc85700b9189cd63 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Fri, 19 Aug 2022 20:18:38 +0200 Subject: [PATCH 026/177] Add support for expressions in event controls (#3550) Signed-off-by: Krzysztof Bieganski --- src/V3Const.cpp | 4 +- src/V3LinkResolve.cpp | 36 ---- src/V3Sched.cpp | 116 +++++++++--- src/V3Task.cpp | 12 ++ src/V3Width.cpp | 17 +- src/verilog.y | 23 +-- test_regress/t/t_event_control_expr.pl | 33 ++++ test_regress/t/t_event_control_expr.v | 174 ++++++++++++++++++ test_regress/t/t_event_control_expr_unsup.out | 9 + test_regress/t/t_event_control_expr_unsup.pl | 22 +++ 10 files changed, 361 insertions(+), 85 deletions(-) create mode 100755 test_regress/t/t_event_control_expr.pl create mode 100644 test_regress/t/t_event_control_expr.v create mode 100644 test_regress/t/t_event_control_expr_unsup.out create mode 100755 test_regress/t/t_event_control_expr_unsup.pl diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 05f673851..52334947a 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2717,9 +2717,7 @@ private: } UINFO(8, "senItem(NOT...) " << nodep << " " << invert << endl); if (invert) nodep->edgeType(nodep->edgeType().invert()); - AstNodeVarRef* const senvarp = VN_AS(lastSensp->unlinkFrBack(), NodeVarRef); - UASSERT_OBJ(senvarp, sensp, "Non-varref sensitivity variable"); - sensp->replaceWith(senvarp); + sensp->replaceWith(lastSensp->unlinkFrBack()); VL_DO_DANGLING(sensp->deleteTree(), sensp); } } diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp index a215b5dc0..073ffda44 100644 --- a/src/V3LinkResolve.cpp +++ b/src/V3LinkResolve.cpp @@ -145,42 +145,6 @@ private: nodep->scopeNamep(new AstScopeName{nodep->fileline(), false}); } } - - virtual void visit(AstSenItem* nodep) override { - // Remove bit selects, and bark if it's not a simple variable - iterateChildren(nodep); - if (!nodep->isClocked()) { - // Old V1995 sensitivity list; we'll probably mostly ignore - bool did = true; - while (did) { - did = false; - if (AstNodeSel* const selp = VN_CAST(nodep->sensp(), NodeSel)) { - AstNode* const fromp = selp->fromp()->unlinkFrBack(); - selp->replaceWith(fromp); - VL_DO_DANGLING(selp->deleteTree(), selp); - did = true; - } - // NodeSel doesn't include AstSel.... - if (AstSel* const selp = VN_CAST(nodep->sensp(), Sel)) { - AstNode* const fromp = selp->fromp()->unlinkFrBack(); - selp->replaceWith(fromp); - VL_DO_DANGLING(selp->deleteTree(), selp); - did = true; - } - if (AstNodePreSel* const selp = VN_CAST(nodep->sensp(), NodePreSel)) { - AstNode* const fromp = selp->fromp()->unlinkFrBack(); - selp->replaceWith(fromp); - VL_DO_DANGLING(selp->deleteTree(), selp); - did = true; - } - } - } - if (nodep->isIllegal()) { - if (debug()) nodep->dumpTree(cout, "-tree: "); - nodep->v3warn(E_UNSUPPORTED, "Unsupported: Complex statement in sensitivity list"); - } - } - virtual void visit(AstNodePreSel* nodep) override { if (!nodep->attrp()) { iterateChildren(nodep); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index f1085acc6..e3f875327 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -268,13 +268,19 @@ class SenExprBuilder final { AstCFunc* const m_initp; // The initialization function AstScope* const m_scopeTopp; // Top level scope - std::vector m_updates; // Update assignments + std::vector m_locals; // Trigger eval local variables + std::vector m_preUpdates; // Pre update assignments + std::vector m_postUpdates; // Post update assignments std::unordered_map, AstVarScope*> m_prev; // The 'previous value' signals - std::unordered_set> m_hasUpdate; // Whether the given sen expression already - // has an update statement in m_updates + std::unordered_map, AstVarScope*> m_curr; // The 'current value' signals + std::unordered_set> m_hasPreUpdate; // Whether the given sen expression already + // has an update statement in m_preUpdates + std::unordered_set> m_hasPostUpdate; // Likewis for m_postUpdates - V3UniqueNames m_uniqueNames{"__Vtrigprev__expression"}; // For generating unique signal names + V3UniqueNames m_currNames{"__Vtrigcurr__expression"}; // For generating unique current value + // signal names + V3UniqueNames m_prevNames{"__Vtrigprev__expression"}; // Likewise for previous values static bool isSupportedDType(AstNodeDType* dtypep) { dtypep = dtypep->skipRefp(); @@ -285,29 +291,62 @@ class SenExprBuilder final { return false; } + static bool isSimpleExpr(const AstNode* const exprp) { + return exprp->forall([](const AstNode* const nodep) { + return VN_IS(nodep, Const) || VN_IS(nodep, NodeVarRef) || VN_IS(nodep, Sel) + || VN_IS(nodep, NodeSel) || VN_IS(nodep, MemberSel) + || VN_IS(nodep, CMethodHard); + }); + } + // METHODS - AstVarScope* getPrev(AstNode* currp) { - FileLine* const flp = currp->fileline(); - const auto rdCurr = [=]() { return currp->cloneTree(false); }; + AstNode* getCurr(AstNode* exprp) { + // For simple expressions like varrefs or selects, just use them directly + if (isSimpleExpr(exprp)) return exprp->cloneTree(false); + + // Create the 'current value' variable + FileLine* const flp = exprp->fileline(); + auto result = m_curr.emplace(*exprp, nullptr); + if (result.second) { + AstVar* const varp + = new AstVar{flp, VVarType::BLOCKTEMP, m_currNames.get(exprp), exprp->dtypep()}; + varp->funcLocal(true); + m_locals.push_back(varp); + AstVarScope* vscp = new AstVarScope{flp, m_scopeTopp, varp}; + m_scopeTopp->addVarp(vscp); + result.first->second = vscp; + } + AstVarScope* const currp = result.first->second; + + // Add pre update if it does not exist yet in this round + if (m_hasPreUpdate.emplace(*currp).second) { + m_preUpdates.push_back(new AstAssign{flp, new AstVarRef{flp, currp, VAccess::WRITE}, + exprp->cloneTree(false)}); + } + return new AstVarRef{flp, currp, VAccess::READ}; + } + AstVarScope* getPrev(AstNode* exprp) { + FileLine* const flp = exprp->fileline(); + const auto rdCurr = [=]() { return getCurr(exprp); }; // Create the 'previous value' variable - auto it = m_prev.find(*currp); + auto it = m_prev.find(*exprp); if (it == m_prev.end()) { // For readability, use the scoped signal name if the trigger is a simple AstVarRef string name; - if (AstVarRef* const refp = VN_CAST(currp, VarRef)) { + if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) { AstVarScope* vscp = refp->varScopep(); name = "__Vtrigrprev__" + vscp->scopep()->nameDotless() + "__" + vscp->varp()->name(); } else { - name = m_uniqueNames.get(currp); + name = m_prevNames.get(exprp); } - AstVarScope* const prevp = m_scopeTopp->createTemp(name, currp->dtypep()); - it = m_prev.emplace(*currp, prevp).first; + AstVarScope* const prevp = m_scopeTopp->createTemp(name, exprp->dtypep()); + it = m_prev.emplace(*exprp, prevp).first; // Add the initializer init - AstNode* const initp = rdCurr(); + AstNode* const initp = exprp->cloneTree(false); m_initp->addStmtsp( new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE}, initp}); } @@ -316,22 +355,22 @@ class SenExprBuilder final { const auto wrPrev = [=]() { return new AstVarRef{flp, prevp, VAccess::WRITE}; }; - // Add update if it does not exist yet - if (m_hasUpdate.emplace(*currp).second) { - if (!isSupportedDType(currp->dtypep())) { - currp->v3warn(E_UNSUPPORTED, + // Add post update if it does not exist yet + if (m_hasPostUpdate.emplace(*exprp).second) { + if (!isSupportedDType(exprp->dtypep())) { + exprp->v3warn(E_UNSUPPORTED, "Unsupported: Cannot detect changes on expression of complex type" " (see combinational cycles reported by UNOPTFLAT)"); return prevp; } - if (AstUnpackArrayDType* const dtypep = VN_CAST(currp->dtypep(), UnpackArrayDType)) { + if (AstUnpackArrayDType* const dtypep = VN_CAST(exprp->dtypep(), UnpackArrayDType)) { AstCMethodHard* const cmhp = new AstCMethodHard{flp, wrPrev(), "assign", rdCurr()}; cmhp->dtypeSetVoid(); cmhp->statement(true); - m_updates.push_back(cmhp); + m_postUpdates.push_back(cmhp); } else { - m_updates.push_back(new AstAssign{flp, wrPrev(), rdCurr()}); + m_postUpdates.push_back(new AstAssign{flp, wrPrev(), rdCurr()}); } } @@ -342,7 +381,7 @@ class SenExprBuilder final { FileLine* const flp = senItemp->fileline(); AstNode* const senp = senItemp->sensp(); - const auto currp = [=]() { return senp->cloneTree(false); }; + const auto currp = [=]() { return getCurr(senp); }; const auto prevp = [=]() { return new AstVarRef{flp, getPrev(senp), VAccess::READ}; }; const auto lsb = [=](AstNodeMath* opp) { return new AstSel{flp, opp, 0, 1}; }; @@ -371,7 +410,7 @@ class SenExprBuilder final { AstCMethodHard* const callp = new AstCMethodHard{flp, currp(), "isFired"}; callp->dtypeSetBit(); AstIf* const ifp = new AstIf{flp, callp}; - m_updates.push_back(ifp); + m_postUpdates.push_back(ifp); // Clear 'fired' state when done AstCMethodHard* const clearp = new AstCMethodHard{flp, currp(), "clearFired"}; @@ -417,9 +456,16 @@ public: return {resultp, firedAtInitialization}; } - std::vector getAndClearUpdates() { - m_hasUpdate.clear(); - return std::move(m_updates); + std::vector getAndClearLocals() { return std::move(m_locals); } + + std::vector getAndClearPreUpdates() { + m_hasPreUpdate.clear(); + return std::move(m_preUpdates); + } + + std::vector getAndClearPostUpdates() { + m_hasPostUpdate.clear(); + return std::move(m_postUpdates); } // CONSTRUCTOR @@ -606,7 +652,25 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui ++triggerNumber; } // Add the update statements - for (AstNodeStmt* const nodep : senExprBuilder.getAndClearUpdates()) funcp->addStmtsp(nodep); + for (AstNodeStmt* const nodep : senExprBuilder.getAndClearPostUpdates()) { + funcp->addStmtsp(nodep); + } + const auto& preUpdates = senExprBuilder.getAndClearPreUpdates(); + if (!preUpdates.empty()) { + for (AstNodeStmt* const nodep : vlstd::reverse_view(preUpdates)) { + UASSERT_OBJ(funcp->stmtsp(), funcp, + "No statements in trigger eval function, but there are pre updates"); + funcp->stmtsp()->addHereThisAsNext(nodep); + } + } + const auto& locals = senExprBuilder.getAndClearLocals(); + if (!locals.empty()) { + UASSERT_OBJ(funcp->stmtsp(), funcp, + "No statements in trigger eval function, but there are locals"); + for (AstVar* const nodep : vlstd::reverse_view(locals)) { + funcp->stmtsp()->addHereThisAsNext(nodep); + } + } // Add the initialization statements if (initialTrigsp) { diff --git a/src/V3Task.cpp b/src/V3Task.cpp index fee06a242..42c995865 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -360,6 +360,7 @@ private: AstScope* m_scopep = nullptr; // Current scope InsertMode m_insMode = IM_BEFORE; // How to insert AstNode* m_insStmtp = nullptr; // Where to insert statement + bool m_inSensesp = false; // Are we under a senitem? int m_modNCalls = 0; // Incrementing func # for making symbols DpiCFuncs m_dpiNames; // Map of all created DPI functions @@ -1369,6 +1370,11 @@ private: m_scopep = nullptr; } virtual void visit(AstNodeFTaskRef* nodep) override { + if (m_inSensesp) { + nodep->v3warn(E_UNSUPPORTED, "Unsupported: function calls in sensitivity lists"); + nodep->taskp(nullptr); // So V3Broken doesn't complain + return; + } // Includes handling AstMethodCall, AstNew UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked?"); iterateIntoFTask(nodep->taskp()); // First, do hierarchical funcs @@ -1521,6 +1527,12 @@ private: iterateChildren(nodep); m_insStmtp = nullptr; // Next thing should be new statement } + virtual void visit(AstSenItem* nodep) override { + UASSERT_OBJ(!m_inSensesp, nodep, "Senitem under senitem?"); + VL_RESTORER(m_inSensesp); + m_inSensesp = true; + iterateChildren(nodep); + } //-------------------- virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index ba22697b5..f938f95d7 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -5050,7 +5050,22 @@ private: m_procedurep = nullptr; } virtual void visit(AstSenItem* nodep) override { - userIterateChildren(nodep, WidthVP(SELF, BOTH).p()); + UASSERT_OBJ(nodep->isClocked(), nodep, "Invalid edge"); + // Optimize concat/replicate senitems; this has to be done here at the latest, otherwise we + // emit WIDTHCONCAT if there are unsized constants + if (VN_IS(nodep->sensp(), Concat) || VN_IS(nodep->sensp(), Replicate)) { + auto* const concatOrReplp = VN_CAST(nodep->sensp(), NodeBiop); + auto* const rhsp = concatOrReplp->rhsp()->unlinkFrBack(); + if (nodep->edgeType() == VEdgeType::ET_CHANGED) { + // If it's ET_CHANGED, split concatenations into multiple senitems + auto* const lhsp = concatOrReplp->lhsp()->unlinkFrBack(); + nodep->addNextHere(new AstSenItem{lhsp->fileline(), nodep->edgeType(), lhsp}); + } // Else only use the RHS + nodep->replaceWith(new AstSenItem{rhsp->fileline(), nodep->edgeType(), rhsp}); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } else { + userIterateChildren(nodep, WidthVP(SELF, BOTH).p()); + } } virtual void visit(AstWith* nodep) override { // Should otherwise be underneath a method call diff --git a/src/verilog.y b/src/verilog.y index b3baf5ac3..71e97120c 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3027,15 +3027,8 @@ event_expression: // IEEE: event_expression - split over several senitem: // IEEE: part of event_expression, non-'OR' ',' terms senitemEdge { $$ = $1; } - | senitemVar { $$ = $1; } - | '(' senitem ')' { $$ = $2; } - //UNSUP expr { UNSUP } - | '{' event_expression '}' { $$ = $2; } - | senitem yP_ANDAND senitem { $$ = new AstSenItem($2, AstSenItem::Illegal()); } + | expr { $$ = new AstSenItem{$1, VEdgeType::ET_CHANGED, $1}; } //UNSUP expr yIFF expr { UNSUP } - // Since expr is unsupported we allow and ignore constants (removed in V3Const) - | yaINTNUM { $$ = nullptr; } - | yaFLOATNUM { $$ = nullptr; } ; senitemVar: @@ -3043,19 +3036,11 @@ senitemVar: ; senitemEdge: // IEEE: part of event_expression - //UNSUP // Below are all removed - yPOSEDGE idClassSel { $$ = new AstSenItem($1, VEdgeType::ET_POSEDGE, $2); } - | yNEGEDGE idClassSel { $$ = new AstSenItem($1, VEdgeType::ET_NEGEDGE, $2); } - | yEDGE idClassSel { $$ = new AstSenItem($1, VEdgeType::ET_BOTHEDGE, $2); } - | yPOSEDGE '(' idClassSel ')' { $$ = new AstSenItem($1, VEdgeType::ET_POSEDGE, $3); } - | yNEGEDGE '(' idClassSel ')' { $$ = new AstSenItem($1, VEdgeType::ET_NEGEDGE, $3); } - | yEDGE '(' idClassSel ')' { $$ = new AstSenItem($1, VEdgeType::ET_BOTHEDGE, $3); } - //UNSUP // Above are all removed, replace with: - //UNSUP yPOSEDGE expr { UNSUP } + yPOSEDGE expr { $$ = new AstSenItem{$1, VEdgeType::ET_POSEDGE, $2}; } + | yNEGEDGE expr { $$ = new AstSenItem{$1, VEdgeType::ET_NEGEDGE, $2}; } + | yEDGE expr { $$ = new AstSenItem{$1, VEdgeType::ET_BOTHEDGE, $2}; } //UNSUP yPOSEDGE expr yIFF expr { UNSUP } - //UNSUP yNEGEDGE expr { UNSUP } //UNSUP yNEGEDGE expr yIFF expr { UNSUP } - //UNSUP yEDGE expr { UNSUP } //UNSUP yEDGE expr yIFF expr { UNSUP } ; diff --git a/test_regress/t/t_event_control_expr.pl b/test_regress/t/t_event_control_expr.pl new file mode 100755 index 000000000..a50ea7293 --- /dev/null +++ b/test_regress/t/t_event_control_expr.pl @@ -0,0 +1,33 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + # do not test classes for multithreaded, as V3InstrCount doesn't handle MemberSel + verilator_flags2 => $Self->{vltmt} ? ['-DNO_CLASS'] : [], + ); + +execute( + check_finished => 1, + ); + +for my $file (glob_all("$Self->{obj_dir}/$Self->{VM_PREFIX}*.cpp")) { + # Check that these simple expressions are not stored in temp variables + file_grep_not($file, qr/__Vtrigcurr__expression_.* = vlSelf->clk;/); + file_grep_not($file, qr/__Vtrigcurr__expression_.* = vlSelf->t__DOT__q.at\(0U\);/); + file_grep_not($file, qr/__Vtrigcurr__expression_.* = .*vlSelf->t__DOT____Vcellinp__u_array__t/); + file_grep_not($file, qr/__Vtrigcurr__expression_.* = .*vlSymsp->TOP__t__DOT__u_class.__PVT__obj/); + # The line below should only be generated if concats/replicates aren't converted to separate senitems + file_grep_not($file, qr/__Vtrigcurr__expression_.* = .*vlSelf->t__DOT__a/); +} + +ok(1); +1; diff --git a/test_regress/t/t_event_control_expr.v b/test_regress/t/t_event_control_expr.v new file mode 100644 index 000000000..23fc5bc55 --- /dev/null +++ b/test_regress/t/t_event_control_expr.v @@ -0,0 +1,174 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +`define STRINGIFY(text) `"text`" + +//======================================================================== +// Various expression tests. The macro generates a module with the desired +// input and tested expression. +// +`define EXPR_TEST(name, test_edges, inputs, expr) \ +module t_``name inputs; \ + logic[$bits(expr)-1:0] last = 0; \ + always @(expr) begin \ + if ($bits(expr) > 1) begin \ + `WRITE_VERBOSE(("[%0t] %s [changed] %s=%0x, last=%0x\n", $time, `STRINGIFY(name), `STRINGIFY(expr), expr, last)); \ + end \ + if ($time > 0 && (expr) == last) $stop; \ + last <= expr; \ + end \ + generate if (test_edges) begin \ + always @(posedge expr) begin \ + `WRITE_VERBOSE(("[%0t] %s [posedge] %s=%0x, last=%0x\n", $time, `STRINGIFY(name), `STRINGIFY(expr), expr, last)); \ + if ($time > 0 && ({1'b0, ~(expr)}[0] || last[0])) $stop; \ + end \ + always @(negedge expr) begin \ + `WRITE_VERBOSE(("[%0t] %s [negedge] %s=%0x, last=%0x\n", $time, `STRINGIFY(name), `STRINGIFY(expr), expr, last)); \ + if ($time > 0 && ({1'b0, expr}[0] || ~last[0])) $stop; \ + end \ + end endgenerate \ +endmodule + +`EXPR_TEST(xor, 1, (input a, b), b^a) +`EXPR_TEST(nand, 1, (input a, b, c), ~(c&b&a)) +`EXPR_TEST(concat1, 1, (input a, b, c), {{a, b},c,a,{2{a,b,c}}}) +`EXPR_TEST(reduce, 1, (input[3:0] v), v[0]^v[1]^v[2]^v[3]) +`EXPR_TEST(concat2, 1, (input[3:0] v), {{v[0]|v[1]},v[1]|v[2],{4{v[2]|v[3]}}}) +`EXPR_TEST(add, 0, (input int i, j), i+j) +`EXPR_TEST(lt, 1, (input int i, j), i 0 && expr == last) $stop; \ + last <= expr; \ + end \ +endmodule + +`CLASS_TEST(class, obj.k) + +`ifdef UNSUP +`CLASS_TEST(method, obj.get_k()) +`endif +`endif + +//======================================================================== +// $c test has to be written out explicitly as the STRINGIFY macro can't handle it +// +module t_cstmt; + logic last = 0; + always @($c("vlSelf->clk")) begin + if ($time > 0 && logic'($c("vlSelf->clk")) == last) $stop; + last <= logic'($c("vlSelf->clk")); + end + always @(posedge $c("vlSelf->clk")) begin + `WRITE_VERBOSE(("[%0t] cstmt [posedge] $c(\"vlSelf->clk\")=%0b, last=%b\n", $time, $c("vlSelf->clk"), last)); + if ($time > 0 && (~logic'($c("vlSelf->clk")) || last)) $stop; + end + always @(negedge $c("vlSelf->clk")) begin + `WRITE_VERBOSE(("[%0t] cstmt [negedge] $c(\"vlSelf->clk\")=%0b, last=%b\n", $time, $c("vlSelf->clk"), last)); + if ($time > 0 && (logic'($c("vlSelf->clk")) || !last)) $stop; + end +endmodule + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic a = 0, b = 0, c = 0; + t_xor u_xor(.*); + t_nand u_nand(.*); + t_concat1 u_concat1(.*); + + logic[3:0] v = '0; + t_reduce u_reduce(.*); + t_concat2 u_concat2(.*); + + int i = 0, j = 0; + t_add u_add(.*); + t_lt u_lt(.*); + + int t[5] = {0, 1, 2, 3, 4}; + t_array u_array(.*); + t_array_complex u_array_complex(.*); + + int q[$]; + t_queue u_queue(.*); + t_queue_mul u_queue_mul(.*); + +`ifdef UNSUP + t_func u_func(.*); +`endif + + int k; + assign k = i + j; + `ifndef NO_CLASS + t_class u_class(.*); +`ifdef UNSUP + t_method u_method(.*); +`endif + `endif + + t_cstmt u_cstmt; + + int cyc = 0; + + always @(posedge clk) begin + cyc <= cyc + 1; + // a, b, c + a <= ~a; + if (cyc % 2 == 0) b <= ~b; + else c <= ~c; + // v + if (cyc % 3 == 0) v[0] <= 1; + else v <= v << 1; + // i, j + i <= i + 2; + if (cyc % 2 == 0) j <= j + 4; + // t + t[cyc % 5] <= t[cyc % 5] + cyc; + // q + q.push_front(cyc); + `WRITE_VERBOSE(("[%0t] values: clk=%b, cyc=%0d, a=%b, b=%b, v=%b, i=%0x, j=%0x, t=[%0x, %0x, %0x, %0x, %0x], obj.k=%0x\n", + $time, clk, cyc, a, b, v, i, j, t[0], t[1], t[2], t[3], t[4], k)); + `WRITE_VERBOSE((" q=%p\n", q)); + if (cyc == 20) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_event_control_expr_unsup.out b/test_regress/t/t_event_control_expr_unsup.out new file mode 100644 index 000000000..8f8e2cb64 --- /dev/null +++ b/test_regress/t/t_event_control_expr_unsup.out @@ -0,0 +1,9 @@ +%Error-UNSUPPORTED: t/t_event_control_expr.v:55:13: Unsupported: function calls in sensitivity lists + 55 | always @(id(cyc)) begin + | ^~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_event_control_expr.v:82:17: Unsupported: function calls in sensitivity lists + : ... In instance t.u_method + 82 | always @(obj.get_k()) begin + | ^~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_event_control_expr_unsup.pl b/test_regress/t/t_event_control_expr_unsup.pl new file mode 100755 index 000000000..1bf0030aa --- /dev/null +++ b/test_regress/t/t_event_control_expr_unsup.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); # no vltmt, as AstMemberSel is unhandled in V3InstrCount + +top_filename("t_event_control_expr.v"); + +compile( + verilator_flags2 => ['-DUNSUP'], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; From 39af5d020e6913dcb0d696c57bf80dab29241e30 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Mon, 22 Aug 2022 14:26:32 +0200 Subject: [PATCH 027/177] Timing support (#3363) Adds timing support to Verilator. It makes it possible to use delays, event controls within processes (not just at the start), wait statements, and forks. Building a design with those constructs requires a compiler that supports C++20 coroutines (GCC 10, Clang 5). The basic idea is to have processes and tasks with delays/event controls implemented as C++20 coroutines. This allows us to suspend and resume them at any time. There are five main runtime classes responsible for managing suspended coroutines: * `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle` with move semantics and automatic cleanup. * `VlDelayScheduler`, for coroutines suspended by delays. It resumes them at a proper simulation time. * `VlTriggerScheduler`, for coroutines suspended by event controls. It resumes them if its corresponding trigger was set. * `VlForkSync`, used for syncing `fork..join` and `fork..join_any` blocks. * `VlCoroutine`, the return type of all verilated coroutines. It allows for suspending a stack of coroutines (normally, C++ coroutines are stackless). There is a new visitor in `V3Timing.cpp` which: * scales delays according to the timescale, * simplifies intra-assignment timing controls and net delays into regular timing controls and assignments, * simplifies wait statements into loops with event controls, * marks processes and tasks with timing controls in them as suspendable, * creates delay, trigger scheduler, and fork sync variables, * transforms timing controls and fork joins into C++ awaits There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`) that integrate static scheduling with timing. This involves providing external domains for variables, so that the necessary combinational logic gets triggered after coroutine resumption, as well as statements that need to be injected into the design eval function to perform this resumption at the correct time. There is also a function that transforms forked processes into separate functions. See the comments in `verilated_timing.h`, `verilated_timing.cpp`, `V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals documentation for more details. Signed-off-by: Krzysztof Bieganski --- Changes | 2 + bin/verilator | 3 + ci/docker/buildenv/Dockerfile | 2 +- ci/docker/buildenv/README.rst | 2 +- ci/docker/run/Dockerfile | 2 +- configure.ac | 30 + docs/CONTRIBUTORS | 1 + docs/gen/ex_STMTDLY_msg.rst | 2 +- docs/guide/connecting.rst | 23 +- docs/guide/exe_verilator.rst | 33 + docs/guide/extensions.rst | 16 + docs/guide/languages.rst | 72 +- docs/guide/warnings.rst | 79 +- docs/internals.rst | 183 ++ include/verilated.mk.in | 8 + include/verilated_timing.cpp | 180 ++ include/verilated_timing.h | 354 +++ include/verilated_types.h | 4 + src/Makefile_obj.in | 2 + src/V3Active.cpp | 46 +- src/V3Ast.h | 84 +- src/V3AstNodes.cpp | 37 +- src/V3AstNodes.h | 80 +- src/V3Begin.cpp | 27 +- src/V3Cast.cpp | 3 +- src/V3Const.cpp | 6 +- src/V3Dead.cpp | 1 + src/V3Delayed.cpp | 135 +- src/V3EmitCFunc.cpp | 6 + src/V3EmitCFunc.h | 5 + src/V3EmitCHeaders.cpp | 1 + src/V3EmitCImp.cpp | 4 + src/V3EmitCMain.cpp | 7 +- src/V3EmitCMake.cpp | 5 + src/V3EmitCModel.cpp | 50 +- src/V3EmitMk.cpp | 5 + src/V3Error.h | 18 +- src/V3FileLine.h | 2 + src/V3Gate.cpp | 11 + src/V3Global.h | 3 + src/V3Hasher.cpp | 5 + src/V3InstrCount.cpp | 46 +- src/V3Life.cpp | 21 +- src/V3LinkInc.cpp | 19 + src/V3LinkJump.cpp | 11 - src/V3LinkParse.cpp | 4 +- src/V3Options.cpp | 10 + src/V3Options.h | 3 + src/V3Order.cpp | 16 +- src/V3Sched.cpp | 81 +- src/V3Sched.h | 36 + src/V3SchedReplicate.cpp | 4 + src/V3SchedTiming.cpp | 361 ++++ src/V3Split.cpp | 4 + src/V3Timing.cpp | 659 ++++++ src/V3Timing.h | 32 + src/V3Unknown.cpp | 22 +- src/V3Width.cpp | 111 +- src/Verilator.cpp | 9 + src/config_build.h.in | 1 + src/verilog.l | 4 + src/verilog.y | 129 +- test_regress/driver.pl | 81 +- test_regress/t/t_altera_lpm_counter.pl | 2 +- test_regress/t/t_delay.pl | 2 +- test_regress/t/t_delay.v | 2 + test_regress/t/t_delay_func_bad.out | 9 - test_regress/t/t_delay_func_bad.v | 27 - test_regress/t/t_delay_stmtdly_bad.out | 28 +- test_regress/t/t_delay_stmtdly_bad.pl | 2 +- test_regress/t/t_delay_timing.pl | 32 + test_regress/t/t_event_control.out | 12 + ...nt_control_unsup.pl => t_event_control.pl} | 5 +- ...vent_control_unsup.v => t_event_control.v} | 0 test_regress/t/t_event_control_timing.out | 3 + test_regress/t/t_event_control_timing.pl | 30 + test_regress/t/t_event_control_unsup.out | 12 - test_regress/t/t_fork.out | 6 +- test_regress/t/t_fork.pl | 1 + test_regress/t/t_fork_bbox.pl | 2 +- test_regress/t/t_fork_disable.out | 6 +- test_regress/t/t_fork_disable.pl | 2 +- test_regress/t/t_fork_label.pl | 4 +- test_regress/t/t_fork_label_timing.pl | 30 + test_regress/t/t_fork_timing.pl | 29 + test_regress/t/t_func_lib_sub.pl | 1 + test_regress/t/t_func_lib_sub_timing.pl | 25 + test_regress/t/t_func_rand.pl | 2 +- test_regress/t/t_gate_basic.pl | 1 + test_regress/t/t_gate_basic_timing.pl | 32 + test_regress/t/t_gate_delay_unsup.out | 12 +- test_regress/t/t_gate_delay_unsup.pl | 2 +- test_regress/t/t_gen_forif.pl | 1 + test_regress/t/t_gen_intdot.pl | 1 + test_regress/t/t_lib.pl | 2 + test_regress/t/t_lib_nolib.pl | 2 +- test_regress/t/t_lib_prot.pl | 2 + test_regress/t/t_lib_prot_clk_gated.pl | 2 + test_regress/t/t_lib_prot_shared.pl | 2 + test_regress/t/t_lint_stmtdly_bad.out | 2 +- test_regress/t/t_lint_stmtdly_bad.pl | 1 + test_regress/t/t_lint_wait_bad.out | 12 + test_regress/t/t_lint_wait_bad.pl | 22 + test_regress/t/t_math_signed5.pl | 1 + test_regress/t/t_math_signed5_timing.pl | 30 + test_regress/t/t_mem_slot.pl | 2 +- test_regress/t/t_mod_dollar$.pl | 2 +- test_regress/t/t_net_delay.out | 11 + ...g_intra_assign_event.pl => t_net_delay.pl} | 2 +- .../t/{t_timing_net_delay.v => t_net_delay.v} | 2 +- test_regress/t/t_net_delay_timing.pl | 30 + test_regress/t/t_net_delay_timing_sc.pl | 34 + test_regress/t/t_notiming.out | 33 + .../{t_timing_net_delay.pl => t_notiming.pl} | 4 +- test_regress/t/t_notiming.v | 28 + test_regress/t/t_notiming_off.out | 29 + test_regress/t/t_notiming_off.pl | 22 + test_regress/t/t_order.pl | 3 + test_regress/t/t_order_timing.pl | 32 + test_regress/t/t_package_ddecl.pl | 1 + test_regress/t/t_package_ddecl_timing.pl | 30 + test_regress/t/t_parse_delay.pl | 5 +- test_regress/t/t_parse_delay_timing.pl | 25 + test_regress/t/t_timing_always.pl | 28 + test_regress/t/t_timing_always.v | 43 + test_regress/t/t_timing_class.pl | 28 + test_regress/t/t_timing_class.v | 166 ++ test_regress/t/t_timing_class_unsup.out | 6 + ...ay_func_bad.pl => t_timing_class_unsup.pl} | 3 +- test_regress/t/t_timing_class_unsup.v | 12 + test_regress/t/t_timing_clkgen1.pl | 27 +- test_regress/t/t_timing_clkgen2.pl | 28 + test_regress/t/t_timing_clkgen2.v | 42 + test_regress/t/t_timing_clkgen3.pl | 28 + test_regress/t/t_timing_clkgen3.v | 46 + test_regress/t/t_timing_clkgen_sc.pl | 32 + test_regress/t/t_timing_clkgen_unsup.out | 6 + test_regress/t/t_timing_clkgen_unsup.pl | 22 + test_regress/t/t_timing_cmake.pl | 28 + test_regress/t/t_timing_debug1.out | 1909 +++++++++++++++++ test_regress/t/t_timing_debug1.pl | 35 + test_regress/t/t_timing_debug2.out | 616 ++++++ test_regress/t/t_timing_debug2.pl | 35 + test_regress/t/t_timing_delay_callstack.pl | 28 + test_regress/t/t_timing_delay_callstack.v | 61 + test_regress/t/t_timing_events.pl | 28 + test_regress/t/t_timing_events.v | 32 + test_regress/t/t_timing_fork_comb.pl | 36 + test_regress/t/t_timing_fork_comb.v | 58 + test_regress/t/t_timing_fork_join.out | 25 + test_regress/t/t_timing_fork_join.pl | 29 + test_regress/t/t_timing_fork_join.v | 80 + test_regress/t/t_timing_fork_many.pl | 28 + test_regress/t/t_timing_fork_many.v | 43 + test_regress/t/t_timing_fork_unsup.out | 34 + test_regress/t/t_timing_fork_unsup.pl | 20 + test_regress/t/t_timing_fork_unsup.v | 31 + test_regress/t/t_timing_func_bad.out | 25 + test_regress/t/t_timing_func_bad.pl | 20 + test_regress/t/t_timing_func_bad.v | 46 + test_regress/t/t_timing_intra_assign.out | 15 + test_regress/t/t_timing_intra_assign.pl | 40 + test_regress/t/t_timing_intra_assign.v | 56 + .../t/t_timing_intra_assign_delay.out | 31 - test_regress/t/t_timing_intra_assign_delay.v | 24 - .../t/t_timing_intra_assign_event.out | 32 - test_regress/t/t_timing_intra_assign_event.v | 34 - test_regress/t/t_timing_long.pl | 17 +- test_regress/t/t_timing_nba.pl | 28 + test_regress/t/t_timing_nba.v | 41 + test_regress/t/t_timing_net_delay.out | 11 - test_regress/t/t_timing_off.pl | 28 + test_regress/t/t_timing_off.v | 39 + test_regress/t/t_timing_pong.pl | 28 + test_regress/t/t_timing_pong.v | 35 + test_regress/t/t_timing_reentry.pl | 25 +- test_regress/t/t_timing_sched.pl | 28 + test_regress/t/t_timing_sched.v | 61 + test_regress/t/t_timing_sched_if.pl | 28 + test_regress/t/t_timing_sched_if.v | 69 + test_regress/t/t_timing_sched_nba.pl | 28 + test_regress/t/t_timing_sched_nba.v | 50 + test_regress/t/t_timing_unset1.out | 34 + ...tra_assign_delay.pl => t_timing_unset1.pl} | 6 +- test_regress/t/t_timing_unset2.out | 26 + test_regress/t/t_timing_unset2.pl | 22 + test_regress/t/t_timing_wait.pl | 28 + test_regress/t/t_timing_wait.v | 55 + test_regress/t/t_timing_zerodly_unsup.out | 5 + test_regress/t/t_timing_zerodly_unsup.pl | 20 + test_regress/t/t_timing_zerodly_unsup.v | 18 + test_regress/t/t_verilated_all.pl | 2 + test_regress/t/t_verilated_all.v | 2 +- test_regress/t/t_verilated_all_newest.pl | 3 +- test_regress/t/t_verilated_threaded.pl | 3 +- test_regress/t/t_vlt_timing.pl | 30 + test_regress/t/t_vlt_timing.vlt | 11 + test_regress/t/t_wait.out | 35 +- test_regress/t/t_wait.pl | 2 +- test_regress/t/t_wait_timing.pl | 30 + verilator-config.cmake.in | 11 + 201 files changed, 8216 insertions(+), 538 deletions(-) create mode 100644 include/verilated_timing.cpp create mode 100644 include/verilated_timing.h create mode 100644 src/V3SchedTiming.cpp create mode 100644 src/V3Timing.cpp create mode 100644 src/V3Timing.h delete mode 100644 test_regress/t/t_delay_func_bad.out delete mode 100644 test_regress/t/t_delay_func_bad.v create mode 100755 test_regress/t/t_delay_timing.pl create mode 100644 test_regress/t/t_event_control.out rename test_regress/t/{t_event_control_unsup.pl => t_event_control.pl} (90%) rename test_regress/t/{t_event_control_unsup.v => t_event_control.v} (100%) create mode 100644 test_regress/t/t_event_control_timing.out create mode 100755 test_regress/t/t_event_control_timing.pl delete mode 100644 test_regress/t/t_event_control_unsup.out create mode 100755 test_regress/t/t_fork_label_timing.pl create mode 100755 test_regress/t/t_fork_timing.pl create mode 100755 test_regress/t/t_func_lib_sub_timing.pl create mode 100755 test_regress/t/t_gate_basic_timing.pl create mode 100644 test_regress/t/t_lint_wait_bad.out create mode 100755 test_regress/t/t_lint_wait_bad.pl create mode 100755 test_regress/t/t_math_signed5_timing.pl create mode 100644 test_regress/t/t_net_delay.out rename test_regress/t/{t_timing_intra_assign_event.pl => t_net_delay.pl} (89%) rename test_regress/t/{t_timing_net_delay.v => t_net_delay.v} (91%) create mode 100755 test_regress/t/t_net_delay_timing.pl create mode 100755 test_regress/t/t_net_delay_timing_sc.pl create mode 100644 test_regress/t/t_notiming.out rename test_regress/t/{t_timing_net_delay.pl => t_notiming.pl} (90%) create mode 100644 test_regress/t/t_notiming.v create mode 100644 test_regress/t/t_notiming_off.out create mode 100755 test_regress/t/t_notiming_off.pl create mode 100755 test_regress/t/t_order_timing.pl create mode 100755 test_regress/t/t_package_ddecl_timing.pl create mode 100755 test_regress/t/t_parse_delay_timing.pl create mode 100755 test_regress/t/t_timing_always.pl create mode 100644 test_regress/t/t_timing_always.v create mode 100755 test_regress/t/t_timing_class.pl create mode 100644 test_regress/t/t_timing_class.v create mode 100644 test_regress/t/t_timing_class_unsup.out rename test_regress/t/{t_delay_func_bad.pl => t_timing_class_unsup.pl} (82%) create mode 100644 test_regress/t/t_timing_class_unsup.v create mode 100755 test_regress/t/t_timing_clkgen2.pl create mode 100644 test_regress/t/t_timing_clkgen2.v create mode 100755 test_regress/t/t_timing_clkgen3.pl create mode 100644 test_regress/t/t_timing_clkgen3.v create mode 100755 test_regress/t/t_timing_clkgen_sc.pl create mode 100644 test_regress/t/t_timing_clkgen_unsup.out create mode 100755 test_regress/t/t_timing_clkgen_unsup.pl create mode 100755 test_regress/t/t_timing_cmake.pl create mode 100644 test_regress/t/t_timing_debug1.out create mode 100755 test_regress/t/t_timing_debug1.pl create mode 100644 test_regress/t/t_timing_debug2.out create mode 100755 test_regress/t/t_timing_debug2.pl create mode 100755 test_regress/t/t_timing_delay_callstack.pl create mode 100644 test_regress/t/t_timing_delay_callstack.v create mode 100755 test_regress/t/t_timing_events.pl create mode 100644 test_regress/t/t_timing_events.v create mode 100755 test_regress/t/t_timing_fork_comb.pl create mode 100644 test_regress/t/t_timing_fork_comb.v create mode 100644 test_regress/t/t_timing_fork_join.out create mode 100755 test_regress/t/t_timing_fork_join.pl create mode 100644 test_regress/t/t_timing_fork_join.v create mode 100755 test_regress/t/t_timing_fork_many.pl create mode 100644 test_regress/t/t_timing_fork_many.v create mode 100644 test_regress/t/t_timing_fork_unsup.out create mode 100755 test_regress/t/t_timing_fork_unsup.pl create mode 100644 test_regress/t/t_timing_fork_unsup.v create mode 100644 test_regress/t/t_timing_func_bad.out create mode 100755 test_regress/t/t_timing_func_bad.pl create mode 100644 test_regress/t/t_timing_func_bad.v create mode 100644 test_regress/t/t_timing_intra_assign.out create mode 100755 test_regress/t/t_timing_intra_assign.pl create mode 100644 test_regress/t/t_timing_intra_assign.v delete mode 100644 test_regress/t/t_timing_intra_assign_delay.out delete mode 100644 test_regress/t/t_timing_intra_assign_delay.v delete mode 100644 test_regress/t/t_timing_intra_assign_event.out delete mode 100644 test_regress/t/t_timing_intra_assign_event.v create mode 100755 test_regress/t/t_timing_nba.pl create mode 100644 test_regress/t/t_timing_nba.v delete mode 100644 test_regress/t/t_timing_net_delay.out create mode 100755 test_regress/t/t_timing_off.pl create mode 100644 test_regress/t/t_timing_off.v create mode 100755 test_regress/t/t_timing_pong.pl create mode 100644 test_regress/t/t_timing_pong.v create mode 100755 test_regress/t/t_timing_sched.pl create mode 100644 test_regress/t/t_timing_sched.v create mode 100755 test_regress/t/t_timing_sched_if.pl create mode 100644 test_regress/t/t_timing_sched_if.v create mode 100755 test_regress/t/t_timing_sched_nba.pl create mode 100644 test_regress/t/t_timing_sched_nba.v create mode 100644 test_regress/t/t_timing_unset1.out rename test_regress/t/{t_timing_intra_assign_delay.pl => t_timing_unset1.pl} (87%) create mode 100644 test_regress/t/t_timing_unset2.out create mode 100755 test_regress/t/t_timing_unset2.pl create mode 100755 test_regress/t/t_timing_wait.pl create mode 100644 test_regress/t/t_timing_wait.v create mode 100644 test_regress/t/t_timing_zerodly_unsup.out create mode 100755 test_regress/t/t_timing_zerodly_unsup.pl create mode 100644 test_regress/t/t_timing_zerodly_unsup.v create mode 100755 test_regress/t/t_vlt_timing.pl create mode 100644 test_regress/t/t_vlt_timing.vlt create mode 100755 test_regress/t/t_wait_timing.pl diff --git a/Changes b/Changes index 710b675db..0fae642f7 100644 --- a/Changes +++ b/Changes @@ -17,6 +17,8 @@ Verilator 5.001 devel * Fully support the Active and NBA scheduling regions as defined by the SystemVerilog standard (IEEE 1800-2017 chapter 4). This means all generated clocks are now simulated correctly (#3278, #3384). [Geza Lore, Shunyao CAD] +* Support timing controls (delays, event controls in any location, wait + statements) and forks. See docs for details. [Krzysztof Bieganski, Antmicro Ltd] Verilator 4.225 devel diff --git a/bin/verilator b/bin/verilator index f2c858558..703a09ed4 100755 --- a/bin/verilator +++ b/bin/verilator @@ -341,6 +341,7 @@ detailed descriptions of these arguments. --lib-create Create a DPI library +libext++[ext]... Extensions for finding modules --lint-only Lint, but do not make output + --main Generate a main C++ file --make Generate scripts for specified build tool -MAKEFLAGS Arguments to pass to make during --build --max-num-width Maximum number width (default: 64K) @@ -393,6 +394,8 @@ detailed descriptions of these arguments. --threads Enable multithreading --threads-dpi Enable multithreaded DPI --threads-max-mtasks Tune maximum mtask partitioning + --timing Enable timing support + --no-timing Disable timing support --timescale Sets default timescale --timescale-override Overrides all timescales --top Alias of --top-module diff --git a/ci/docker/buildenv/Dockerfile b/ci/docker/buildenv/Dockerfile index 7e6682357..7adf4b3d2 100644 --- a/ci/docker/buildenv/Dockerfile +++ b/ci/docker/buildenv/Dockerfile @@ -6,7 +6,7 @@ # Version 2.0. # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -FROM ubuntu:20.04 +FROM ubuntu:22.04 RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive \ diff --git a/ci/docker/buildenv/README.rst b/ci/docker/buildenv/README.rst index 5e7110c84..3ff025fc9 100644 --- a/ci/docker/buildenv/README.rst +++ b/ci/docker/buildenv/README.rst @@ -8,7 +8,7 @@ Verilator build. It uses the following parameters: - Source revision (default: master) -- Compiler (GCC 9.3.0, clang 10.0.0, default: 9.3.0) +- Compiler (GCC 10.3.0, clang 10.0.0, default: 10.3.0) The container is published as ``verilator/verilator-buildenv`` on `docker hub diff --git a/ci/docker/run/Dockerfile b/ci/docker/run/Dockerfile index 18c40483f..fa5c98519 100644 --- a/ci/docker/run/Dockerfile +++ b/ci/docker/run/Dockerfile @@ -6,7 +6,7 @@ # Version 2.0. # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -FROM ubuntu:20.04 +FROM ubuntu:22.04 RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive \ diff --git a/configure.ac b/configure.ac index 281dd64cd..19d022983 100644 --- a/configure.ac +++ b/configure.ac @@ -374,6 +374,36 @@ _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_WEXTRA,-Wlogical-op) _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_WEXTRA,-Wthread-safety) AC_SUBST(CFG_CXXFLAGS_WEXTRA) +# Flags for coroutine support for dynamic scheduling +_MY_CXX_CHECK_IFELSE( + -fcoroutines-ts, + [CFG_CXXFLAGS_COROUTINES="-fcoroutines-ts"], + [CFG_CXXFLAGS_COROUTINES="-fcoroutines"]) +AC_SUBST(CFG_CXXFLAGS_COROUTINES) + +# HAVE_COROUTINES +# Check if coroutines are supported at all +AC_MSG_CHECKING([whether coroutines are supported by $CXX]) +ACO_SAVE_CXXFLAGS="$CXXFLAGS" +CXXFLAGS="$CXXFLAGS $CFG_CXXFLAGS_COROUTINES" +AC_LINK_IFELSE( + [AC_LANG_PROGRAM([ +#ifdef __clang__ +#define __cpp_impl_coroutine 1 +#endif +#include + ],[[]])], + [_my_result=yes + AC_DEFINE([HAVE_COROUTINES],[1],[Defined if coroutines are supported by $CXX])], + [AC_LINK_IFELSE( + [AC_LANG_PROGRAM([#include ],[[]])], + [_my_result=yes + AC_DEFINE([HAVE_COROUTINES],[1],[Defined if coroutines are supported by $CXX])], + [_my_result=no])]) +AC_MSG_RESULT($_my_result) +CXXFLAGS="$ACO_SAVE_CXXFLAGS" +AC_SUBST(HAVE_COROUTINES) + # Flags for compiling Verilator internals including parser always _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-Qunused-arguments) _MY_CXX_CHECK_OPT(CFG_CXXFLAGS_SRC,-faligned-new) diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 228787d5e..5f2441d2d 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -88,6 +88,7 @@ Nathan Kohagen Nathan Myers Patrick Stewart Paul Wright +Pawel Sagan Peter Horvath Peter Monsson Philipp Wagner diff --git a/docs/gen/ex_STMTDLY_msg.rst b/docs/gen/ex_STMTDLY_msg.rst index fe19fb943..3b7c7bc69 100644 --- a/docs/gen/ex_STMTDLY_msg.rst +++ b/docs/gen/ex_STMTDLY_msg.rst @@ -1,4 +1,4 @@ .. comment: generated by t_lint_stmtdly_bad .. code-block:: - %Warning-STMTDLY: example.v:1:8 Unsupported: Ignoring delay on this delayed statement. + %Warning-STMTDLY: example.v:1:8 Ignoring delay on this statement due to --no-timing diff --git a/docs/guide/connecting.rst b/docs/guide/connecting.rst index 443702c7d..beb3ad774 100644 --- a/docs/guide/connecting.rst +++ b/docs/guide/connecting.rst @@ -94,7 +94,10 @@ commented example. Top level IO signals are read and written as members of the model. You call the model's :code:`eval()` method to evaluate the model. When the simulation is complete call the model's :code:`final()` method to execute -any SystemVerilog final blocks, and complete any assertions. See +any SystemVerilog final blocks, and complete any assertions. If using +:vlopt:`--timing`, there are two additional functions for checking if +there are any events pending in the simulation due to delays, and for +retrieving the simulation time of the next delayed event. See :ref:`Evaluation Loop`. @@ -440,10 +443,24 @@ there is only a single design, you would call :code:`eval_step()` then :code:`eval_end_step()`; in fact :code:`eval()` described above is just a wrapper which calls these two functions. +3. If using delays and :vlopt:`--timing`, there are two additional methods +the user should call: + + * :code:`designp->eventsPending()`, which returns :code:`true` if there are + any delayed events penging, + * :code:`designp->nextTimeSlot()`, which returns the simulation time of the + next delayed event. This method can only be called if + :code:`designp->nextTimeSlot()` returned :code:`true`. +Call :code:`eventsPending()` to check if you should continue with the +simulation, and then :code:`nextTimeSlot()` to move simulation time forward. +:vlopt:`--main` can be used with :vlopt:`--timing` to generate a basic example +of a timing-enabled eval loop. + When :code:`eval()` (or :code:`eval_step()`) is called Verilator looks for changes in clock signals and evaluates related sequential always blocks, -such as computing always_ff @ (posedge...) outputs. Then Verilator -evaluates combinatorial logic. +such as computing always_ff @ (posedge...) outputs. With :vlopt:`--timing`, it +resumes any delayed processes awaiting the current simulation time. Then +Verilator evaluates combinational logic. Note combinatorial logic is not computed before sequential always blocks are computed (for speed reasons). Therefore it is best to set any non-clock diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index ae1d2af79..25891aa32 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -684,6 +684,16 @@ Summary: If the design is not to be completely Verilated see also the :vlopt:`--bbox-sys` and :vlopt:`--bbox-unsup` options. +.. option:: --main + + Generates a simple main C++ file. Without :vlopt:`--timing`, you need to + modify this file to provide some stimuli to the design. However, this option + is especially useful with :vlopt:`--timing` and delay-generated clocks, as + then the main file provides a timing-enabled eval loop and requires no + modification by the user. :vlopt:`--build` can then be used to build the + simulation, allowing you to use Verilator without directly invoking + the C++ toolchain. + .. option:: --make Generates a script for the specified build tool. @@ -1164,6 +1174,16 @@ Summary: module. As "1fs" is the finest time precision it may be desirable to always use a precision of "1fs". +.. option:: --timing + +.. option:: --no-timing + + Enables/disables support for timing constructs such as delays, event + controls (unless it's at the top of a process), wait statements, and joins. + When disabled, timing control constructs are ignored the same way as they + were in earlier versions of Verilator. Enabling this feature requires a C++ + compiler with coroutine support (GCC 10, Clang 5, or newer). + .. option:: --top .. option:: --top-module @@ -1747,6 +1767,19 @@ The grammar of configuration commands is as follows: Same as :option:`/*verilator&32;split_var*/` metacomment. +.. option:: timing_on [-file "" [-lines [ - ]]] + +.. option:: timing_off [-file "" [-lines [ - ]]] + + Enables/disables timing constructs for the specified file and lines. + When disabled, all timing control constructs in the specified source + code locations are ignored the same way as with the + :option:`--no-timing`, and code:`fork`/:code:`join*` blocks are + converted into :code:`begin`/:code:`end` blocks. + + Same as :option:`/*verilator&32;timing_on*/`, + :option:`/*verilator&32;timing_off*/` metacomments. + .. option:: tracing_on [-file "" [-lines [ - ]]] .. option:: tracing_off [-file "" [-lines [ - ]]] diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index 230bbac69..f353f6f43 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -505,6 +505,22 @@ or "`ifdef`"'s may break other tools. Verilator) text that should be passed through to the XML output as a tag, for use by downstream applications. +.. option:: /*verilator&32;timing_off*/ + + Ignore all timing constructs after this metacomment. All timing controls + behave as if they were not there (the same way as with + :option:`--no-timing`), and :code:`fork`/:code:`join*` blocks are + converted into :code:`begin`/:code:`end` blocks. + + Same as :option:`timing_off` configuration file option. + +.. option:: /*verilator&32;timing_on*/ + + Re-enable all timing constructs after this metacomment (only applicable + after :option:`timing_off`). + + Same as :option:`timing_on` configuration file option. + .. option:: /*verilator&32;trace_init_task*/ Attached to a DPI import to indicate that function should be called when diff --git a/docs/guide/languages.rst b/docs/guide/languages.rst index 063ebad78..e600dc314 100644 --- a/docs/guide/languages.rst +++ b/docs/guide/languages.rst @@ -95,6 +95,72 @@ keywords on case statement, as well as "unique" on if statements. However, "priority if" is currently ignored. +Time +==== + +With :vlopt:`--timing`, all timing controls are supported: + +* delay statements, +* event control statements not only at the top of a process, +* intra-assignment timing controls, +* net delays, +* :code:`wait` statements, + +as well as all flavors of :code:`fork`. + +Compiling a verilated design that makes use of these features requires a +compiler with C++20 coroutine support, e.g. Clang 5, GCC 10, or newer. + +:code:`#0` delays cause Verilator to issue the :option:`ZERODLY` warning, as +they work differently than described in the LRM. They do not schedule process +resumption in the Inactive region, though the process will get resumed in the +same time slot. + +Rising/falling/turn-off delays are currently unsupported and cause the +:option:`RISEFALLDLY` warning. + +Minimum/typical/maximum delays are currently unsupported. The typical delay is +always the one chosen. Such expressions cause the :option:`MINTYPMAX` warning. + +Another consequence of using :vlopt:`--timing` is that the :vlopt:`--main` +option generates a main file with a proper timing eval loop, eliminating the +need for writing any driving C++ code. You can then simply compile the +simulation (perhaps using :vlopt:`--build`) and run it. + +With :vlopt:`--no-timing`, all timing controls cause the :option:`NOTIMING` +error, with the exception of: + +* delay statements – they are ignored (as they are in synthesis), though they + do issue a :option:`STMTDLY` warning, +* intra-assignment timing controls – they are ignored, though they do issue an + :option:`ASSIGNDLY` warning, +* net delays – they are ignored, +* event controls at the top of the procedure, + +Forks cause this error as well, with the exception of: + +* forks with no statements, +* :code:`fork..join` or :code:`fork..join_any` with one statement, +* forks with :vlopt:`--bbox-unsup`. + +If neither :vlopt:`--timing` nor :vlopt:`--no-timing` is specified, all timing +controls cause the :option:`NEEDTIMINGOPT` error, with the exception of event +controls at the top of the process. Forks cause this error as well, with the +exception of: + +* forks with no statements, +* :code:`fork..join` or :code:`fork..join_any` with one statement, +* forks with :vlopt:`--bbox-unsup`. + +Timing controls and forks can also be ignored in specific files or parts of +files. The :option:`/*verilator&32;timing_off*/` and +:option:`/*verilator&32;timing_off*/` metacomments will make Verilator ignore +the encompassed timing controls and forks, regardless of the chosen +:vlopt:`--timing` or :vlopt:`--no-timing` option. This can also be achieved +using the :option:`timing_off` and :option:`timing_off` options in Verilator +configuration files. + + .. _Language Limitations: Language Limitations @@ -180,12 +246,6 @@ structure from blocking, and another from non-blocking assignments is unsupported. -Time ----- - -All delays (#) are ignored, as they are in synthesis. - - .. _Unknown States: Unknown States diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 1b7026d4f..a94898a10 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -112,6 +112,8 @@ List Of Warnings simulators, however at one point this was a common style so disabled by default as a code style warning. + This warning is issued only if Verilator is run with :vlopt:`--no-timing`. + .. option:: ASSIGNIN @@ -503,9 +505,10 @@ List Of Warnings constructs (e.g. always_ff and always_latch). Another way DIDNOTCONVERGE may occur is if # delays are used to generate - clocks. Verilator ignores the delays and gives an :option:`ASSIGNDLY` - or :option:`STMTDLY` warning. If these were suppressed, due to the - absence of the delay, the code may now oscillate. + clocks if Verilator is run with :vlopt:`--no-timing`. In this mode, + Verilator ignores the delays and gives an :option:`ASSIGNDLY` or + :option:`STMTDLY` warning. If these were suppressed, due to the absence of + the delay, the code may now oscillate. Finally, rare, more difficult cases can be debugged like a C++ program; either enter :command:`gdb` and use its tracing facilities, or edit the @@ -700,9 +703,9 @@ List Of Warnings Warns that a while or for statement has a condition that is always true. and thus results in an infinite loop if the statement ever executes. - This might be unintended behavior if the loop body contains statements - that in other simulators would make time pass, which Verilator is - ignoring due to e.g. ``STMTDLY`` warnings being disabled. + This might be unintended behavior if Verilator is run with + :vlopt:`--no-timing` and the loop body contains statements that would make + time pass otherwise. Ignoring this warning will only suppress the lint check, it will simulate correctly (i.e. hang due to the infinite loop). @@ -763,6 +766,16 @@ List Of Warnings simulate correctly. +.. option:: MINTYPMAX + + .. code-block:: sv + + #(3:5:8) clk = ~clk; + + Warns that minimum, typical, and maximum delay expressions are currently + unsupported. Only the typical delay value is used by Verilator. + + .. option:: MODDUP .. TODO better example @@ -833,6 +846,13 @@ List Of Warnings input. +.. option:: NEEDTIMINGOPT + + Error when a timing-related construct, such as an event control or delay, + has been encountered, without specifying how Verilator should handle it + (neither :vlopt:`--timing` nor :vlopt:`--no-timing` option was provided). + + .. option:: NOLATCH .. TODO better example @@ -845,6 +865,13 @@ List Of Warnings simulate correctly. +.. option:: NOTIMING + + Error when a timing-related construct that requires :vlopt:`--timing` has + been encountered. Issued only if Verilator is run with the + :vlopt:`--no-timing` option. + + .. option:: NULLPORT Warns that a null port was detected in the module definition port @@ -1082,6 +1109,16 @@ List Of Warnings "Duplicate macro arguments with name". +.. option:: RISEFALLDLY + + .. code-block:: sv + + and #(1,2,3) AND (out, a, b); + + Warns that rising, falling, and turn-off delays are currently unsupported. + The first (rising) delay is used for all cases. + + .. option:: SELRANGE Warns that a selection index will go out of bounds. @@ -1181,11 +1218,11 @@ List Of Warnings .. include:: ../../docs/gen/ex_STMTDLY_msg.rst - This is a warning because Verilator does not support delayed statements. - It will ignore all such delays. In many cases ignoring a delay might be - harmless, but if the delayed statement is, as in this example, used to - cause some important action at a later time, it might be an important - difference. + This warning is issued only if Verilator is run with :vlopt:`--no-timing`. + All delays on statements are ignored in this mode. In many cases ignoring a + delay might be harmless, but if the delayed statement is, as in this + example, used to cause some important action at a later time, it might be an + important difference. Some possible workarounds: @@ -1195,6 +1232,8 @@ List Of Warnings * Convert the statement into a FSM, or other statement that tests against $time. + * Run Verilator with :vlopt:`--timing`. + .. option:: SYMRSVDWORD @@ -1576,6 +1615,16 @@ List Of Warnings To resolve, rename the variable to a unique name. +.. option:: WAITCONST + + .. code-block:: sv + + wait(0); // Blocks forever + + Warns that a `wait` statement awaits a constant condition, which means it + either blocks forever or never blocks. + + .. option:: WIDTH Warns that based on width rules of Verilog: @@ -1645,6 +1694,14 @@ List Of Warnings width to the parameter usage (:code:`{PAR[31:0], PAR[31:0]}`). +.. option:: ZERODLY + + Warns that `#0` delays do not schedule the process to be resumed in the + Inactive region. Such processes do get resumed in the same time slot + somewhere in the Active region. Issued only if Verilator is run with the + :vlopt:`--timing` option. + + Historical Warnings =================== diff --git a/docs/internals.rst b/docs/internals.rst index 104f18503..09a88bc5b 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -504,6 +504,189 @@ the top level `_eval` function, which on the high level has the form: } +Timing +------ + +Timing support in Verilator utilizes C++ coroutines, which is a new feature in +C++20. The basic idea is to represent processes and tasks that await a certain +event or simulation time as coroutines. These coroutines get suspended at the +await, and resumed whenever the triggering event occurs, or at the expected +simulation time. + +There are several runtime classes used for managing such coroutines defined in +``verilated_timing.h`` and ``verilated_timing.cpp``. + +``VlCoroutineHandle`` +^^^^^^^^^^^^^^^^^^^^^ + +A thin wrapper around an ``std::coroutine_handle<>``. It forces move semantics, +destroys the coroutine if it remains suspended at the end of the design's +lifetime, and prevents multiple ``resume`` calls in the case of +``fork..join_any``. + +``VlCoroutine`` +^^^^^^^^^^^^^^^ + +Return value of all coroutines. Together with the promise type contained +within, it allows for chaining coroutines – resuming coroutines from up the +call stack. The calling coroutine's handle is saved in the promise object as a +continuation, that is, the coroutine that must be resumed after the promise's +coroutine finishes. This is necessary as C++ coroutines are stackless, meaning +each one is suspended independently of others in the call graph. + +``VlDelayScheduler`` +^^^^^^^^^^^^^^^^^^^^ + +This class manages processes suspended by delays. There is one instance of this +class per design. Coroutines ``co_await`` this object's ``delay`` function. +Internally, they are stored in a heap structure sorted by simulation time in +ascending order. When ``resume`` is called on the delay scheduler, all +coroutines awaiting the current simulation time are resumed. The current +simulation time is retrieved from a ``VerilatedContext`` object. + +``VlTriggerScheduler`` +^^^^^^^^^^^^^^^^^^^^^^ + +This class manages processes that await events (triggers). There is one such +object per each trigger awaited by coroutines. Coroutines ``co_await`` this +object's ``trigger`` function. They are stored in two stages – `uncommitted` +and `ready`. First, they land in the `uncommitted` stage, and cannot be +resumed. The ``resume`` function resumes all coroutines from the `ready` stage +and moves `uncommitted` coroutines into `ready`. The ``commit`` function only +moves `uncommitted` coroutines into `ready`. + +This split is done to avoid self-triggering and triggering coroutines multiple +times. See the `Scheduling with timing` section for details on how this is +used. + +``VlForkSync`` +^^^^^^^^^^^^^^ + +Used for synchronizing ``fork..join`` and ``fork..join_any``. Forking +coroutines ``co_await`` its ``join`` function, and forked ones call ``done`` +when they're finished. Once the required number of coroutines (set using +``setCounter``) finish execution, the forking coroutine is resumed. + +Awaitable utilities +^^^^^^^^^^^^^^^^^^^ + +There are also two small utility awaitable types: + +* ``VlNow`` is an awaitable that suspends and immediately resumes coroutines. + It is used for forcing a coroutine to be moved onto the heap. See the `Forks` + section for more detail. +* ``VlForever`` is used for blocking a coroutine forever. See the `Timing pass` + section for more detail. + +Timing pass +^^^^^^^^^^^ + +The visitor in ``V3Timing.cpp`` transforms each timing control into a ``co_await``. + +* event controls are turned into ``co_await`` on a trigger scheduler's + ``trigger`` method. The awaited trigger scheduler is the one corresponding to + the sentree referenced by the event control. This sentree is also referenced + by the ``AstCAwait`` node, to be used later by the static scheduling code. +* delays are turned into ``co_await`` on a delay scheduler's ``delay`` method. + The created ``AstCAwait`` nodes also reference a special sentree related to + delays, to be used later by the static scheduling code. +* ``join`` and ``join_any`` are turned into ``co_await`` on a ``VlForkSync``'s + ``join`` method. Each forked process gets a ``VlForkSync::done`` call at the + end. + +Assignments with intra-assignment timing controls are simplified into +assignments after those timing controls, with the LHS and RHS values evaluated +before them and stored in temporary variables. + +``wait`` statements are transformed into while loops that check the condition +and then await changes in variables used in the condition. If the condition is +always false, the ``wait`` statement is replaced by a ``co_await`` on a +``VlForever``. This is done instead of a return in case the ``wait`` is deep in +a call stack (otherwise the coroutine's caller would continue execution). + +Each sub-statement of a ``fork`` is put in an ``AstBegin`` node for easier +grouping. In a later step, each of these gets transformed into a new, separate +function. See the `Forks` section for more detail. + +Processes that use awaits are marked as suspendable. Later, during ``V3Sched``, +they are transformed into coroutines. Functions that use awaits get the return +type of ``VlCoroutine``. This immediately makes them coroutines. Note that if a +process calls a function that is a coroutine, the call gets wrapped in an +await, which means the process itself will be marked as suspendable. A virtual +function is a coroutine if any of its overriding or overridden functions are +coroutines. The visitor keeps a dependency graph of functions and processes to +handle such cases. + +Scheduling with timing +^^^^^^^^^^^^^^^^^^^^^^ + +Timing features in Verilator are built on top of the static scheduler. Triggers +are used for determining which delay or trigger schedulers should resume. A +special trigger is used for the delay scheduler. This trigger is set if there +are any coroutines awaiting the current simulation time +(``VlDelayScheduler::awaitingCurrentTime()``). + +All triggers used by a suspendable process are mapped to variables written in +that process. When ordering code using ``V3Order``, these triggers are provided +as external domains of these variables. This ensures that the necessary +combinational logic is triggered after a coroutine resumption. + +There are two functions for managing timing logic called by ``_eval()``: + +* ``_timing_commit()``, which commits all coroutines whose triggers were not set + in the current iteration, +* ``_timing_resume()``, which calls `resume()` on all trigger and delay + schedulers whose triggers were set in the current iteration. + +Thanks to this separation, a coroutine awaiting a trigger cannot be suspended +and resumed in the same iteration, and it cannot be resumed before it suspends. + +All coroutines are committed and resumed in the 'act' eval loop. With timing +features enabled, the ``_eval()`` function takes this form: + +:: + void _eval() { + while (true) { + _eval__triggers__ico(); + if (!ico_triggers.any()) break; + _eval_ico(); + } + + while (true) { + while (true) { + _eval__triggers__act(); + + // Commit all non-triggered coroutines + _timing_commit(); + + if (!act_triggers.any()) break; + latch_act_triggers_for_nba(); + + // Resume all triggered coroutines + _timing_resume(); + + _eval_act(); + } + if (!nba_triggers.any()) break; + _eval_nba(); + } + } + +Forks +^^^^^ + +After the scheduling step, forks sub-statements are transformed into separate +functions, and these functions are called in place of the sub-statements. These +calls must be without ``co_await``, so that suspension of a forked process +doesn't suspend the forking process. + +In forked processes, references to local variables are only allowed in +``fork..join``, as this is the only case that ensures the lifetime of these +locals is at least as long as the execution of the forked processes. This is +where ``VlNow`` is used, to ensure the locals are moved to the heap before they +are passed by reference to the forked processes. + + Multithreaded Mode ------------------ diff --git a/include/verilated.mk.in b/include/verilated.mk.in index c86e93676..b01799dfb 100644 --- a/include/verilated.mk.in +++ b/include/verilated.mk.in @@ -27,6 +27,8 @@ CFG_CXXFLAGS_STD_NEWEST = @CFG_CXXFLAGS_STD_NEWEST@ CFG_CXXFLAGS_NO_UNUSED = @CFG_CXXFLAGS_NO_UNUSED@ # Compiler flags that turn on extra warnings CFG_CXXFLAGS_WEXTRA = @CFG_CXXFLAGS_WEXTRA@ +# Compiler flags that enable coroutine support +CFG_CXXFLAGS_COROUTINES = @CFG_CXXFLAGS_COROUTINES@ # Linker libraries for multithreading CFG_LDLIBS_THREADS = @CFG_LDLIBS_THREADS@ @@ -142,6 +144,12 @@ ifneq ($(VM_THREADS),0) endif endif +ifneq ($(VM_TIMING),0) + ifneq ($(VM_TIMING),) + CPPFLAGS += $(CFG_CXXFLAGS_COROUTINES) + endif +endif + ifneq ($(VK_C11),0) ifneq ($(VK_C11),) # Need C++11 at least, so always default to newest diff --git a/include/verilated_timing.cpp b/include/verilated_timing.cpp new file mode 100644 index 000000000..8c0da322e --- /dev/null +++ b/include/verilated_timing.cpp @@ -0,0 +1,180 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Code available from: https://verilator.org +// +// Copyright 2022 by Wilson Snyder. This program is free software; you can +// redistribute it and/or modify it under the terms of either the GNU Lesser +// General Public License Version 3 or the Perl Artistic License Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//========================================================================= +/// +/// \file +/// \brief Verilated timing implementation code +/// +/// This file must be compiled and linked against all Verilated objects +/// that use timing features. +/// +/// See the internals documentation docs/internals.rst for details. +/// +//========================================================================= + +#include "verilated_timing.h" + +//====================================================================== +// VlCoroutineHandle:: Methods + +void VlCoroutineHandle::resume() { + // Only null if we have a fork..join_any and one of the other child processes resumed the + // main process + if (VL_LIKELY(m_coro)) { + VL_DEBUG_IF(VL_DBG_MSGF(" Resuming: "); dump();); + m_coro(); + m_coro = nullptr; + } +} + +#ifdef VL_DEBUG +void VlCoroutineHandle::dump() { + VL_DEBUG_IF(VL_PRINTF("Process waiting at %s:%d\n", m_filename, m_linenum);); +} +#endif + +//====================================================================== +// VlDelayScheduler:: Methods + +#ifdef VL_DEBUG +void VlDelayScheduler::VlDelayedCoroutine::dump() { + VL_DEBUG_IF(VL_DBG_MSGF(" Awaiting time %lu: ", m_timestep);); + m_handle.dump(); +} +#endif + +void VlDelayScheduler::resume() { +#ifdef VL_DEBUG + dump(); + VL_DEBUG_IF(VL_DBG_MSGF(" Resuming delayed processes\n");); +#endif + while (awaitingCurrentTime()) { + if (m_queue.front().m_timestep != m_context.time()) { + VL_FATAL_MT(__FILE__, __LINE__, "", + "%Error: Encountered process that should've been resumed at an " + "earlier simulation time. Missed a time slot?"); + } + // Move max element in the heap to the end + std::pop_heap(m_queue.begin(), m_queue.end()); + VlCoroutineHandle handle = std::move(m_queue.back().m_handle); + m_queue.pop_back(); + handle.resume(); + } +} + +uint64_t VlDelayScheduler::nextTimeSlot() { + if (empty()) { + VL_FATAL_MT(__FILE__, __LINE__, "", "%Error: There is no next time slot scheduled"); + } + return m_queue.front().m_timestep; +} + +#ifdef VL_DEBUG +void VlDelayScheduler::dump() { + if (m_queue.empty()) { + VL_DEBUG_IF(VL_DBG_MSGF(" No delayed processes:\n");); + } else { + VL_DEBUG_IF(VL_DBG_MSGF(" Delayed processes:\n");); + for (auto& susp : m_queue) susp.dump(); + } +} +#endif + +//====================================================================== +// VlTriggerScheduler:: Methods + +void VlTriggerScheduler::resume(const char* eventDescription) { +#ifdef VL_DEBUG + dump(eventDescription); + VL_DEBUG_IF(VL_DBG_MSGF(" Resuming processes waiting for %s\n", eventDescription);); +#endif + for (auto& susp : m_ready) susp.resume(); + m_ready.clear(); + commit(eventDescription); +} + +void VlTriggerScheduler::commit(const char* eventDescription) { +#ifdef VL_DEBUG + if (!m_uncommitted.empty()) { + VL_DEBUG_IF( + VL_DBG_MSGF(" Committing processes waiting for %s:\n", eventDescription); + for (auto& susp + : m_uncommitted) { + VL_DBG_MSGF(" - "); + susp.dump(); + }); + } +#endif + m_ready.reserve(m_ready.size() + m_uncommitted.size()); + m_ready.insert(m_ready.end(), std::make_move_iterator(m_uncommitted.begin()), + std::make_move_iterator(m_uncommitted.end())); + m_uncommitted.clear(); +} + +#ifdef VL_DEBUG +void VlTriggerScheduler::dump(const char* eventDescription) { + if (m_ready.empty()) { + VL_DEBUG_IF( + VL_DBG_MSGF(" No ready processes waiting for %s\n", eventDescription);); + } else { + VL_DEBUG_IF(for (auto& susp + : m_ready) { + VL_DBG_MSGF(" Ready processes waiting for %s:\n", eventDescription); + VL_DBG_MSGF(" - "); + susp.dump(); + }); + } + if (!m_uncommitted.empty()) { + VL_DEBUG_IF( + VL_DBG_MSGF(" Uncommitted processes waiting for %s:\n", eventDescription); + for (auto& susp + : m_uncommitted) { + VL_DBG_MSGF(" - "); + susp.dump(); + }); + } +} +#endif + +//====================================================================== +// VlForkSync:: Methods + +void VlForkSync::done(const char* filename, int linenum) { + VL_DEBUG_IF(VL_DBG_MSGF(" Process forked at %s:%d finished", filename, linenum);); + if (m_join->m_counter > 0) m_join->m_counter--; + if (m_join->m_counter == 0) m_join->m_susp.resume(); +} + +//====================================================================== +// VlCoroutine:: Methods + +VlCoroutine::VlPromise::~VlPromise() { + // Indicate to the return object that the coroutine has finished or been destroyed + if (m_corop) m_corop->m_promisep = nullptr; + // If there is a continuation, destroy it + if (m_continuation) m_continuation.destroy(); +} + +std::suspend_never VlCoroutine::VlPromise::final_suspend() noexcept { + // Indicate to the return object that the coroutine has finished + if (m_corop) { + m_corop->m_promisep = nullptr; + // Forget the return value, we won't need it and it won't be able to let us know if + // it's destroyed + m_corop = nullptr; + } + // If there is a continuation, resume it + if (m_continuation) { + m_continuation(); + m_continuation = nullptr; + } + return {}; +} diff --git a/include/verilated_timing.h b/include/verilated_timing.h new file mode 100644 index 000000000..f7fd2ae3f --- /dev/null +++ b/include/verilated_timing.h @@ -0,0 +1,354 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Code available from: https://verilator.org +// +// Copyright 2022 by Wilson Snyder. This program is free software; you can +// redistribute it and/or modify it under the terms of either the GNU Lesser +// General Public License Version 3 or the Perl Artistic License Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +/// +/// \file +/// \brief Verilated timing header +/// +/// This file is included automatically by Verilator in some of the C++ files +/// it generates if timing features are used. +/// +/// This file is not part of the Verilated public-facing API. +/// It is only for internal use. +/// +/// See the internals documentation docs/internals.rst for details. +/// +//************************************************************************* +#ifndef VERILATOR_VERILATED_TIMING_H_ +#define VERILATOR_VERILATED_TIMING_H_ + +#include "verilated.h" + +// clang-format off +// Some preprocessor magic to support both Clang and GCC coroutines with both libc++ and libstdc++ +#ifdef __clang__ +# if __clang_major__ < 14 +# ifdef __GLIBCXX__ // Using stdlibc++ +# define __cpp_impl_coroutine 1 // Clang doesn't define this, but it's needed for libstdc++ +# include + namespace std { // Bring coroutine library into std::experimental, as Clang < 14 expects it to be there + namespace experimental { + using namespace std; + } + } +# else // Using libc++ +# include // Clang older than 14, coroutines are under experimental + namespace std { + using namespace experimental; // Bring std::experimental into the std namespace + } +# endif +# else // Clang >= 14 +# if __GLIBCXX__ // Using stdlibc++ +# define __cpp_impl_coroutine 1 // Clang doesn't define this, but it's needed for libstdc++ +# endif +# include +# endif +#else // Not Clang +# include +#endif +// clang-format on + +//============================================================================= +// VlCoroutineHandle is a non-copyable (but movable) coroutine handle. On resume, the handle is +// cleared, as we assume that either the coroutine has finished and deleted itself, or, if it got +// suspended, another VlCoroutineHandle was created to manage it. + +class VlCoroutineHandle final { + VL_UNCOPYABLE(VlCoroutineHandle); + + // MEMBERS + std::coroutine_handle<> m_coro; // The wrapped coroutine handle +#ifdef VL_DEBUG + const char* m_filename; + int m_linenum; +#endif + +public: + // CONSTRUCTORS + // Construct + VlCoroutineHandle(std::coroutine_handle<> coro = nullptr, const char* filename = nullptr, + int linenum = 0) + : m_coro { + coro + } +#ifdef VL_DEBUG + , m_filename{filename}, m_linenum { linenum } +#endif + {} + // Move the handle, leaving a nullptr + VlCoroutineHandle(VlCoroutineHandle&& moved) + : m_coro { + std::exchange(moved.m_coro, nullptr) + } +#ifdef VL_DEBUG + , m_filename{moved.m_filename}, m_linenum { moved.m_linenum } +#endif + {} + // Destroy if the handle isn't null + ~VlCoroutineHandle() { + // Usually these coroutines should get resumed; we only need to clean up if we destroy a + // model with some coroutines suspended + if (VL_UNLIKELY(m_coro)) m_coro.destroy(); + } + // METHODS + // Move the handle, leaving a null handle + auto& operator=(VlCoroutineHandle&& moved) { + m_coro = std::exchange(moved.m_coro, nullptr); + return *this; + } + // Resume the coroutine if the handle isn't null + void resume(); +#ifdef VL_DEBUG + void dump(); +#endif +}; + +//============================================================================= +// VlDelayScheduler stores coroutines to be resumed at a certain simulation time. If the current +// time is equal to a coroutine's resume time, the coroutine gets resumed. + +class VlDelayScheduler final { + // TYPES + struct VlDelayedCoroutine { + uint64_t m_timestep; // Simulation time when the coroutine should be resumed + VlCoroutineHandle m_handle; // The suspended coroutine to be resumed + + // Comparison operator for std::push_heap(), std::pop_heap() + bool operator<(const VlDelayedCoroutine& other) const { + return m_timestep > other.m_timestep; + } +#ifdef VL_DEBUG + void dump(); +#endif + }; + using VlDelayedCoroutineQueue = std::vector; + + // MEMBERS + VerilatedContext& m_context; + VlDelayedCoroutineQueue m_queue; // Coroutines to be restored at a certain simulation time + +public: + // CONSTRUCTORS + VlDelayScheduler(VerilatedContext& context) + : m_context{context} {} + // METHODS + // Resume coroutines waiting for the current simulation time + void resume(); + // Returns the simulation time of the next time slot (aborts if there are no delayed + // coroutines) + uint64_t nextTimeSlot(); + // Are there no delayed coroutines awaiting? + bool empty() { return m_queue.empty(); } + // Are there coroutines to resume at the current simulation time? + bool awaitingCurrentTime() { + return !empty() && m_queue.front().m_timestep <= m_context.time(); + } +#ifdef VL_DEBUG + void dump(); +#endif + // Used by coroutines for co_awaiting a certain simulation time + auto delay(uint64_t delay, const char* filename, int linenum) { + struct Awaitable { + VlDelayedCoroutineQueue& queue; + uint64_t delay; +#ifdef VL_DEBUG + const char* filename; + int linenum; +#endif + bool await_ready() { return false; } // Always suspend + void await_suspend(std::coroutine_handle<> coro) { +#ifdef VL_DEBUG + queue.push_back({delay, VlCoroutineHandle{coro, filename, linenum}}); +#else + queue.push_back({delay, coro}); +#endif + // Move last element to the proper place in the max-heap + std::push_heap(queue.begin(), queue.end()); + } + void await_resume() {} + }; +#ifdef VL_DEBUG + return Awaitable{m_queue, m_context.time() + delay, filename, linenum}; +#else + return Awaitable{m_queue, m_context.time() + delay}; +#endif + } +}; + +//============================================================================= +// VlTriggerScheduler stores coroutines to be resumed by a trigger. It does not keep track of its +// trigger, relying on calling code to resume when appropriate. Coroutines are kept in two stages +// - 'uncommitted' and 'ready'. Whenever a coroutine is suspended, it lands in the 'uncommited' +// stage. Only when commit() is called, these coroutines get moved to the 'ready' stage. That's +// when they can be resumed. This is done to avoid resuming processes before they start waiting. + +class VlTriggerScheduler final { + // TYPES + using VlCoroutineVec = std::vector; + + // MEMBERS + VlCoroutineVec m_uncommitted; // Coroutines suspended before commit() was called + // (not resumable) + VlCoroutineVec m_ready; // Coroutines that can be resumed (all coros from m_uncommitted are + // moved here in commit()) + +public: + // METHODS + // Resumes all coroutines from the 'ready' stage + void resume(const char* eventDescription); + // Moves all coroutines from m_uncommitted to m_ready + void commit(const char* eventDescription); + // Are there no coroutines awaiting? + bool empty() { return m_ready.empty() && m_uncommitted.empty(); } +#ifdef VL_DEBUG + void dump(const char* eventDescription); +#endif + // Used by coroutines for co_awaiting a certain trigger + auto trigger(const char* eventDescription, const char* filename, int linenum) { + VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n", + eventDescription, filename, linenum);); + struct Awaitable { + VlCoroutineVec& suspended; // Coros waiting on trigger +#ifdef VL_DEBUG + const char* filename; + int linenum; +#endif + bool await_ready() { return false; } // Always suspend + void await_suspend(std::coroutine_handle<> coro) { +#ifdef VL_DEBUG + suspended.emplace_back(coro, filename, linenum); +#else + suspended.emplace_back(coro); +#endif + } + void await_resume() {} + }; +#ifdef VL_DEBUG + return Awaitable{m_uncommitted, filename, linenum}; +#else + return Awaitable{m_uncommitted}; +#endif + } +}; + +//============================================================================= +// VlNow is a helper awaitable type that always suspends, and then immediately resumes a coroutine. +// Allows forcing the move of coroutine locals to the heap. + +struct VlNow { + bool await_ready() { return false; } // Always suspend + bool await_suspend(std::coroutine_handle<>) { return false; } // Resume immediately + void await_resume() {} +}; + +//============================================================================= +// VlForever is a helper awaitable type for suspending coroutines forever. Used for constant +// wait statements. + +struct VlForever { + bool await_ready() { return false; } // Always suspend + void await_suspend(std::coroutine_handle<> coro) { coro.destroy(); } + void await_resume() {} +}; + +//============================================================================= +// VlForkSync is used to manage fork..join and fork..join_any constructs. + +class VlForkSync final { + // VlJoin stores the handle of a suspended coroutine that did a fork..join or fork..join_any. + // If the counter reaches 0, the suspended coroutine shall be resumed. + struct VlJoin { + size_t m_counter = 0; // When reaches 0, resume suspended coroutine + VlCoroutineHandle m_susp; // Coroutine to resume + }; + + // The join info is shared among all forked processes + std::shared_ptr m_join; + +public: + // Create the join object and set the counter to the specified number + void init(size_t count) { m_join.reset(new VlJoin{count, {}}); } + // Called whenever any of the forked processes finishes. If the join counter reaches 0, the + // main process gets resumed + void done(const char* filename, int linenum); + // Used by coroutines for co_awaiting a join + auto join(const char* filename, int linenum) { + assert(m_join); + VL_DEBUG_IF( + VL_DBG_MSGF(" Awaiting join of fork at: %s:%d", filename, linenum);); + struct Awaitable { + const std::shared_ptr join; // Join to await on + bool await_ready() { return join->m_counter == 0; } // Suspend if join still exists + void await_suspend(std::coroutine_handle<> coro) { join->m_susp = coro; } + void await_resume() {} + }; + return Awaitable{m_join}; + } +}; + +//============================================================================= +// VlCoroutine +// Return value of a coroutine. Used for chaining coroutine suspension/resumption. + +class VlCoroutine final { +private: + // TYPES + struct VlPromise { + std::coroutine_handle<> m_continuation; // Coroutine to resume after this one finishes + VlCoroutine* m_corop = nullptr; // Pointer to the coroutine return object + + ~VlPromise(); + + VlCoroutine get_return_object() { return {this}; } + + // Never suspend at the start of the coroutine + std::suspend_never initial_suspend() { return {}; } + + // Never suspend at the end of the coroutine (thanks to this, the coroutine will clean up + // after itself) + std::suspend_never final_suspend() noexcept; + + void unhandled_exception() { std::abort(); } + void return_void() const {} + }; + + // MEMBERS + VlPromise* m_promisep; // The promise created for this coroutine + +public: + // TYPES + using promise_type = VlPromise; // promise_type has to be public + + // CONSTRUCTORS + // Construct + VlCoroutine(VlPromise* p) + : m_promisep{p} { + m_promisep->m_corop = this; + } + // Move. Update the pointers each time the return object is moved + VlCoroutine(VlCoroutine&& other) + : m_promisep{std::exchange(other.m_promisep, nullptr)} { + if (m_promisep) m_promisep->m_corop = this; + } + ~VlCoroutine() { + // Indicate to the promise that the return object is gone + if (m_promisep) m_promisep->m_corop = nullptr; + } + + // METHODS + // Suspend the awaiter if the coroutine is suspended (the promise exists) + bool await_ready() const noexcept { return !m_promisep; } + // Set the awaiting coroutine as the continuation of the current coroutine + void await_suspend(std::coroutine_handle<> coro) { m_promisep->m_continuation = coro; } + void await_resume() const noexcept {} +}; + +#endif // Guard diff --git a/include/verilated_types.h b/include/verilated_types.h index 76126e3a1..6a9b7d074 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -133,6 +133,10 @@ public: void clearTriggered() { m_triggered = false; } }; +inline std::string VL_TO_STRING(const VlEvent& e) { + return std::string("triggered=") + (e.isTriggered() ? "true" : "false"); +} + //=================================================================== // Shuffle RNG diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 97acb8090..4d4f12ef1 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -181,6 +181,7 @@ RAW_OBJS = \ V3DepthBlock.o \ V3Descope.o \ V3DupFinder.o \ + V3Timing.o \ V3EmitCBase.o \ V3EmitCConstPool.o \ V3EmitCFunc.o \ @@ -241,6 +242,7 @@ RAW_OBJS = \ V3SchedAcyclic.o \ V3SchedPartition.o \ V3SchedReplicate.o \ + V3SchedTiming.o \ V3Scope.o \ V3Scoreboard.o \ V3Slice.o \ diff --git a/src/V3Active.cpp b/src/V3Active.cpp index f9defceb2..4cedf73e2 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -373,9 +373,11 @@ private: } // Convert to blocking assignment - nodep->replaceWith(new AstAssign{nodep->fileline(), // - nodep->lhsp()->unlinkFrBack(), // - nodep->rhsp()->unlinkFrBack()}); + nodep->replaceWith(new AstAssign{ + nodep->fileline(), // + nodep->lhsp()->unlinkFrBack(), // + nodep->rhsp()->unlinkFrBack(), // + nodep->timingControlp() ? nodep->timingControlp()->unlinkFrBack() : nullptr}); VL_DO_DANGLING(nodep->deleteTree(), nodep); } @@ -473,8 +475,9 @@ private: } AstActive* const wantactivep - = m_clockedProcess ? m_namer.getActive(nodep->fileline(), oldsensesp) - : m_namer.getSpecialActive(nodep->fileline()); + = !m_clockedProcess ? m_namer.getSpecialActive(nodep->fileline()) + : oldsensesp ? m_namer.getActive(nodep->fileline(), oldsensesp) + : m_namer.getSpecialActive(nodep->fileline()); // Delete sensitivity list if (oldsensesp) VL_DO_DANGLING(oldsensesp->deleteTree(), oldsensesp); @@ -495,6 +498,12 @@ private: } } + void visitSenItems(AstNode* nodep) { + if (v3Global.opt.timing().isSetTrue()) { + nodep->foreach([this](AstSenItem* senItemp) { visit(senItemp); }); + } + } + // VISITORS virtual void visit(AstScope* nodep) override { m_namer.main(nodep); // Clear last scope's names, and collect this scope's existing names @@ -509,6 +518,7 @@ private: } virtual void visit(AstInitial* nodep) override { const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL}; + visitSenItems(nodep); moveUnderSpecial(nodep); } virtual void visit(AstFinal* nodep) override { @@ -529,6 +539,7 @@ private: VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); return; } + visitSenItems(nodep); visitAlways(nodep, nodep->sensesp(), nodep->keyword()); } virtual void visit(AstAlwaysPostponed* nodep) override { @@ -540,16 +551,18 @@ private: virtual void visit(AstAlwaysPublic* nodep) override { visitAlways(nodep, nodep->sensesp(), VAlwaysKwd::ALWAYS); } + virtual void visit(AstCFunc* nodep) override { visitSenItems(nodep); } virtual void visit(AstSenItem* nodep) override { - UASSERT_OBJ(!m_walkingBody, nodep, "Should not reach here when walking body"); + UASSERT_OBJ(!m_walkingBody, nodep, + "Should not reach here when walking body without --timing"); if (!nodep->sensp()) return; // Ignore sequential items (e.g.: initial, comb, etc.) m_clockedProcess = true; if (nodep->edgeType() != VEdgeType::ET_CHANGED) m_allChanged = false; - if (nodep->varrefp()) { - if (const AstBasicDType* const basicp = nodep->varrefp()->dtypep()->basicp()) { + if (const auto* const dtypep = nodep->sensp()->dtypep()) { + if (const auto* const basicp = dtypep->basicp()) { if (basicp->isEvent()) nodep->edgeType(VEdgeType::ET_EVENT); } } @@ -573,6 +586,7 @@ private: } virtual void visit(AstAssignDly* nodep) override { m_canBeComb = false; + if (nodep->isTimingControl()) m_clockedProcess = true; iterateChildrenConst(nodep); } virtual void visit(AstFireEvent* nodep) override { @@ -587,13 +601,27 @@ private: m_canBeComb = false; iterateChildrenConst(nodep); } + virtual void visit(AstFork* nodep) override { + if (nodep->isTimingControl()) { + m_canBeComb = false; + m_clockedProcess = true; + } + // Do not iterate children, technically not part of this process + } //-------------------- virtual void visit(AstVar*) override {} // Accelerate virtual void visit(AstVarScope*) override {} // Accelerate virtual void visit(AstNode* nodep) override { - if (m_walkingBody && !m_canBeComb) return; // Accelerate + if (!v3Global.opt.timing().isSetTrue() && m_walkingBody && !m_canBeComb) { + return; // Accelerate + } if (!nodep->isPure()) m_canBeComb = false; + if (nodep->isTimingControl()) { + m_canBeComb = false; + m_clockedProcess = true; + return; + } iterateChildren(nodep); } diff --git a/src/V3Ast.h b/src/V3Ast.h index ed8b8bc93..6964cdce6 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -450,6 +450,9 @@ public: CHARPTR, MTASKSTATE, TRIGGERVEC, + DELAY_SCHEDULER, + TRIGGER_SCHEDULER, + FORK_SYNC, // Unsigned and two state; fundamental types UINT32, UINT64, @@ -460,21 +463,57 @@ public: }; enum en m_e; const char* ascii() const { - static const char* const names[] - = {"%E-unk", "bit", "byte", "chandle", "event", - "int", "integer", "logic", "longint", "real", - "shortint", "time", "string", "VerilatedScope*", "char*", - "VlMTaskState", "VlTriggerVec", "IData", "QData", "LOGIC_IMPLICIT", - " MAX"}; + static const char* const names[] = {"%E-unk", + "bit", + "byte", + "chandle", + "event", + "int", + "integer", + "logic", + "longint", + "real", + "shortint", + "time", + "string", + "VerilatedScope*", + "char*", + "VlMTaskState", + "VlTriggerVec", + "VlDelayScheduler", + "VlTriggerScheduler", + "VlFork", + "IData", + "QData", + "LOGIC_IMPLICIT", + " MAX"}; return names[m_e]; } const char* dpiType() const { - static const char* const names[] - = {"%E-unk", "svBit", "char", "void*", "char", - "int", "%E-integer", "svLogic", "long long", "double", - "short", "%E-time", "const char*", "dpiScope", "const char*", - "%E-mtaskstate", "%E-triggervec", "IData", "QData", "%E-logic-implct", - " MAX"}; + static const char* const names[] = {"%E-unk", + "svBit", + "char", + "void*", + "char", + "int", + "%E-integer", + "svLogic", + "long long", + "double", + "short", + "%E-time", + "const char*", + "dpiScope", + "const char*", + "%E-mtaskstate", + "%E-triggervec", + "%E-dly-sched", + "%E-trig-sched", + "%E-fork", + "IData", + "QData", + "%E-logic-implct", + " MAX"}; return names[m_e]; } static void selfTest() { @@ -508,6 +547,9 @@ public: case CHARPTR: return 0; // opaque case MTASKSTATE: return 0; // opaque case TRIGGERVEC: return 0; // opaque + case DELAY_SCHEDULER: return 0; // opaque + case TRIGGER_SCHEDULER: return 0; // opaque + case FORK_SYNC: return 0; // opaque case UINT32: return 32; case UINT64: return 64; default: return 0; @@ -544,7 +586,8 @@ public: } bool isOpaque() const { // IE not a simple number we can bit optimize return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR - || m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DOUBLE); + || m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DELAY_SCHEDULER + || m_e == TRIGGER_SCHEDULER || m_e == FORK_SYNC || m_e == DOUBLE); } bool isDouble() const { return m_e == DOUBLE; } bool isEvent() const { return m_e == EVENT; } @@ -1825,17 +1868,19 @@ public: // Changes control flow, disable some optimizations virtual bool isBrancher() const { return false; } // Else a AstTime etc that can't be pushed out - virtual bool isGateOptimizable() const { return true; } + virtual bool isGateOptimizable() const { return !isTimingControl(); } // GateDedupable is a slightly larger superset of GateOptimzable (eg, AstNodeIf) virtual bool isGateDedupable() const { return isGateOptimizable(); } // Else creates output or exits, etc, not unconsumed virtual bool isOutputter() const { return false; } // Else a AstTime etc which output can't be predicted from input - virtual bool isPredictOptimizable() const { return true; } + virtual bool isPredictOptimizable() const { return !isTimingControl(); } // Else a $display, etc, that must be ordered with other displays virtual bool isPure() const { return true; } // Else a AstTime etc that can't be substituted out virtual bool isSubstOptimizable() const { return true; } + // An event control, delay, wait, etc. + virtual bool isTimingControl() const { return false; } // isUnlikely handles $stop or similar statement which means an above IF // statement is unlikely to be taken virtual bool isUnlikely() const { return false; } @@ -2628,6 +2673,8 @@ public: class AstNodeProcedure VL_NOT_FINAL : public AstNode { // IEEE procedure: initial, final, always + bool m_suspendable = false; // Is suspendable by a Delay, EventControl, etc. + protected: AstNodeProcedure(VNType t, FileLine* fl, AstNode* bodysp) : AstNode{t, fl} { @@ -2641,6 +2688,8 @@ public: AstNode* bodysp() const { return op2p(); } // op2 = Statements to evaluate void addStmtp(AstNode* nodep) { addOp2p(nodep); } bool isJustOneBodyStmt() const { return bodysp() && !bodysp()->nextp(); } + bool isSuspendable() const { return m_suspendable; } + void setSuspendable() { m_suspendable = true; } }; class AstNodeStmt VL_NOT_FINAL : public AstNode { @@ -2670,7 +2719,7 @@ protected: : AstNodeStmt{t, fl} { setOp1p(rhsp); setOp2p(lhsp); - addNOp3p(timingControlp); + setNOp3p(timingControlp); dtypeFrom(lhsp); } @@ -2683,7 +2732,7 @@ public: AstNode* lhsp() const { return op2p(); } // op2 = Assign to // op3 = Timing controls (delays, event controls) AstNode* timingControlp() const { return op3p(); } - void addTimingControlp(AstNode* const np) { addNOp3p(np); } + void timingControlp(AstNode* const np) { setNOp3p(np); } void rhsp(AstNode* np) { setOp1p(np); } void lhsp(AstNode* np) { setOp2p(np); } virtual bool hasDType() const override { return true; } @@ -2691,6 +2740,7 @@ public: virtual int instrCount() const override { return widthInstrs(); } virtual bool same(const AstNode*) const override { return true; } virtual string verilogKwd() const override { return "="; } + virtual bool isTimingControl() const override { return timingControlp(); } virtual bool brokeLhsMustBeLvalue() const = 0; }; diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index d60fee66d..5b35b2dcf 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -711,6 +711,12 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const { info.m_type = "VlMTaskVertex"; } else if (bdtypep->isTriggerVec()) { info.m_type = "VlTriggerVec<" + cvtToStr(dtypep->width()) + ">"; + } else if (bdtypep->isDelayScheduler()) { + info.m_type = "VlDelayScheduler"; + } else if (bdtypep->isTriggerScheduler()) { + info.m_type = "VlTriggerScheduler"; + } else if (bdtypep->isForkSync()) { + info.m_type = "VlForkSync"; } else if (bdtypep->isEvent()) { info.m_type = "VlEvent"; } else if (dtypep->widthMin() <= 8) { // Handle unpacked arrays; not bdtypep->width @@ -1296,7 +1302,10 @@ void AstNode::dump(std::ostream& str) const { } } -void AstNodeProcedure::dump(std::ostream& str) const { this->AstNode::dump(str); } +void AstNodeProcedure::dump(std::ostream& str) const { + this->AstNode::dump(str); + if (isSuspendable()) str << " [SUSP]"; +} void AstAlways::dump(std::ostream& str) const { this->AstNodeProcedure::dump(str); @@ -1954,8 +1963,34 @@ void AstCFunc::dump(std::ostream& str) const { if (isConstructor()) str << " [CTOR]"; if (isDestructor()) str << " [DTOR]"; if (isVirtual()) str << " [VIRT]"; + if (isCoroutine()) str << " [CORO]"; +} +void AstCAwait::dump(std::ostream& str) const { + this->AstNodeStmt::dump(str); + if (sensesp()) { + str << " => "; + sensesp()->dump(str); + } } void AstCUse::dump(std::ostream& str) const { this->AstNode::dump(str); str << " [" << useType() << "]"; } + +AstAlways* AstAssignW::convertToAlways() { + AstNode* const lhs1p = lhsp()->unlinkFrBack(); + AstNode* const rhs1p = rhsp()->unlinkFrBack(); + AstNode* const controlp = timingControlp() ? timingControlp()->unlinkFrBack() : nullptr; + FileLine* const flp = fileline(); + AstNode* bodysp = new AstAssign{flp, lhs1p, rhs1p, controlp}; + if (controlp) { + // If there's a timing control, put the assignment in a fork..join_none. This process won't + // get marked as suspendable and thus will be scheduled normally + auto* forkp = new AstFork{flp, "", bodysp}; + forkp->joinType(VJoinType::JOIN_NONE); + bodysp = forkp; + } + AstAlways* const newp = new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, bodysp}; + replaceWith(newp); // User expected to then deleteTree(); + return newp; +} diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index 0316ce921..c8edf7702 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -1004,6 +1004,9 @@ public: bool isDouble() const { return keyword().isDouble(); } bool isEvent() const { return keyword() == VBasicDTypeKwd::EVENT; } bool isTriggerVec() const { return keyword() == VBasicDTypeKwd::TRIGGERVEC; } + bool isForkSync() const { return keyword() == VBasicDTypeKwd::FORK_SYNC; } + bool isDelayScheduler() const { return keyword() == VBasicDTypeKwd::DELAY_SCHEDULER; } + bool isTriggerScheduler() const { return keyword() == VBasicDTypeKwd::TRIGGER_SCHEDULER; } bool isOpaque() const { return keyword().isOpaque(); } bool isString() const { return keyword().isString(); } bool isZeroInit() const { return keyword().isZeroInit(); } @@ -1930,6 +1933,7 @@ public: return op1p(); } // op1 = Extracting what (nullptr=TBD during parsing) AstNode* lsbp() const { return op2p(); } // op2 = Msb selection expression + void lsbp(AstNode* const lsbp) { setOp2p(lsbp); } AstNode* widthp() const { return op3p(); } // op3 = Width int widthConst() const { return VN_AS(widthp(), Const)->toSInt(); } int lsbConst() const { return VN_AS(lsbp(), Const)->toSInt(); } @@ -2108,6 +2112,7 @@ private: bool m_isLatched : 1; // Not assigned in all control paths of combo always bool m_isForceable : 1; // May be forced/released externally from user C code bool m_isWrittenByDpi : 1; // This variable can be written by a DPI Export + bool m_isWrittenBySuspendable : 1; // This variable can be written by a suspendable process void init() { m_ansi = false; @@ -2148,6 +2153,7 @@ private: m_isLatched = false; m_isForceable = false; m_isWrittenByDpi = false; + m_isWrittenBySuspendable = false; m_attrClocker = VVarAttrClocker::CLOCKER_UNKNOWN; } @@ -2315,6 +2321,8 @@ public: void setForceable() { m_isForceable = true; } bool isWrittenByDpi() const { return m_isWrittenByDpi; } void setWrittenByDpi() { m_isWrittenByDpi = true; } + bool isWrittenBySuspendable() const { return m_isWrittenBySuspendable; } + void setWrittenBySuspendable() { m_isWrittenBySuspendable = true; } // METHODS virtual void name(const string& name) override { m_name = name; } virtual void tag(const string& text) override { m_tag = text; } @@ -3468,6 +3476,7 @@ public: } // op1 = Expression sensitized, if any AstNode* sensp() const { return op1p(); } + void sensp(AstNode* const nodep) { setOp1p(nodep); } AstNodeVarRef* varrefp() const { return VN_CAST(op1p(), NodeVarRef); } // bool isClocked() const { return edgeType().clockedStmt(); } @@ -3604,7 +3613,8 @@ public: } ASTNODE_NODE_FUNCS(Assign) virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { - return new AstAssign(this->fileline(), lhsp, rhsp); + AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; + return new AstAssign(this->fileline(), lhsp, rhsp, controlp); } virtual bool brokeLhsMustBeLvalue() const override { return true; } }; @@ -3628,7 +3638,8 @@ public: : ASTGEN_SUPER_AssignDly(fl, lhsp, rhsp, timingControlp) {} ASTNODE_NODE_FUNCS(AssignDly) virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { - return new AstAssignDly(this->fileline(), lhsp, rhsp); + AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; + return new AstAssignDly(this->fileline(), lhsp, rhsp, controlp); } virtual bool isGateOptimizable() const override { return false; } virtual string verilogKwd() const override { return "<="; } @@ -3638,21 +3649,15 @@ public: class AstAssignW final : public AstNodeAssign { // Like assign, but wire/assign's in verilog, the only setting of the specified variable public: - AstAssignW(FileLine* fl, AstNode* lhsp, AstNode* rhsp) - : ASTGEN_SUPER_AssignW(fl, lhsp, rhsp) {} + AstAssignW(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* timingControlp = nullptr) + : ASTGEN_SUPER_AssignW(fl, lhsp, rhsp, timingControlp) {} ASTNODE_NODE_FUNCS(AssignW) virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { - return new AstAssignW(this->fileline(), lhsp, rhsp); + AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; + return new AstAssignW(this->fileline(), lhsp, rhsp, controlp); } virtual bool brokeLhsMustBeLvalue() const override { return true; } - AstAlways* convertToAlways() { - AstNode* const lhs1p = lhsp()->unlinkFrBack(); - AstNode* const rhs1p = rhsp()->unlinkFrBack(); - AstAlways* const newp = new AstAlways(fileline(), VAlwaysKwd::ALWAYS, nullptr, - new AstAssign(fileline(), lhs1p, rhs1p)); - replaceWith(newp); // User expected to then deleteTree(); - return newp; - } + AstAlways* convertToAlways(); }; class AstAssignVarScope final : public AstNodeAssign { @@ -3722,7 +3727,7 @@ public: } ASTNODE_NODE_FUNCS(FireEvent); AstNode* operandp() const { return op1p(); } - bool isDeleyed() const { return m_delayed; } + bool isDelayed() const { return m_delayed; } }; class AstAssignPre final : public AstNodeAssign { @@ -3943,6 +3948,7 @@ public: setNOp2p(stmtsp); } ASTNODE_NODE_FUNCS(Delay) + virtual bool isTimingControl() const override { return true; } virtual bool same(const AstNode* /*samep*/) const override { return true; } // AstNode* lhsp() const { return op1p(); } // op1 = delay value @@ -4735,8 +4741,10 @@ public: addNOp3p(bodysp); } ASTNODE_NODE_FUNCS(Wait) - AstNode* bodysp() const { return op3p(); } // op3 = body of loop + AstNode* condp() const { return op2p(); } // op2 = condition + AstNode* bodysp() const { return op3p(); } // op3 = statements after wait bool isFirstInMyListOfStatements(AstNode* n) const override { return n == bodysp(); } + virtual bool isTimingControl() const override { return true; } }; class AstWhile final : public AstNodeStmt { @@ -5110,6 +5118,7 @@ public: AstFork(FileLine* fl, const string& name, AstNode* stmtsp) : ASTGEN_SUPER_Fork(fl, name, stmtsp) {} ASTNODE_NODE_FUNCS(Fork) + virtual bool isTimingControl() const override { return !joinType().joinNone(); } virtual void dump(std::ostream& str) const override; VJoinType joinType() const { return m_joinType; } void joinType(const VJoinType& flag) { m_joinType = flag; } @@ -5391,21 +5400,20 @@ public: class AstEventControl final : public AstNodeStmt { // Parents: stmtlist + // Children: sentree, stmtlist public: AstEventControl(FileLine* fl, AstSenTree* sensesp, AstNode* stmtsp) : ASTGEN_SUPER_EventControl(fl) { setNOp1p(sensesp); - setNOp2p(stmtsp); + addNOp2p(stmtsp); } ASTNODE_NODE_FUNCS(EventControl) virtual string verilogKwd() const override { return "@(%l) %r"; } - virtual bool isGateOptimizable() const override { return false; } - virtual bool isPredictOptimizable() const override { return false; } - virtual bool isPure() const override { return false; } - virtual bool isOutputter() const override { return false; } + virtual bool isTimingControl() const override { return true; } virtual int instrCount() const override { return 0; } AstSenTree* sensesp() const { return VN_AS(op1p(), SenTree); } AstNode* stmtsp() const { return op2p(); } + void stmtsp(AstNode* stmtsp) { setNOp2p(stmtsp); } }; class AstTimeFormat final : public AstNodeStmt { @@ -9109,6 +9117,7 @@ public: AstScope* scopep() const { return m_scopep; } void scopep(AstScope* nodep) { m_scopep = nodep; } string rtnTypeVoid() const { return ((m_rtnType == "") ? "void" : m_rtnType); } + void rtnType(const string& rtnType) { m_rtnType = rtnType; } bool dontCombine() const { return m_dontCombine || isTrace() || entryPoint(); } void dontCombine(bool flag) { m_dontCombine = flag; } bool dontInline() const { return dontCombine() || slow() || funcPublic(); } @@ -9153,6 +9162,7 @@ public: void dpiImportWrapper(bool flag) { m_dpiImportWrapper = flag; } void dpiTraceInit(bool flag) { m_dpiTraceInit = flag; } bool dpiTraceInit() const { return m_dpiTraceInit; } + bool isCoroutine() const { return m_rtnType == "VlCoroutine"; } // // If adding node accessors, see below emptyBody AstNode* argsp() const { return op1p(); } @@ -9321,6 +9331,32 @@ public: VUseType useType() const { return m_useType; } }; +class AstCAwait final : public AstNodeStmt { + // Emit C++'s co_await statement + // Children: expression + AstSenTree* m_sensesp; // Sentree related to this await +public: + AstCAwait(FileLine* fl, AstNode* exprsp, AstSenTree* sensesp = nullptr) + : ASTGEN_SUPER_CAwait(fl) + , m_sensesp{sensesp} { + setNOp1p(exprsp); + } + ASTNODE_NODE_FUNCS(CAwait) + virtual bool isTimingControl() const override { return true; } + virtual const char* broken() const override { + BROKEN_RTN(m_sensesp && !m_sensesp->brokeExists()); + return nullptr; + } + virtual void cloneRelink() override { + if (m_sensesp && m_sensesp->clonep()) m_sensesp = m_sensesp->clonep(); + } + virtual void dump(std::ostream& str) const override; + AstNode* exprp() const { return op1p(); } // op1 = awaited expression + void exprp(AstNode* const nodep) { setNOp1p(nodep); } + AstSenTree* sensesp() const { return m_sensesp; } + void clearSensesp() { m_sensesp = nullptr; } +}; + class AstMTaskBody final : public AstNode { // Hold statements for each MTask private: @@ -9475,6 +9511,7 @@ private: AstCFunc* m_evalp = nullptr; // The '_eval' function AstCFunc* m_evalNbap = nullptr; // The '_eval__nba' function AstVarScope* m_dpiExportTriggerp = nullptr; // The DPI export trigger variable + AstVar* m_delaySchedulerp = nullptr; // The delay scheduler variable AstTopScope* m_topScopep = nullptr; // The singleton AstTopScope under the top module VTimescale m_timeunit; // Global time unit VTimescale m_timeprecision; // Global time precision @@ -9492,6 +9529,7 @@ public: BROKEN_RTN(m_evalp && !m_evalp->brokeExists()); BROKEN_RTN(m_dpiExportTriggerp && !m_dpiExportTriggerp->brokeExists()); BROKEN_RTN(m_topScopep && !m_topScopep->brokeExists()); + BROKEN_RTN(m_delaySchedulerp && !m_delaySchedulerp->brokeExists()); return nullptr; } virtual void cloneRelink() override { V3ERROR_NA; } @@ -9528,6 +9566,8 @@ public: void evalNbap(AstCFunc* funcp) { m_evalNbap = funcp; } AstVarScope* dpiExportTriggerp() const { return m_dpiExportTriggerp; } void dpiExportTriggerp(AstVarScope* varScopep) { m_dpiExportTriggerp = varScopep; } + AstVar* delaySchedulerp() const { return m_delaySchedulerp; } + void delaySchedulerp(AstVar* const varScopep) { m_delaySchedulerp = varScopep; } AstTopScope* topScopep() const { return m_topScopep; } void createTopScope(AstScope* scopep) { UASSERT(scopep, "Must not be nullptr"); diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp index 0e4176739..d2e9e66d7 100644 --- a/src/V3Begin.cpp +++ b/src/V3Begin.cpp @@ -68,6 +68,7 @@ private: string m_namedScope; // Name of begin blocks above us string m_unnamedScope; // Name of begin blocks, including unnamed blocks int m_ifDepth = 0; // Current if depth + bool m_underFork = false; // True if the current statement is directly under a fork // METHODS VL_DEBUG_FUNC; // Declare debug() @@ -120,6 +121,12 @@ private: } // VISITORS + virtual void visit(AstFork* nodep) override { + VL_RESTORER(m_underFork); + m_underFork = true; + dotNames(nodep, "__FORK__"); + nodep->name(""); + } virtual void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); { @@ -169,11 +176,19 @@ private: VL_RESTORER(m_namedScope); VL_RESTORER(m_unnamedScope); { - dotNames(nodep, "__BEGIN__"); - + { + VL_RESTORER(m_underFork); + m_underFork = false; + dotNames(nodep, "__BEGIN__"); + } UASSERT_OBJ(!nodep->genforp(), nodep, "GENFORs should have been expanded earlier"); // Cleanup + if (m_underFork) { + // If we're under a fork, keep this begin to group its statements together + nodep->name(""); + return; + } AstNode* addsp = nullptr; if (AstNode* const stmtsp = nodep->stmtsp()) { stmtsp->unlinkFrBackWithNext(); @@ -251,6 +266,8 @@ private: } // VISITORS - LINT CHECK virtual void visit(AstIf* nodep) override { // not AstNodeIf; other types not covered + VL_RESTORER(m_underFork); + m_underFork = false; // Check IFDEPTH warning - could be in other transform files if desire VL_RESTORER(m_ifDepth); if (m_ifDepth == -1 || v3Global.opt.ifDepth() < 1) { // Turned off @@ -264,7 +281,11 @@ private: } iterateChildren(nodep); } - virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } + virtual void visit(AstNode* nodep) override { + VL_RESTORER(m_underFork); + m_underFork = false; + iterateChildren(nodep); + } public: // CONSTRUCTORS diff --git a/src/V3Cast.cpp b/src/V3Cast.cpp index baea49588..ad8c66572 100644 --- a/src/V3Cast.cpp +++ b/src/V3Cast.cpp @@ -159,7 +159,8 @@ private: const AstNode* const backp = nodep->backp(); if (nodep->access().isReadOnly() && !VN_IS(backp, CCast) && VN_IS(backp, NodeMath) && !VN_IS(backp, ArraySel) && !VN_IS(backp, RedXor) - && (nodep->varp()->basicp() && !nodep->varp()->basicp()->isTriggerVec()) + && (nodep->varp()->basicp() && !nodep->varp()->basicp()->isTriggerVec() + && !nodep->varp()->basicp()->isForkSync()) && backp->width() && castSize(nodep) != castSize(nodep->varp())) { // Cast vars to IData first, else below has upper bits wrongly set // CData x=3; out = (QData)(x<<30); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 52334947a..6458d1106 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2853,6 +2853,7 @@ private: // Zero elimination virtual void visit(AstNodeAssign* nodep) override { iterateChildren(nodep); + if (nodep->timingControlp()) m_hasJumpDelay = true; if (m_doNConst && replaceNodeAssign(nodep)) return; } virtual void visit(AstAssignAlias* nodep) override { @@ -3164,10 +3165,6 @@ private: //----- // Jump elimination - virtual void visit(AstDelay* nodep) override { - iterateChildren(nodep); - m_hasJumpDelay = true; - } virtual void visit(AstJumpGo* nodep) override { iterateChildren(nodep); // Jump to label where label immediately follows label is not useful @@ -3563,6 +3560,7 @@ private: << nodep->prettyTypeName() << " to constant."); } } else { + if (nodep->isTimingControl()) m_hasJumpDelay = true; // Calculate the width of this operation if (m_params && !nodep->width()) nodep = V3Width::widthParamsEdit(nodep); iterateChildren(nodep); diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index 8cc905958..e72d4ab21 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -280,6 +280,7 @@ private: } else { // Track like any other statement iterateAndNextNull(nodep->lhsp()); } + iterateNull(nodep->timingControlp()); } } diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index db5024cbf..6a85328c9 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -71,6 +71,8 @@ private: // AstVarScope::user1p() -> AstVarScope*. Points to temp var created. // AstVarScope::user2p() -> AstActive*. Points to activity block of signal // (valid when AstVarScope::user1p is valid) + // AstVarScope::user3p() -> AstAlwaysPost*. Post block for this variable used for + // AssignDlys in suspendable processes // AstVarScope::user4p() -> AstAlwaysPost*. Post block for this variable // AstVarScope::user5p() -> AstVarRef*. Last blocking or non-blocking reference // AstVar::user2() -> bool. Set true if already made warning @@ -92,6 +94,8 @@ private: AstActive* m_activep = nullptr; // Current activate const AstCFunc* m_cfuncp = nullptr; // Current public C Function AstAssignDly* m_nextDlyp = nullptr; // Next delayed assignment in a list of assignments + AstNodeProcedure* m_procp = nullptr; // Current process + std::set m_timingDomains; // Timing resume domains bool m_inDly = false; // True in delayed assignments bool m_inLoop = false; // True in for loops bool m_inInitial = false; // True in static initializers and initial blocks @@ -203,7 +207,7 @@ private: } } - AstNode* createDlyArray(AstAssignDly* nodep, AstNode* lhsp) { + AstNode* createDlyOnSet(AstAssignDly* nodep, AstNode* lhsp) { // Create delayed assignment // See top of this file for transformation // Return the new LHS for the assignment, Null = unlink @@ -211,17 +215,24 @@ private: AstNode* newlhsp = nullptr; // nullptr = unlink old assign const AstSel* bitselp = nullptr; AstArraySel* arrayselp = nullptr; + AstVarRef* varrefp = nullptr; if (VN_IS(lhsp, Sel)) { bitselp = VN_AS(lhsp, Sel); - arrayselp = VN_AS(bitselp->fromp(), ArraySel); - } else { + arrayselp = VN_CAST(bitselp->fromp(), ArraySel); + if (!arrayselp) varrefp = VN_AS(bitselp->fromp(), VarRef); + } else if (VN_IS(lhsp, ArraySel)) { arrayselp = VN_AS(lhsp, ArraySel); + } else { + varrefp = VN_AS(lhsp, VarRef); + } + if (arrayselp) { + UASSERT_OBJ(!VN_IS(arrayselp->dtypep()->skipRefp(), UnpackArrayDType), nodep, + "ArraySel with unpacked arrays should have been removed in V3Slice"); + UINFO(4, "AssignDlyArray: " << nodep << endl); + } else { + UASSERT_OBJ(varrefp, nodep, "No arraysel nor varref"); + UINFO(4, "AssignDlyOnSet: " << nodep << endl); } - UASSERT_OBJ(arrayselp, nodep, "No arraysel under bitsel?"); - UASSERT_OBJ(!VN_IS(arrayselp->dtypep()->skipRefp(), UnpackArrayDType), nodep, - "ArraySel with unpacked arrays should have been removed in V3Slice"); - UINFO(4, "AssignDlyArray: " << nodep << endl); - // //=== Dimensions: __Vdlyvdim__ std::deque dimvalp; // Assignment value for each dimension of assignment AstNode* dimselp = arrayselp; @@ -229,7 +240,7 @@ private: AstNode* const valp = VN_AS(dimselp, ArraySel)->bitp()->unlinkFrBack(); dimvalp.push_front(valp); } - AstVarRef* const varrefp = VN_AS(dimselp, VarRef); + if (dimselp) varrefp = VN_AS(dimselp, VarRef); UASSERT_OBJ(varrefp, nodep, "No var underneath arraysels"); UASSERT_OBJ(varrefp->varScopep(), varrefp, "Var didn't get varscoped in V3Scope.cpp"); varrefp->unlinkFrBack(); @@ -292,7 +303,7 @@ private: AstVarScope* setvscp; AstAssignPre* setinitp = nullptr; - if (nodep->user3p()) { + if (!m_procp->isSuspendable() && nodep->user3p()) { // Simplistic optimization. If the previous statement in same scope was also a =>, // then we told this nodep->user3 we can use its Vdlyvset rather than making a new one. // This is good for code like: @@ -303,9 +314,12 @@ private: const string setvarname = (string("__Vdlyvset__") + oldvarp->shortName() + "__v" + cvtToStr(modVecNum)); setvscp = createVarSc(varrefp->varScopep(), setvarname, 1, nullptr); - setinitp = new AstAssignPre(nodep->fileline(), - new AstVarRef(nodep->fileline(), setvscp, VAccess::WRITE), - new AstConst(nodep->fileline(), 0)); + if (!m_procp->isSuspendable()) { + // Suspendables reset __Vdlyvset__ in the AstAlwaysPost + setinitp = new AstAssignPre{ + nodep->fileline(), new AstVarRef{nodep->fileline(), setvscp, VAccess::WRITE}, + new AstConst{nodep->fileline(), 0}}; + } AstAssign* const setassignp = new AstAssign( nodep->fileline(), new AstVarRef(nodep->fileline(), setvscp, VAccess::WRITE), new AstConst(nodep->fileline(), AstConst::BitTrue())); @@ -331,19 +345,36 @@ private: // Build "IF (changeit) ... UINFO(9, " For " << setvscp << endl); UINFO(9, " & " << varrefp << endl); - AstAlwaysPost* finalp = VN_AS(varrefp->varScopep()->user4p(), AlwaysPost); - if (finalp) { - AstActive* const oldactivep = VN_AS(finalp->user2p(), Active); - checkActivePost(varrefp, oldactivep); - if (setinitp) oldactivep->addStmtsp(setinitp); - } else { // first time we've dealt with this memory - finalp = new AstAlwaysPost(nodep->fileline(), nullptr /*sens*/, nullptr /*body*/); - UINFO(9, " Created " << finalp << endl); - AstActive* const newactp = createActive(varrefp); - newactp->addStmtsp(finalp); - varrefp->varScopep()->user4p(finalp); - finalp->user2p(newactp); - if (setinitp) newactp->addStmtsp(setinitp); + AstAlwaysPost* finalp = nullptr; + if (m_procp->isSuspendable()) { + finalp = VN_AS(varrefp->varScopep()->user3p(), AlwaysPost); + if (!finalp) { + FileLine* const flp = nodep->fileline(); + finalp = new AstAlwaysPost{flp, nullptr, nullptr}; + UINFO(9, " Created " << finalp << endl); + if (!m_procp->user3p()) { + AstActive* const newactp = createActive(varrefp); + m_procp->user3p(newactp); + varrefp->varScopep()->user3p(finalp); + } + AstActive* const actp = VN_AS(m_procp->user3p(), Active); + actp->addStmtsp(finalp); + } + } else { + finalp = VN_AS(varrefp->varScopep()->user4p(), AlwaysPost); + if (finalp) { + AstActive* const oldactivep = VN_AS(finalp->user2p(), Active); + checkActivePost(varrefp, oldactivep); + if (setinitp) oldactivep->addStmtsp(setinitp); + } else { // first time we've dealt with this memory + finalp = new AstAlwaysPost{nodep->fileline(), nullptr /*sens*/, nullptr /*body*/}; + UINFO(9, " Created " << finalp << endl); + AstActive* const newactp = createActive(varrefp); + newactp->addStmtsp(finalp); + varrefp->varScopep()->user4p(finalp); + finalp->user2p(newactp); + if (setinitp) newactp->addStmtsp(setinitp); + } } AstIf* postLogicp; if (finalp->user3p() == setvscp) { @@ -361,6 +392,11 @@ private: finalp->user4p(postLogicp); // and the associated IF, as we may be able to reuse it } postLogicp->addIfsp(new AstAssign(nodep->fileline(), selectsp, valreadp)); + if (m_procp->isSuspendable()) { + FileLine* const flp = nodep->fileline(); + postLogicp->addIfsp(new AstAssign{flp, new AstVarRef{flp, setvscp, VAccess::WRITE}, + new AstConst{flp, 0}}); + } return newlhsp; } @@ -393,10 +429,38 @@ private: iterateChildren(nodep); } } + virtual void visit(AstNodeProcedure* nodep) override { + m_procp = nodep; + m_timingDomains.clear(); + iterateChildren(nodep); + m_procp = nullptr; + if (m_timingDomains.empty()) return; + if (auto* const actp = VN_AS(nodep->user3p(), Active)) { + // Merge all timing domains (and possibly the active's domain) to create a sentree for + // the post logic + // TODO: allow multiple sentrees per active, so we don't have to merge them and create + // a new trigger + auto* clockedDomain + = actp->sensesp()->hasClocked() ? actp->sensesp()->cloneTree(false) : nullptr; + for (auto* const domainp : m_timingDomains) { + if (!clockedDomain) { + clockedDomain = domainp->cloneTree(false); + } else { + clockedDomain->addSensesp(domainp->sensesp()->cloneTree(true)); + clockedDomain->multi(true); // Comment that it was made from multiple domains + } + } + // We cannot constify the sentree using V3Const as user1-5 is already taken up by + // V3Delayed + actp->sensesp(clockedDomain); + actp->sensesStorep(clockedDomain); + } + } + virtual void visit(AstCAwait* nodep) override { m_timingDomains.insert(nodep->sensesp()); } virtual void visit(AstFireEvent* nodep) override { UASSERT_OBJ(v3Global.hasEvents(), nodep, "Inconsistent"); FileLine* const flp = nodep->fileline(); - if (nodep->isDeleyed()) { + if (nodep->isDelayed()) { AstVarRef* const vrefp = VN_AS(nodep->operandp(), VarRef); vrefp->unlinkFrBack(); const string newvarname = (string("__Vdly__") + vrefp->varp()->shortName()); @@ -442,12 +506,14 @@ private: nodep->v3warn(E_UNSUPPORTED, "Unsupported: Delayed assignment inside public function/task"); } - if (VN_IS(nodep->lhsp(), ArraySel) - || (VN_IS(nodep->lhsp(), Sel) - && VN_IS(VN_AS(nodep->lhsp(), Sel)->fromp(), ArraySel))) { - AstNode* const lhsp = nodep->lhsp()->unlinkFrBack(); - AstNode* const newlhsp = createDlyArray(nodep, lhsp); - if (m_inLoop) { + UASSERT_OBJ(m_procp, nodep, "Delayed assignment not under process"); + const bool isArray = VN_IS(nodep->lhsp(), ArraySel) + || (VN_IS(nodep->lhsp(), Sel) + && VN_IS(VN_AS(nodep->lhsp(), Sel)->fromp(), ArraySel)); + if (m_procp->isSuspendable() || isArray) { + AstNode* const lhsp = nodep->lhsp(); + AstNode* const newlhsp = createDlyOnSet(nodep, lhsp); + if (m_inLoop && isArray) { nodep->v3warn(BLKLOOPINIT, "Unsupported: Delayed assignment to array inside for " "loops (non-delayed is ok - see docs)"); } @@ -456,11 +522,12 @@ private: nodep->v3warn(E_UNSUPPORTED, "Unsupported: event arrays"); } if (newlhsp) { + if (nodep->lhsp()) nodep->lhsp()->unlinkFrBack(); nodep->lhsp(newlhsp); } else { VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } - VL_DO_DANGLING(pushDeletep(lhsp), lhsp); + if (!lhsp->backp()) VL_DO_DANGLING(pushDeletep(lhsp), lhsp); } else { iterateChildren(nodep); } diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 7e0e0e8fe..ba64a897e 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -684,6 +684,12 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP } else if (basicp && basicp->keyword() == VBasicDTypeKwd::STRING) { // String's constructor deals with it return ""; + } else if (basicp && basicp->isForkSync()) { + return ""; + } else if (basicp && basicp->isDelayScheduler()) { + return ""; + } else if (basicp && basicp->isTriggerScheduler()) { + return ""; } else if (basicp) { const bool zeroit = (varp->attrFileDescr() // Zero so we don't do file IO if never $fopen diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index aba15163b..43d016c3f 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -413,6 +413,11 @@ public: puts(funcp->nameProtect()); emitCCallArgs(nodep, ""); } + virtual void visit(AstCAwait* nodep) override { + puts("co_await "); + iterate(nodep->exprp()); + if (nodep->isStatement()) puts(";\n"); + } virtual void visit(AstCNew* nodep) override { bool comma = false; puts("std::make_shared<" + prefixNameProtect(nodep->dtypep()) + ">("); diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index a32f261d0..474964b26 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -313,6 +313,7 @@ class EmitCHeader final : public EmitCConstInit { if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n"); if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n"); if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n"); + if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n"); emitAll(modp); diff --git a/src/V3EmitCImp.cpp b/src/V3EmitCImp.cpp index a90b7fbbc..4791d25e5 100644 --- a/src/V3EmitCImp.cpp +++ b/src/V3EmitCImp.cpp @@ -254,6 +254,10 @@ class EmitCImp final : EmitCFunc { puts("("); putsQuoted(varp->nameProtect()); puts(")\n"); + } else if (dtypep->isDelayScheduler()) { + puts(", "); + puts(varp->nameProtect()); + puts("{*symsp->_vm_contextp__}\n"); } } } diff --git a/src/V3EmitCMain.cpp b/src/V3EmitCMain.cpp index c717b7751..776186b2c 100644 --- a/src/V3EmitCMain.cpp +++ b/src/V3EmitCMain.cpp @@ -80,7 +80,12 @@ private: puts(/**/ "// Evaluate model\n"); puts(/**/ "topp->eval();\n"); puts(/**/ "// Advance time\n"); - puts(/**/ "contextp->timeInc(1);\n"); + if (v3Global.rootp()->delaySchedulerp()) { + puts("if (!topp->eventsPending()) break;\n"); + puts("contextp->time(topp->nextTimeSlot());\n"); + } else { + puts("contextp->timeInc(1);\n"); + } puts("}\n"); puts("\n"); diff --git a/src/V3EmitCMake.cpp b/src/V3EmitCMake.cpp index c053cc45a..48d0e628f 100644 --- a/src/V3EmitCMake.cpp +++ b/src/V3EmitCMake.cpp @@ -113,6 +113,8 @@ class CMakeEmitter final { cmake_set_raw(*of, name + "_SC", v3Global.opt.systemC() ? "1" : "0"); *of << "# Coverage output mode? 0/1 (from --coverage)\n"; cmake_set_raw(*of, name + "_COVERAGE", v3Global.opt.coverage() ? "1" : "0"); + *of << "# Timing mode? 0/1\n"; + cmake_set_raw(*of, name + "_TIMING", v3Global.usesTiming() ? "1" : "0"); *of << "# Threaded output mode? 0/1/N threads (from --threads)\n"; cmake_set_raw(*of, name + "_THREADS", cvtToStr(v3Global.opt.threads())); *of << "# VCD Tracing output mode? 0/1 (from --trace)\n"; @@ -169,6 +171,9 @@ class CMakeEmitter final { + ".cpp"); } } + if (v3Global.usesTiming()) { + global.emplace_back("${VERILATOR_ROOT}/include/verilated_timing.cpp"); + } if (v3Global.opt.threads()) { global.emplace_back("${VERILATOR_ROOT}/include/verilated_threads.cpp"); } diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 15919bfcb..443e4f3f8 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -114,6 +114,7 @@ class EmitCModel final : public EmitCFunc { } } } + if (optSystemC() && v3Global.usesTiming()) puts("sc_event trigger_eval;\n"); // Cells instantiated by the top level (for access to /* verilator public */) puts("\n// CELLS\n" @@ -162,7 +163,12 @@ class EmitCModel final : public EmitCFunc { if (!optSystemC()) { puts("/// Evaluate the model. Application must call when inputs change.\n"); } - puts("void eval() { eval_step(); " + callEvalEndStep + "}\n"); + if (optSystemC() && v3Global.usesTiming()) { + puts("void eval();\n"); + puts("void eval_sens();\n"); + } else { + puts("void eval() { eval_step(); " + callEvalEndStep + "}\n"); + } if (!optSystemC()) { puts("/// Evaluate when calling multiple units/models per time step.\n"); } @@ -184,6 +190,11 @@ class EmitCModel final : public EmitCFunc { ofp()->putsPrivate(false); // public: puts("void final();\n"); + puts("/// Are there scheduled events to handle?\n"); + puts("bool eventsPending();\n"); + puts("/// Returns time at next time slot. Aborts if !eventsPending()\n"); + puts("uint64_t nextTimeSlot();\n"); + if (v3Global.opt.trace()) { puts("/// Trace signals in the model; called by application code\n"); puts("void trace(" + v3Global.opt.traceClassBase() @@ -278,6 +289,7 @@ class EmitCModel final : public EmitCFunc { // Create sensitivity list for when to evaluate the model. putsDecoration("// Sensitivities on all clocks and combinational inputs\n"); puts("SC_METHOD(eval);\n"); + if (v3Global.usesTiming()) puts("SC_METHOD(eval_sens);\n"); for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) { if (const AstVar* const varp = VN_CAST(nodep, Var)) { if (varp->isNonOutput() && (varp->isScSensitive() || varp->isUsedClock())) { @@ -343,6 +355,24 @@ class EmitCModel final : public EmitCFunc { puts("void " + topModNameProtected + "__" + protect("_eval_settle") + selfDecl + ";\n"); puts("void " + topModNameProtected + "__" + protect("_eval") + selfDecl + ";\n"); + if (optSystemC() && v3Global.usesTiming()) { + // ::eval + puts("\nvoid " + topClassName() + "::eval() {\n"); + puts("eval_step();\n"); + puts("if (eventsPending()) {\n"); + puts("sc_time dt = sc_time::from_value(nextTimeSlot() - contextp()->time());\n"); + puts("next_trigger(dt, trigger_eval);\n"); + puts("} else {\n"); + puts("next_trigger(trigger_eval);\n"); + puts("}\n"); + puts("}\n"); + + // ::eval_sens + puts("\nvoid " + topClassName() + "::eval_sens() {\n"); + puts("trigger_eval.notify();\n"); + puts("}\n"); + } + // ::eval_step puts("\nvoid " + topClassName() + "::eval_step() {\n"); puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+++++TOP Evaluate " + topClassName() @@ -408,6 +438,24 @@ class EmitCModel final : public EmitCFunc { puts("}\n"); } + if (v3Global.usesTiming()) { + putSectionDelimiter("Events and timing"); + if (auto* const delaySchedp = v3Global.rootp()->delaySchedulerp()) { + puts("bool " + topClassName() + "::eventsPending() { return !vlSymsp->TOP."); + puts(delaySchedp->nameProtect()); + puts(".empty(); }\n\n"); + puts("uint64_t " + topClassName() + "::nextTimeSlot() { return vlSymsp->TOP."); + puts(delaySchedp->nameProtect()); + puts(".nextTimeSlot(); }\n"); + } else { + puts("bool " + topClassName() + "::eventsPending() { return false; }\n\n"); + puts("uint64_t " + topClassName() + "::nextTimeSlot() {\n"); + puts("VL_FATAL_MT(__FILE__, __LINE__, \"\", \"%Error: No delays in the " + "design\");\n"); + puts("return 0;\n}\n"); + } + } + putSectionDelimiter("Utilities"); if (!optSystemC()) { diff --git a/src/V3EmitMk.cpp b/src/V3EmitMk.cpp index e4db53ede..f16bb959a 100644 --- a/src/V3EmitMk.cpp +++ b/src/V3EmitMk.cpp @@ -50,6 +50,10 @@ public: of.puts("\n### Switches...\n"); of.puts("# C11 constructs required? 0/1 (always on now)\n"); of.puts("VM_C11 = 1\n"); + of.puts("# Timing enabled? 0/1\n"); + of.puts("VM_TIMING = "); + of.puts(v3Global.usesTiming() ? "1" : "0"); + of.puts("\n"); of.puts("# Coverage output mode? 0/1 (from --coverage)\n"); of.puts("VM_COVERAGE = "); of.puts(v3Global.opt.coverage() ? "1" : "0"); @@ -108,6 +112,7 @@ public: putMakeClassEntry(of, v3Global.opt.traceSourceLang() + ".cpp"); } } + if (v3Global.usesTiming()) putMakeClassEntry(of, "verilated_timing.cpp"); if (v3Global.opt.threads()) putMakeClassEntry(of, "verilated_threads.cpp"); if (v3Global.opt.usesProfiler()) { putMakeClassEntry(of, "verilated_profiler.cpp"); diff --git a/src/V3Error.h b/src/V3Error.h index ab7177d34..1283ed785 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -51,12 +51,15 @@ public: I_TRACING, // Tracing is on/off from /*verilator tracing_on/off*/ I_LINT, // All lint messages I_DEF_NETTYPE_WIRE, // `default_nettype is WIRE (false=NONE) + I_TIMING, // Enable timing from /*verilator timing_on/off*/ // Error codes: E_DETECTARRAY, // Error: Unsupported: Can't detect changes on arrayed variable E_ENCAPSULATED, // Error: local/protected violation E_PORTSHORT, // Error: Output port is connected to a constant, electrical short E_UNSUPPORTED, // Error: Unsupported (generally) E_TASKNSVAR, // Error: Task I/O not simple + E_NEEDTIMINGOPT, // Error: --timing/--no-timing option not specified + E_NOTIMING, // Timing control encountered with --no-timing // // Warning codes: EC_FIRST_WARN, // Just a code so the program knows where to start warnings @@ -83,6 +86,8 @@ public: DEFPARAM, // Style: Defparam DECLFILENAME, // Declaration doesn't match filename DEPRECATED, // Feature will be deprecated + RISEFALLDLY, // Unsupported: rise/fall/turn-off delays + MINTYPMAXDLY, // Unsupported: min/typ/max delay expressions ENDLABEL, // End lable name mismatch EOFNEWLINE, // End-of-file missing newline GENCLK, // Generated Clock. Historical, never issued. @@ -135,8 +140,10 @@ public: USERINFO, // Elaboration time $info USERWARN, // Elaboration time $warning VARHIDDEN, // Hiding variable + WAITCONST, // Wait condition is constant WIDTH, // Width mismatch WIDTHCONCAT, // Unsized numbers/parameters in concatenations + ZERODLY, // #0 delay _ENUM_MAX // ***Add new elements below also*** }; @@ -157,16 +164,16 @@ public: // Leading spaces indicate it can't be disabled. " MIN", " INFO", " FATAL", " FATALEXIT", " FATALSRC", " ERROR", " FIRST_NAMED", // Boolean - " I_CELLDEFINE", " I_COVERAGE", " I_TRACING", " I_LINT", " I_DEF_NETTYPE_WIRE", + " I_CELLDEFINE", " I_COVERAGE", " I_TRACING", " I_LINT", " I_DEF_NETTYPE_WIRE", " I_TIMING", // Errors - "DETECTARRAY", "ENCAPSULATED", "PORTSHORT", "UNSUPPORTED", "TASKNSVAR", + "DETECTARRAY", "ENCAPSULATED", "PORTSHORT", "UNSUPPORTED", "TASKNSVAR", "NEEDTIMINGOPT", "NOTIMING", // Warnings " EC_FIRST_WARN", "ALWCOMBORDER", "ASSIGNDLY", "ASSIGNIN", "BADSTDPRAGMA", "BLKANDNBLK", "BLKLOOPINIT", "BLKSEQ", "BSSPACE", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONTASSREG", - "DEFPARAM", "DECLFILENAME", "DEPRECATED", + "DEFPARAM", "DECLFILENAME", "DEPRECATED", "RISEFALLDLY", "MINTYPMAXDLY", "ENDLABEL", "EOFNEWLINE", "GENCLK", "HIERBLOCK", "IFDEPTH", "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPORTSTAR", "IMPURE", @@ -180,7 +187,7 @@ public: "UNDRIVEN", "UNOPT", "UNOPTFLAT", "UNOPTTHREADS", "UNPACKED", "UNSIGNED", "UNUSED", "USERERROR", "USERFATAL", "USERINFO", "USERWARN", - "VARHIDDEN", "WIDTH", "WIDTHCONCAT", + "VARHIDDEN", "WAITCONST", "WIDTH", "WIDTHCONCAT", "ZERODLY", " MAX" }; // clang-format on @@ -197,7 +204,8 @@ public: bool pretendError() const { return (m_e == ASSIGNIN || m_e == BADSTDPRAGMA || m_e == BLKANDNBLK || m_e == BLKLOOPINIT || m_e == CONTASSREG || m_e == IMPURE || m_e == PINNOTFOUND || m_e == PKGNODECL - || m_e == PROCASSWIRE); // Says IEEE + || m_e == PROCASSWIRE // Says IEEE + || m_e == ZERODLY); } // Warnings to mention manual bool mentionManual() const { diff --git a/src/V3FileLine.h b/src/V3FileLine.h index f16bc5bca..078b5cafc 100644 --- a/src/V3FileLine.h +++ b/src/V3FileLine.h @@ -222,6 +222,8 @@ public: void coverageOn(bool flag) { warnOn(V3ErrorCode::I_COVERAGE, flag); } bool tracingOn() const { return m_warnOn.test(V3ErrorCode::I_TRACING); } void tracingOn(bool flag) { warnOn(V3ErrorCode::I_TRACING, flag); } + bool timingOn() const { return m_warnOn.test(V3ErrorCode::I_TIMING); } + void timingOn(bool flag) { warnOn(V3ErrorCode::I_TIMING, flag); } // METHODS - Global // and match what GCC outputs diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp index 88a9332d4..578d00dfe 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -247,6 +247,8 @@ private: m_substTreep = nodep->rhsp(); if (!VN_IS(nodep->lhsp(), NodeVarRef)) { clearSimple("ASSIGN(non-VARREF)"); + } else if (nodep->isTimingControl()) { + clearSimple("Timing control"); } else { iterateChildren(nodep); } @@ -343,6 +345,13 @@ private: VDouble0 m_statAssignMerged; // Statistic tracking // METHODS + void checkTimingControl(AstNode* nodep) { + if (nodep->isTimingControl() && m_logicVertexp) { + m_logicVertexp->clearReducibleAndDedupable("TimingControl"); + m_logicVertexp->setConsumed("TimingControl"); + } + } + void iterateNewStmt(AstNode* nodep, const char* nonReducibleReason, const char* consumeReason) { if (m_scopep) { @@ -357,6 +366,7 @@ private: } if (consumeReason) m_logicVertexp->setConsumed(consumeReason); if (VN_IS(nodep, SenItem)) m_logicVertexp->setConsumed("senItem"); + checkTimingControl(nodep); iterateChildren(nodep); m_logicVertexp = nullptr; } @@ -555,6 +565,7 @@ private: virtual void visit(AstNode* nodep) override { iterateChildren(nodep); if (nodep->isOutputter() && m_logicVertexp) m_logicVertexp->setConsumed("outputter"); + checkTimingControl(nodep); } public: diff --git a/src/V3Global.h b/src/V3Global.h index a8a051aa4..3addcf4ec 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -106,6 +106,7 @@ class V3Global final { bool m_needTraceDumper = false; // Need __Vm_dumperp in symbols bool m_dpi = false; // Need __Dpi include files bool m_hasEvents = false; // Design uses SystemVerilog named events + bool m_usesTiming = false; // Design uses timing constructs bool m_hasForceableSignals = false; // Need to apply V3Force pass bool m_hasSCTextSections = false; // Has `systemc_* sections that need to be emitted bool m_useParallelBuild = false; // Use parallel build for model @@ -148,6 +149,8 @@ public: void dpi(bool flag) { m_dpi = flag; } bool hasEvents() const { return m_hasEvents; } void setHasEvents() { m_hasEvents = true; } + bool usesTiming() const { return m_usesTiming; } + void setUsesTiming() { m_usesTiming = true; } bool hasForceableSignals() const { return m_hasForceableSignals; } void setHasForceableSignals() { m_hasForceableSignals = true; } bool hasSCTextSections() const { return m_hasSCTextSections; } diff --git a/src/V3Hasher.cpp b/src/V3Hasher.cpp index 53a276c89..bd6417414 100644 --- a/src/V3Hasher.cpp +++ b/src/V3Hasher.cpp @@ -262,6 +262,11 @@ private: m_hash += nodep->name(); }); } + virtual void visit(AstCAwait* nodep) override { + m_hash += hashNodeAndIterate(nodep, HASH_DTYPE, HASH_CHILDREN, [=]() { // + iterateNull(nodep->sensesp()); + }); + } virtual void visit(AstCoverInc* nodep) override { m_hash += hashNodeAndIterate(nodep, false, HASH_CHILDREN, [=]() { // iterateNull(nodep->declp()); diff --git a/src/V3InstrCount.cpp b/src/V3InstrCount.cpp index d0ec37562..e07db1461 100644 --- a/src/V3InstrCount.cpp +++ b/src/V3InstrCount.cpp @@ -42,6 +42,7 @@ private: const AstNode* const m_startNodep; // Start node of count bool m_tracingCall = false; // Iterating into a CCall to a CFunc bool m_inCFunc = false; // Inside AstCFunc + bool m_ignoreRemaining = false; // Ignore remaining statements in the block const bool m_assertNoDups; // Check for duplicates const std::ostream* const m_osp; // Dump file @@ -81,7 +82,12 @@ public: uint32_t instrCount() const { return m_instrCount; } private: + void reset() { + m_instrCount = 0; + m_ignoreRemaining = false; + } uint32_t startVisitBase(AstNode* nodep) { + UASSERT_OBJ(!m_ignoreRemaining, nodep, "Should not reach here if ignoring"); if (m_assertNoDups && !m_inCFunc) { // Ensure we don't count the same node twice // @@ -110,7 +116,7 @@ private: void endVisitBase(uint32_t savedCount, AstNode* nodep) { UINFO(8, "cost " << std::setw(6) << std::left << m_instrCount << " " << nodep << endl); markCost(nodep); - m_instrCount += savedCount; + if (!m_ignoreRemaining) m_instrCount += savedCount; } void markCost(AstNode* nodep) { if (m_osp) nodep->user4(m_instrCount + 1); // Else don't mark to avoid writeback @@ -118,6 +124,7 @@ private: // VISITORS virtual void visit(AstNodeSel* nodep) override { + if (m_ignoreRemaining) return; // This covers both AstArraySel and AstWordSel // // If some vector is a bazillion dwords long, and we're selecting 1 @@ -129,6 +136,7 @@ private: iterateAndNextNull(nodep->bitp()); } virtual void visit(AstSel* nodep) override { + if (m_ignoreRemaining) return; // Similar to AstNodeSel above, a small select into a large vector // is not expensive. Count the cost of the AstSel itself (scales with // its width) and the cost of the lsbp() and widthp() nodes, but not @@ -144,6 +152,7 @@ private: nodep->v3fatalSrc("AstMemberSel unhandled"); } virtual void visit(AstConcat* nodep) override { + if (m_ignoreRemaining) return; // Nop. // // Ignore concat. The problem with counting concat is that when we @@ -164,22 +173,24 @@ private: markCost(nodep); } virtual void visit(AstNodeIf* nodep) override { + if (m_ignoreRemaining) return; const VisitBase vb{this, nodep}; iterateAndNextNull(nodep->condp()); const uint32_t savedCount = m_instrCount; UINFO(8, "ifsp:\n"); - m_instrCount = 0; + reset(); iterateAndNextNull(nodep->ifsp()); uint32_t ifCount = m_instrCount; if (nodep->branchPred().unlikely()) ifCount = 0; UINFO(8, "elsesp:\n"); - m_instrCount = 0; + reset(); iterateAndNextNull(nodep->elsesp()); uint32_t elseCount = m_instrCount; if (nodep->branchPred().likely()) elseCount = 0; + reset(); if (ifCount >= elseCount) { m_instrCount = savedCount + ifCount; if (nodep->elsesp()) nodep->elsesp()->user4(0); // Don't dump it @@ -189,6 +200,7 @@ private: } } virtual void visit(AstNodeCond* nodep) override { + if (m_ignoreRemaining) return; // Just like if/else above, the ternary operator only evaluates // one of the two expressions, so only count the max. const VisitBase vb{this, nodep}; @@ -196,15 +208,16 @@ private: const uint32_t savedCount = m_instrCount; UINFO(8, "?\n"); - m_instrCount = 0; + reset(); iterateAndNextNull(nodep->expr1p()); const uint32_t ifCount = m_instrCount; UINFO(8, ":\n"); - m_instrCount = 0; + reset(); iterateAndNextNull(nodep->expr2p()); const uint32_t elseCount = m_instrCount; + reset(); if (ifCount < elseCount) { m_instrCount = savedCount + elseCount; if (nodep->expr1p()) nodep->expr1p()->user4(0); // Don't dump it @@ -213,6 +226,25 @@ private: if (nodep->expr2p()) nodep->expr2p()->user4(0); // Don't dump it } } + virtual void visit(AstCAwait* nodep) override { + if (m_ignoreRemaining) return; + iterateChildren(nodep); + // Anything past a co_await is irrelevant + m_ignoreRemaining = true; + } + virtual void visit(AstFork* nodep) override { + if (m_ignoreRemaining) return; + const VisitBase vb{this, nodep}; + uint32_t totalCount = m_instrCount; + // Sum counts in each statement until the first await + for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + reset(); + iterate(stmtp); + totalCount += m_instrCount; + } + m_instrCount = totalCount; + m_ignoreRemaining = false; + } virtual void visit(AstActive* nodep) override { // You'd think that the OrderLogicVertex's would be disjoint trees // of stuff in the AST, but it isn't so: V3Order makes an @@ -230,6 +262,7 @@ private: UASSERT_OBJ(nodep == m_startNodep, nodep, "Multiple actives, or not start node"); } virtual void visit(AstNodeCCall* nodep) override { + if (m_ignoreRemaining) return; const VisitBase vb{this, nodep}; iterateChildren(nodep); m_tracingCall = true; @@ -241,6 +274,7 @@ private: // from the root UASSERT_OBJ(m_tracingCall || nodep == m_startNodep, nodep, "AstCFunc not under AstCCall, or not start node"); + UASSERT_OBJ(!m_ignoreRemaining, nodep, "Should not be ignoring at the start of a CFunc"); m_tracingCall = false; VL_RESTORER(m_inCFunc); { @@ -248,8 +282,10 @@ private: const VisitBase vb{this, nodep}; iterateChildren(nodep); } + m_ignoreRemaining = false; } virtual void visit(AstNode* nodep) override { + if (m_ignoreRemaining) return; const VisitBase vb{this, nodep}; iterateChildren(nodep); } diff --git a/src/V3Life.cpp b/src/V3Life.cpp index 112c3920e..6a704b5ae 100644 --- a/src/V3Life.cpp +++ b/src/V3Life.cpp @@ -306,6 +306,12 @@ private: } } virtual void visit(AstNodeAssign* nodep) override { + if (nodep->isTimingControl()) { + // V3Life doesn't understand time sense - don't optimize + setNoopt(); + iterateChildren(nodep); + return; + } // Collect any used variables first, as lhs may also be on rhs // Similar code in V3Dead const uint64_t lastEdit = AstNode::editCountGbl(); // When it was last edited @@ -326,7 +332,12 @@ private: } } virtual void visit(AstAssignDly* nodep) override { - // Don't treat as normal assign; V3Life doesn't understand time sense + // V3Life doesn't understand time sense + if (nodep->isTimingControl()) { + // Don't optimize + setNoopt(); + } + // Don't treat as normal assign iterateChildren(nodep); } @@ -433,7 +444,13 @@ private: } virtual void visit(AstVar*) override {} // Don't want varrefs under it - virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } + virtual void visit(AstNode* nodep) override { + if (nodep->isTimingControl()) { + // V3Life doesn't understand time sense - don't optimize + setNoopt(); + } + iterateChildren(nodep); + } public: // CONSTRUCTORS diff --git a/src/V3LinkInc.cpp b/src/V3LinkInc.cpp index 66dcbf8e1..a74bda289 100644 --- a/src/V3LinkInc.cpp +++ b/src/V3LinkInc.cpp @@ -149,6 +149,25 @@ private: nodep->v3fatalSrc( "For statements should have been converted to while statements in V3Begin.cpp"); } + virtual void visit(AstDelay* nodep) override { + m_insStmtp = nodep; + iterateAndNextNull(nodep->lhsp()); + m_insStmtp = nullptr; + iterateAndNextNull(nodep->stmtsp()); + m_insStmtp = nullptr; + } + virtual void visit(AstEventControl* nodep) override { + m_insStmtp = nullptr; + iterateAndNextNull(nodep->stmtsp()); + m_insStmtp = nullptr; + } + virtual void visit(AstWait* nodep) override { + m_insStmtp = nodep; + iterateAndNextNull(nodep->condp()); + m_insStmtp = nullptr; + iterateAndNextNull(nodep->bodysp()); + m_insStmtp = nullptr; + } virtual void visit(AstNodeStmt* nodep) override { if (!nodep->isStatement()) { iterateChildren(nodep); diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 9b85ac6f6..db3429fdb 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -175,17 +175,6 @@ private: nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); } - virtual void visit(AstWait* nodep) override { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: wait statements"); - // Statements we'll just execute immediately; equivalent to if they followed this - if (AstNode* const bodysp = nodep->bodysp()) { - bodysp->unlinkFrBackWithNext(); - nodep->replaceWith(bodysp); - } else { - nodep->unlinkFrBack(); - } - VL_DO_DANGLING(nodep->deleteTree(), nodep); - } virtual void visit(AstWhile* nodep) override { // Don't need to track AstRepeat/AstFor as they have already been converted VL_RESTORER(m_loopp); diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 705c40828..4558190df 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -593,9 +593,7 @@ private: << nodep->warnMore() << "... Suggest use a normal 'always'"); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (alwaysp && !alwaysp->sensesp()) { - // Verilator is still ony supporting SenTrees under an always, - // so allow the parser to handle everything and shim to - // historical AST here + // If the event control is at the top, move the sentree to the always if (AstSenTree* const sensesp = nodep->sensesp()) { sensesp->unlinkFrBackWithNext(); alwaysp->sensesp(sensesp); diff --git a/src/V3Options.cpp b/src/V3Options.cpp index ed192262b..cac9b2312 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -715,6 +715,14 @@ bool V3Options::systemCFound() { || (!getenvSYSTEMC_INCLUDE().empty() && !getenvSYSTEMC_LIBDIR().empty())); } +bool V3Options::coroutineSupport() { +#ifdef HAVE_COROUTINES + return true; +#else + return false; +#endif +} + //###################################################################### // V3 Options notification methods @@ -1370,6 +1378,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char m_timeOverridePrec = prec; } }); + DECL_OPTION("-timing", OnOff, &m_timing); DECL_OPTION("-top-module", Set, &m_topModule); DECL_OPTION("-top", Set, &m_topModule); DECL_OPTION("-trace", OnOff, &m_trace); @@ -1748,6 +1757,7 @@ void V3Options::showVersion(bool verbose) { cout << endl; cout << "Features (based on environment or compiled-in support):\n"; cout << " SystemC found = " << cvtToStr(systemCFound()) << endl; + cout << " Coroutine support = " << cvtToStr(coroutineSupport()) << endl; } //====================================================================== diff --git a/src/V3Options.h b/src/V3Options.h index 6ebfe19a9..4b1b7a016 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -271,6 +271,7 @@ private: bool m_threadsCoarsen = true; // main switch: --threads-coarsen bool m_threadsDpiPure = true; // main switch: --threads-dpi all/pure bool m_threadsDpiUnpure = false; // main switch: --threads-dpi all + VOptionBool m_timing; // main switch: --timing bool m_trace = false; // main switch: --trace bool m_traceCoverage = false; // main switch: --trace-coverage bool m_traceParams = true; // main switch: --trace-params @@ -456,6 +457,7 @@ public: bool threadsDpiPure() const { return m_threadsDpiPure; } bool threadsDpiUnpure() const { return m_threadsDpiUnpure; } bool threadsCoarsen() const { return m_threadsCoarsen; } + VOptionBool timing() const { return m_timing; } bool trace() const { return m_trace; } bool traceCoverage() const { return m_traceCoverage; } bool traceParams() const { return m_traceParams; } @@ -647,6 +649,7 @@ public: static string getenvVERILATOR_ROOT(); static bool systemCSystemWide(); static bool systemCFound(); // SystemC installed, or environment points to it + static bool coroutineSupport(); // Compiler supports coroutines // METHODS (file utilities using these options) string fileExists(const string& filename); diff --git a/src/V3Order.cpp b/src/V3Order.cpp index c335553c6..bb133e8b8 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -1276,17 +1276,24 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, // Process procedures per statement (unless profCFuncs), so we can split CFuncs within // procedures. Everything else is handled in one go + bool suspendable = false; + bool slow = m_slow; if (AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure)) { + suspendable = procp->isSuspendable(); + if (suspendable) slow = slow && !VN_IS(procp, Always); nodep = procp->bodysp(); pushDeletep(procp); } + // Put suspendable processes into individual functions on their own + if (suspendable) newFuncpr = nullptr; + // When profCFuncs, create a new function for all logic block if (v3Global.opt.profCFuncs()) newFuncpr = nullptr; while (nodep) { // Split the CFunc if too large (but not when profCFuncs) - if (!v3Global.opt.profCFuncs() + if (!suspendable && !v3Global.opt.profCFuncs() && (v3Global.opt.outputSplitCFuncs() && v3Global.opt.outputSplitCFuncs() < newStmtsr)) { // Put every statement into a unique function to ease profiling or reduce function @@ -1295,10 +1302,11 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, } if (!newFuncpr && domainp != m_deleteDomainp) { const string name = cfuncName(modp, domainp, scopep, nodep); - newFuncpr = new AstCFunc(nodep->fileline(), name, scopep); + newFuncpr + = new AstCFunc{nodep->fileline(), name, scopep, suspendable ? "VlCoroutine" : ""}; newFuncpr->isStatic(false); newFuncpr->isLoose(true); - newFuncpr->slow(m_slow); + newFuncpr->slow(slow); newStmtsr = 0; scopep->addActivep(newFuncpr); // Create top call to it @@ -1328,6 +1336,8 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, nodep = nextp; } + // Put suspendable processes into individual functions on their own + if (suspendable) newFuncpr = nullptr; return activep; } diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index e3f875327..af027ccb4 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -47,7 +47,6 @@ #include "V3Stats.h" #include "V3UniqueNames.h" -#include #include namespace V3Sched { @@ -202,30 +201,45 @@ LogicClasses gatherLogicClasses(AstNetlist* netlistp) { // Simple ordering in source order void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) { + // Create new subfunc for scope + const auto createNewSubFuncp = [&](AstScope* const scopep) { + const string subName{funcp->name() + "__" + scopep->nameDotless()}; + AstCFunc* const subFuncp = new AstCFunc{scopep->fileline(), subName, scopep}; + subFuncp->isLoose(true); + subFuncp->isConst(false); + subFuncp->declPrivate(true); + subFuncp->slow(funcp->slow()); + scopep->addActivep(subFuncp); + // Call it from the top function + funcp->addStmtsp(new AstCCall{scopep->fileline(), subFuncp}); + return subFuncp; + }; const VNUser1InUse user1InUse; // AstScope -> AstCFunc: the sub-function for the scope + const VNUser2InUse user2InUse; // AstScope -> int: sub-function counter used for names for (const auto& pair : lbs) { AstScope* const scopep = pair.first; AstActive* const activep = pair.second; - if (!scopep->user1p()) { - // Create a sub-function per scope so we can V3Combine them later - const string subName{funcp->name() + "__" + scopep->nameDotless()}; - AstCFunc* const subFuncp = new AstCFunc{scopep->fileline(), subName, scopep}; - subFuncp->isLoose(true); - subFuncp->isConst(false); - subFuncp->declPrivate(true); - subFuncp->slow(funcp->slow()); - scopep->addActivep(subFuncp); - scopep->user1p(subFuncp); - // Call it from the top function - funcp->addStmtsp(new AstCCall{scopep->fileline(), subFuncp}); - } - AstCFunc* const subFuncp = VN_AS(scopep->user1p(), CFunc); + // Create a sub-function per scope so we can V3Combine them later + if (!scopep->user1p()) scopep->user1p(createNewSubFuncp(scopep)); // Add statements to sub-function for (AstNode *logicp = activep->stmtsp(), *nextp; logicp; logicp = nextp) { + auto* subFuncp = VN_AS(scopep->user1p(), CFunc); nextp = logicp->nextp(); if (AstNodeProcedure* const procp = VN_CAST(logicp, NodeProcedure)) { - if (AstNode* const bodyp = procp->bodysp()) { + if (AstNode* bodyp = procp->bodysp()) { bodyp->unlinkFrBackWithNext(); + // If the process is suspendable, we need a separate function (a coroutine) + if (procp->isSuspendable()) { + subFuncp = createNewSubFuncp(scopep); + subFuncp->name(subFuncp->name() + "__" + cvtToStr(scopep->user2Inc())); + subFuncp->rtnType("VlCoroutine"); + if (VN_IS(procp, Always)) { + subFuncp->slow(false); + FileLine* const flp = procp->fileline(); + bodyp + = new AstWhile{flp, new AstConst{flp, AstConst::BitTrue{}}, bodyp}; + } + } subFuncp->addStmtsp(bodyp); } } else { @@ -432,6 +446,8 @@ class SenExprBuilder final { callp->dtypeSetBit(); return {callp, false}; } + case VEdgeType::ET_TRUE: // + return {currp(), false}; default: // LCOV_EXCL_START senItemp->v3fatalSrc("Unknown edge type"); return {nullptr, false}; @@ -937,7 +953,8 @@ void createEval(AstNetlist* netlistp, // AstVarScope* preTrigsp, // AstVarScope* nbaTrigsp, // AstCFunc* actFuncp, // - AstCFunc* nbaFuncp // + AstCFunc* nbaFuncp, // + TimingKit& timingKit // ) { FileLine* const flp = netlistp->fileline(); @@ -967,7 +984,10 @@ void createEval(AstNetlist* netlistp, // = makeEvalLoop( netlistp, "act", "Active", actTrig.m_vscp, actTrig.m_dumpp, [&]() { // Trigger - return new AstCCall{flp, actTrig.m_funcp}; + auto* const resultp = new AstCCall{flp, actTrig.m_funcp}; + // Commit trigger awaits from the previous iteration + resultp->addNextNull(timingKit.createCommit(netlistp)); + return resultp; }, [&]() { // Body AstNode* resultp = nullptr; @@ -994,6 +1014,9 @@ void createEval(AstNetlist* netlistp, // resultp = AstNode::addNext(resultp, callp); } + // Resume triggered timing schedulers + resultp = AstNode::addNextNull(resultp, timingKit.createResume(netlistp)); + // Invoke body function return AstNode::addNext(resultp, new AstCCall{flp, actFuncp}); }) @@ -1039,6 +1062,9 @@ void schedule(AstNetlist* netlistp) { V3Stats::addStat("Scheduling, " + name, size); }; + // Step 0. Prepare timing-related logic and external domains + auto timingKit = prepareTiming(netlistp); + // Step 1. Gather and classify all logic in the design LogicClasses logicClasses = gatherLogicClasses(netlistp); @@ -1113,7 +1139,8 @@ void schedule(AstNetlist* netlistp) { const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre, // &logicRegions.m_act, // - &logicRegions.m_nba}); + &logicRegions.m_nba, // + &timingKit.m_lbs}); const TriggerKit& actTrig = createTriggers(netlistp, senExprBuilder, senTreeps, "act", extraTriggers); @@ -1163,6 +1190,8 @@ void schedule(AstNetlist* netlistp) { remapSensitivities(logicRegions.m_pre, preTrigMap); remapSensitivities(logicRegions.m_act, actTrigMap); remapSensitivities(logicReplicas.m_act, actTrigMap); + remapSensitivities(timingKit.m_lbs, actTrigMap); + const auto& actTimingDomains = timingKit.remapDomains(actTrigMap); // Create the inverse map from trigger ref AstSenTree to original AstSenTree std::unordered_map trigToSenAct; @@ -1175,7 +1204,9 @@ void schedule(AstNetlist* netlistp) { AstCFunc* const actFuncp = V3Order::order( netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct, - "act", false, false, [=](const AstVarScope* vscp, std::vector& out) { + "act", false, false, [&](const AstVarScope* vscp, std::vector& out) { + auto it = actTimingDomains.find(vscp); + if (it != actTimingDomains.end()) out = it->second; if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredAct); }); splitCheck(actFuncp); @@ -1186,6 +1217,7 @@ void schedule(AstNetlist* netlistp) { // Remap sensitivities of the input logic to the triggers remapSensitivities(logicRegions.m_nba, nbaTrigMap); remapSensitivities(logicReplicas.m_nba, nbaTrigMap); + const auto& nbaTimingDomains = timingKit.remapDomains(nbaTrigMap); // Create the inverse map from trigger ref AstSenTree to original AstSenTree std::unordered_map trigToSenNba; @@ -1196,7 +1228,9 @@ void schedule(AstNetlist* netlistp) { AstCFunc* const nbaFuncp = V3Order::order( netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba, "nba", - v3Global.opt.mtasks(), false, [=](const AstVarScope* vscp, std::vector& out) { + v3Global.opt.mtasks(), false, [&](const AstVarScope* vscp, std::vector& out) { + auto it = nbaTimingDomains.find(vscp); + if (it != nbaTimingDomains.end()) out = it->second; if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredNba); }); splitCheck(nbaFuncp); @@ -1204,7 +1238,10 @@ void schedule(AstNetlist* netlistp) { if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-nba"); // Step 11: Bolt it all together to create the '_eval' function - createEval(netlistp, icoLoopp, actTrig, preTrigVscp, nbaTrigVscp, actFuncp, nbaFuncp); + createEval(netlistp, icoLoopp, actTrig, preTrigVscp, nbaTrigVscp, actFuncp, nbaFuncp, + timingKit); + + transformForks(netlistp); splitCheck(initp); diff --git a/src/V3Sched.h b/src/V3Sched.h index 71bc1773f..876b0a510 100644 --- a/src/V3Sched.h +++ b/src/V3Sched.h @@ -23,6 +23,7 @@ #include "V3Ast.h" #include +#include #include #include @@ -113,6 +114,41 @@ struct LogicReplicas final { LogicReplicas& operator=(LogicReplicas&&) = default; }; +// Everything needed for combining timing with static scheduling. +class TimingKit final { + AstCFunc* m_resumeFuncp = nullptr; // Global timing resume function + AstCFunc* m_commitFuncp = nullptr; // Global timing commit function + + // Additional var sensitivities for V3Order + std::map> m_externalDomains; + +public: + LogicByScope m_lbs; // Actives that resume timing schedulers + + // Remaps external domains using the specified trigger map + std::map> + remapDomains(const std::unordered_map& trigMap) const; + // Creates a timing resume call (if needed, else returns null) + AstCCall* createResume(AstNetlist* const netlistp); + // Creates a timing commit call (if needed, else returns null) + AstCCall* createCommit(AstNetlist* const netlistp); + + TimingKit() = default; + TimingKit(LogicByScope&& lbs, + std::map>&& externalDomains) + : m_externalDomains{externalDomains} + , m_lbs{lbs} {} + VL_UNCOPYABLE(TimingKit); + TimingKit(TimingKit&&) = default; + TimingKit& operator=(TimingKit&&) = default; +}; + +// Creates the timing kit and marks variables written by suspendables +TimingKit prepareTiming(AstNetlist* const netlistp); + +// Transforms fork sub-statements into separate functions +void transformForks(AstNetlist* const netlistp); + // Top level entry point to scheduling void schedule(AstNetlist*); diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 228f66be8..347cc6a66 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -124,6 +124,10 @@ public: if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi()) { addDrivingRegions(INPUT); } + // Currently we always execute suspendable processes at the beginning of + // the act region, which means combinational logic driven from a suspendable + // processes must be present in the 'act' region + if (varp()->isWrittenBySuspendable()) addDrivingRegions(ACTIVE); } AstVarScope* vscp() const { return m_vscp; } AstVar* varp() const { return m_vscp->varp(); } diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp new file mode 100644 index 000000000..5d22976ad --- /dev/null +++ b/src/V3SchedTiming.cpp @@ -0,0 +1,361 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Code scheduling +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// +// Functions defined in this file are used by V3Sched.cpp to properly integrate +// static scheduling with timing features. They create external domains for +// variables, remap them to trigger vectors, and create timing resume/commit +// calls for the global eval loop. There is also a function that transforms +// forks into emittable constructs. +// +// See the internals documentation docs/internals.rst for more details. +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3EmitCBase.h" +#include "V3Sched.h" + +#include + +namespace V3Sched { + +//============================================================================ +// Remaps external domains using the specified trigger map + +std::map> +TimingKit::remapDomains(const std::unordered_map& trigMap) const { + std::map> remappedDomainMap; + for (const auto& vscpDomains : m_externalDomains) { + const AstVarScope* const vscp = vscpDomains.first; + const auto& domains = vscpDomains.second; + auto& remappedDomains = remappedDomainMap[vscp]; + remappedDomains.reserve(domains.size()); + for (AstSenTree* const domainp : domains) { + remappedDomains.push_back(trigMap.at(domainp)); + } + } + return remappedDomainMap; +} + +//============================================================================ +// Creates a timing resume call (if needed, else returns null) + +AstCCall* TimingKit::createResume(AstNetlist* const netlistp) { + if (!m_resumeFuncp) { + if (m_lbs.empty()) return nullptr; + // Create global resume function + AstScope* const scopeTopp = netlistp->topScopep()->scopep(); + m_resumeFuncp = new AstCFunc{netlistp->fileline(), "_timing_resume", scopeTopp, ""}; + m_resumeFuncp->dontCombine(true); + m_resumeFuncp->isLoose(true); + m_resumeFuncp->isConst(false); + m_resumeFuncp->declPrivate(true); + scopeTopp->addActivep(m_resumeFuncp); + for (auto& p : m_lbs) { + // Put all the timing actives in the resume function + AstActive* const activep = p.second; + m_resumeFuncp->addStmtsp(activep); + } + } + return new AstCCall{m_resumeFuncp->fileline(), m_resumeFuncp}; +} + +//============================================================================ +// Creates a timing commit call (if needed, else returns null) + +AstCCall* TimingKit::createCommit(AstNetlist* const netlistp) { + if (!m_commitFuncp) { + for (auto& p : m_lbs) { + AstActive* const activep = p.second; + auto* const resumep = VN_AS(activep->stmtsp(), CMethodHard); + UASSERT_OBJ(!resumep->nextp(), resumep, "Should be the only statement here"); + AstVarScope* const schedulerp = VN_AS(resumep->fromp(), VarRef)->varScopep(); + UASSERT_OBJ(schedulerp->dtypep()->basicp()->isDelayScheduler() + || schedulerp->dtypep()->basicp()->isTriggerScheduler(), + schedulerp, "Unexpected type"); + if (!schedulerp->dtypep()->basicp()->isTriggerScheduler()) continue; + // Create the global commit function only if we have trigger schedulers + if (!m_commitFuncp) { + AstScope* const scopeTopp = netlistp->topScopep()->scopep(); + m_commitFuncp + = new AstCFunc{netlistp->fileline(), "_timing_commit", scopeTopp, ""}; + m_commitFuncp->dontCombine(true); + m_commitFuncp->isLoose(true); + m_commitFuncp->isConst(false); + m_commitFuncp->declPrivate(true); + scopeTopp->addActivep(m_commitFuncp); + } + AstSenTree* const sensesp = activep->sensesp(); + FileLine* const flp = sensesp->fileline(); + // Negate the sensitivity. We will commit only if the event wasn't triggered on the + // current iteration + auto* const negSensesp = sensesp->cloneTree(false); + negSensesp->sensesp()->sensp( + new AstLogNot{flp, negSensesp->sensesp()->sensp()->unlinkFrBack()}); + sensesp->addNextHere(negSensesp); + auto* const newactp = new AstActive{flp, "", negSensesp}; + // Create the commit call and put it in the commit function + auto* const commitp = new AstCMethodHard{ + flp, new AstVarRef{flp, schedulerp, VAccess::READWRITE}, "commit"}; + commitp->addPinsp(resumep->pinsp()->cloneTree(false)); + commitp->statement(true); + commitp->dtypeSetVoid(); + newactp->addStmtsp(commitp); + m_commitFuncp->addStmtsp(newactp); + } + // We still haven't created a commit function (no trigger schedulers), return null + if (!m_commitFuncp) return nullptr; + } + return new AstCCall{m_commitFuncp->fileline(), m_commitFuncp}; +} + +//============================================================================ +// Creates the timing kit and marks variables written by suspendables + +TimingKit prepareTiming(AstNetlist* const netlistp) { + if (!v3Global.usesTiming()) return {}; + class AwaitVisitor final : public VNVisitor { + private: + // NODE STATE + // AstSenTree::user1() -> bool. Set true if the sentree has been visited. + const VNUser1InUse m_inuser1; + + // STATE + bool m_inProcess = false; // Are we in a process? + bool m_gatherVars = false; // Should we gather vars in m_writtenBySuspendable? + AstScope* const m_scopeTopp; // Scope at the top + LogicByScope& m_lbs; // Timing resume actives + // Additional var sensitivities + std::map>& m_externalDomains; + std::set m_processDomains; // Sentrees from the current process + // Variables written by suspendable processes + std::vector m_writtenBySuspendable; + + // METHODS + // Create an active with a timing scheduler resume() call + void createResumeActive(AstCAwait* const awaitp) { + auto* const methodp = VN_AS(awaitp->exprp(), CMethodHard); + AstVarScope* const schedulerp = VN_AS(methodp->fromp(), VarRef)->varScopep(); + AstSenTree* const sensesp = awaitp->sensesp(); + FileLine* const flp = sensesp->fileline(); + // Create a resume() call on the timing scheduler + auto* const resumep = new AstCMethodHard{ + flp, new AstVarRef{flp, schedulerp, VAccess::READWRITE}, "resume"}; + if (schedulerp->dtypep()->basicp()->isTriggerScheduler()) { + resumep->addPinsp(methodp->pinsp()->cloneTree(false)); + } + resumep->statement(true); + resumep->dtypeSetVoid(); + // Put it in an active and put that in the global resume function + auto* const activep = new AstActive{flp, "_timing", sensesp}; + activep->addStmtsp(resumep); + m_lbs.emplace_back(m_scopeTopp, activep); + } + + // VISITORS + virtual void visit(AstNodeProcedure* const nodep) override { + UASSERT_OBJ(!m_inProcess && !m_gatherVars && m_processDomains.empty() + && m_writtenBySuspendable.empty(), + nodep, "Process in process?"); + m_inProcess = true; + m_gatherVars = nodep->isSuspendable(); // Only gather vars in a suspendable + const VNUser2InUse user2InUse; // AstVarScope -> bool: Set true if var has been added + // to m_writtenBySuspendable + iterateChildren(nodep); + for (AstVarScope* const vscp : m_writtenBySuspendable) { + m_externalDomains[vscp].insert(m_processDomains.begin(), m_processDomains.end()); + vscp->varp()->setWrittenBySuspendable(); + } + m_processDomains.clear(); + m_writtenBySuspendable.clear(); + m_inProcess = false; + m_gatherVars = false; + } + virtual void visit(AstFork* nodep) override { + VL_RESTORER(m_gatherVars); + if (m_inProcess) m_gatherVars = true; + // If not in a process, we don't need to gather variables or domains + iterateChildren(nodep); + } + virtual void visit(AstCAwait* nodep) override { + if (AstSenTree* const sensesp = nodep->sensesp()) { + if (!sensesp->user1SetOnce()) createResumeActive(nodep); + nodep->clearSensesp(); // Clear as these sentrees will get deleted later + if (m_inProcess) m_processDomains.insert(sensesp); + } + } + virtual void visit(AstNodeVarRef* nodep) override { + if (m_gatherVars && nodep->access().isWriteOrRW() + && !nodep->varScopep()->user2SetOnce()) { + m_writtenBySuspendable.push_back(nodep->varScopep()); + } + } + + //-------------------- + virtual void visit(AstNodeMath*) override {} // Accelerate + virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } + + public: + // CONSTRUCTORS + explicit AwaitVisitor(AstNetlist* nodep, LogicByScope& lbs, + std::map>& externalDomains) + : m_scopeTopp{nodep->topScopep()->scopep()} + , m_lbs{lbs} + , m_externalDomains{externalDomains} { + iterate(nodep); + } + virtual ~AwaitVisitor() override = default; + }; + LogicByScope lbs; + std::map> externalDomains; + AwaitVisitor{netlistp, lbs, externalDomains}; + return {std::move(lbs), std::move(externalDomains)}; +} + +//============================================================================ +// Visits all forks and transforms their sub-statements into separate functions. + +void transformForks(AstNetlist* const netlistp) { + if (!v3Global.usesTiming()) return; + // Transform all forked processes into functions + class ForkVisitor final : public VNVisitor { + private: + // NODE STATE + // AstVar::user1() -> bool. Set true if the variable was declared before the current + // fork. + const VNUser1InUse m_inuser1; + + // STATE + bool m_inClass = false; // Are we in a class? + AstFork* m_forkp = nullptr; // Current fork + AstCFunc* m_funcp = nullptr; // Current function + + // METHODS + // Remap local vars referenced by the given fork function + // TODO: We should only pass variables to the fork that are + // live in the fork body, but for that we need a proper data + // flow analysis framework which we don't have at the moment + void remapLocals(AstCFunc* const funcp, AstCCall* const callp) { + const VNUser2InUse user2InUse; // AstVarScope -> AstVarScope: var to remap to + funcp->foreach([&](AstNodeVarRef* refp) { + AstVar* const varp = refp->varp(); + AstBasicDType* const dtypep = varp->dtypep()->basicp(); + // If it a fork sync or an intra-assignment variable, pass it by value + const bool passByValue = (dtypep && dtypep->isForkSync()) + || VString::startsWith(varp->name(), "__Vintra"); + // Only handle vars passed by value or locals declared before the fork + if (!passByValue && (!varp->user1() || !varp->isFuncLocal())) return; + if (passByValue) { + // We can just pass it to the new function + } else if (m_forkp->joinType().join()) { + // If it's fork..join, we can refer to variables from the parent process + if (m_funcp->user1SetOnce()) { // Only do this once per function + // Move all locals to the heap before the fork + auto* const awaitp = new AstCAwait{ + m_forkp->fileline(), new AstCStmt{m_forkp->fileline(), "VlNow{}"}}; + awaitp->statement(true); + m_forkp->addHereThisAsNext(awaitp); + } + } else { + refp->v3warn(E_UNSUPPORTED, "Unsupported: variable local to a forking process " + "accessed in a fork..join_any or fork..join_none"); + return; + } + // Remap the reference + AstVarScope* const vscp = refp->varScopep(); + if (!vscp->user2p()) { + // Clone the var to the new function + AstVar* const varp = refp->varp(); + AstVar* const newvarp + = new AstVar{varp->fileline(), VVarType::BLOCKTEMP, varp->name(), varp}; + newvarp->funcLocal(true); + newvarp->direction(passByValue ? VDirection::INPUT : VDirection::REF); + funcp->addArgsp(newvarp); + AstVarScope* const newvscp + = new AstVarScope{newvarp->fileline(), funcp->scopep(), newvarp}; + funcp->scopep()->addVarp(newvscp); + vscp->user2p(newvscp); + callp->addArgsp(new AstVarRef{refp->fileline(), vscp, VAccess::READ}); + } + auto* const newvscp = VN_AS(vscp->user2p(), VarScope); + refp->varScopep(newvscp); + refp->varp(newvscp->varp()); + }); + } + + // VISITORS + virtual void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_inClass); + m_inClass = VN_IS(nodep, Class); + iterateChildren(nodep); + } + virtual void visit(AstCFunc* nodep) override { + m_funcp = nodep; + iterateChildren(nodep); + m_funcp = nullptr; + } + virtual void visit(AstVar* nodep) override { nodep->user1(true); } + virtual void visit(AstFork* nodep) override { + VL_RESTORER(m_forkp); + m_forkp = nodep; + iterateChildrenConst(nodep); // Const, so we don't iterate the calls twice + // Replace self with the function calls (no co_await, as we don't want the main + // process to suspend whenever any of the children do) + nodep->replaceWith(nodep->stmtsp()->unlinkFrBackWithNext()); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } + virtual void visit(AstBegin* nodep) override { + UASSERT_OBJ(m_forkp, nodep, "Begin outside of a fork"); + UASSERT_OBJ(!nodep->name().empty(), nodep, "Begin needs a name"); + FileLine* const flp = nodep->fileline(); + // Create a function to put this begin's statements in + AstCFunc* const newfuncp + = new AstCFunc{flp, nodep->name(), m_funcp->scopep(), "VlCoroutine"}; + m_funcp->addNextHere(newfuncp); + newfuncp->isLoose(m_funcp->isLoose()); + newfuncp->slow(m_funcp->slow()); + newfuncp->isConst(m_funcp->isConst()); + newfuncp->declPrivate(true); + // Replace the begin with a call to the newly created function + auto* const callp = new AstCCall{flp, newfuncp}; + nodep->replaceWith(callp); + // If we're in a class, add a vlSymsp arg + if (m_inClass) { + newfuncp->argTypes(EmitCBaseVisitor::symClassVar()); + callp->argTypes("vlSymsp"); + } + // Put the begin's statements in the function, delete the begin + newfuncp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext()); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + remapLocals(newfuncp, callp); + } + + //-------------------- + virtual void visit(AstNodeMath*) override {} // Accelerate + virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } + + public: + // CONSTRUCTORS + explicit ForkVisitor(AstNetlist* nodep) { iterate(nodep); } + virtual ~ForkVisitor() override = default; + }; + ForkVisitor{netlistp}; + V3Global::dumpCheckGlobalTree("sched_forks", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 6); +} + +} // namespace V3Sched diff --git a/src/V3Split.cpp b/src/V3Split.cpp index 83dc6ac5b..979371c8b 100644 --- a/src/V3Split.cpp +++ b/src/V3Split.cpp @@ -428,6 +428,10 @@ protected: UINFO(9, " NotSplittable " << nodep << endl); scoreboardPli(nodep); } + if (nodep->isTimingControl()) { + UINFO(9, " NoReordering " << nodep << endl); + m_noReorderWhy = "TimingControl"; + } iterateChildren(nodep); } diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp new file mode 100644 index 000000000..671435ff8 --- /dev/null +++ b/src/V3Timing.cpp @@ -0,0 +1,659 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Prepare AST for timing features +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +// TimingVisitor transformations: +// - for each intra-assignment timing control: +// - if it's a continuous assignment, transform it into an always +// - introduce an intermediate variable +// - write the original RHS to the intermediate variable before the timing control +// - write the intermediate variable to the original LHS after the timing control +// - for each delay: +// - scale it according to the module's timescale +// - replace it with a CAwait statement waiting on the global delay scheduler (with the +// specified delay value) +// - if there is no global delay scheduler (see verilated_timing.{h,cpp}), create it +// - for each event control: +// - if there is no corresponding trigger scheduler (see verilated_timing.{h,cpp}), create it +// - replace with a CAwait statement waiting on the corresponding trigger scheduler +// - for each wait(cond) statement: +// - replace it with a loop like: while (!cond) @() +// - for each fork: +// - put each statement in a begin if it isn't in one already +// - if it's not a fork..join_none: +// - create a join sync variable +// - create statements that sync the main process with its children +// - for each process or C++ function, if it has CAwait statements, mark it as suspendable +// - if we mark a virtual function as suspendable, mark all overriding and overridden functions +// as suspendable, as well as calling processes +// +// See the internals documentation docs/internals.rst for more details. +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Timing.h" + +#include "V3Ast.h" +#include "V3Const.h" +#include "V3EmitV.h" +#include "V3Graph.h" +#include "V3SenTree.h" +#include "V3UniqueNames.h" + +//###################################################################### +// Transform nodes affected by timing + +class TimingVisitor final : public VNVisitor { +private: + // TYPES + // Vertex of a dependency graph of suspendable nodes, e.g. if a node (process or task) is + // suspendable, all its dependents should also be suspendable + class DependencyVertex final : public V3GraphVertex { + AstNode* const m_nodep; // AST node represented by this graph vertex + // ACCESSORS + virtual string name() const override { + return cvtToHex(nodep()) + ' ' + nodep()->prettyTypeName(); + } + virtual FileLine* fileline() const override { return nodep()->fileline(); } + virtual string dotColor() const override { return nodep()->user2() ? "red" : "black"; } + + public: + // CONSTRUCTORS + DependencyVertex(V3Graph* graphp, AstNode* nodep) + : V3GraphVertex{graphp} + , m_nodep{nodep} {} + virtual ~DependencyVertex() override = default; + + // ACCESSORS + virtual AstNode* nodep() const { return m_nodep; } + }; + + // NODE STATE + // AstNode::user1() -> bool. Set true if the node has been + // processed. + // AstSenTree::user1() -> AstVarScope*. Trigger scheduler assigned + // to this sentree + // Ast{NodeProcedure,CFunc,Begin}::user2() -> bool. Set true if process/task is + // suspendable + // AstSenTree::user2() -> AstText*. Debug info passed to the + // timing schedulers + // Ast{NodeProcedure,CFunc,Begin}::user3() -> DependencyVertex*. Vertex in m_depGraph + const VNUser1InUse m_user1InUse; + const VNUser2InUse m_user2InUse; + const VNUser3InUse m_user3InUse; + + // STATE + // Current context + AstNetlist* const m_netlistp; // Root node + AstScope* const m_scopeTopp = m_netlistp->topScopep()->scopep(); // Scope at the top + AstClass* m_classp = nullptr; // Current class + AstScope* m_scopep = nullptr; // Current scope + AstActive* m_activep = nullptr; // Current active + AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Fork we're under + double m_timescaleFactor; // Factor to scale delays by + + // Unique names + V3UniqueNames m_contAssignVarNames{"__VassignWtmp__"}; // Names for temp AssignW vars + V3UniqueNames m_intraValueNames{"__Vintraval__"}; // Intra assign delay value var names + V3UniqueNames m_intraIndexNames{"__Vintraidx__"}; // Intra assign delay index var names + V3UniqueNames m_intraLsbNames{"__Vintralsb__"}; // Intra assign delay LSB var names + V3UniqueNames m_forkNames{"__Vfork__"}; // Fork name generator + V3UniqueNames m_trigSchedNames{"__VtrigSched"}; // Trigger scheduler name generator + + // DTypes + AstBasicDType* m_forkDtp = nullptr; // Fork variable type + AstBasicDType* m_trigSchedDtp = nullptr; // Trigger scheduler type + + // Timing-related globals + AstVarScope* m_delaySchedp = nullptr; // Global delay scheduler + AstSenTree* m_delaySensesp = nullptr; // Domain to trigger if a delayed coroutine is resumed + + // Other + V3Graph m_depGraph; // Dependency graph where a node is a dependency of another if it being + // suspendable makes the other node suspendable + SenTreeFinder m_finder{m_netlistp}; // Sentree finder and uniquifier + + // METHODS + VL_DEBUG_FUNC; // Declare debug() + // Get or create the dependency vertex for the given node + DependencyVertex* getDependencyVertex(AstNode* const nodep) { + if (!nodep->user3p()) nodep->user3p(new DependencyVertex{&m_depGraph, nodep}); + return nodep->user3u().to(); + } + // Find net delay on the LHS of an assignment + AstNode* getLhsNetDelay(AstNodeAssign* nodep) const { + bool foundWrite = false; + AstNode* delayp = nullptr; + nodep->lhsp()->foreach([&](const AstNodeVarRef* const refp) { + if (!refp->access().isWriteOrRW()) return; + UASSERT_OBJ(!foundWrite, nodep, "Should only be one variable written to on the LHS"); + foundWrite = true; + if (refp->varp()->delayp()) delayp = refp->varp()->delayp()->cloneTree(false); + }); + return delayp; + } + // Transform an assignment with an intra timing control into a timing control with the + // assignment under it + AstNodeStmt* factorOutTimingControl(AstNodeAssign* nodep) const { + AstNodeStmt* stmtp = nodep; + AstNode* delayp = getLhsNetDelay(nodep); + FileLine* const flp = nodep->fileline(); + AstNode* const controlp = nodep->timingControlp(); + if (controlp) { + controlp->unlinkFrBack(); + if (!VN_IS(controlp, SenTree)) { + delayp = delayp ? new AstAdd{flp, delayp, controlp} : controlp; + } + } + if (delayp) { + auto* const delayStmtp = new AstDelay{flp, delayp, nullptr}; + stmtp->replaceWith(delayStmtp); + delayStmtp->stmtsp(stmtp); + stmtp = delayStmtp; + } + if (auto* const sensesp = VN_CAST(controlp, SenTree)) { + auto* const eventControlp = new AstEventControl{flp, sensesp, nullptr}; + stmtp->replaceWith(eventControlp); + eventControlp->stmtsp(stmtp); + stmtp = eventControlp; + } + return stmtp == nodep ? nullptr : stmtp; + } + // Calculate the factor to scale delays by + double calculateTimescaleFactor(VTimescale timeunit) const { + int scalePowerOfTen = timeunit.powerOfTen() - m_netlistp->timeprecision().powerOfTen(); + return std::pow(10.0, scalePowerOfTen); + } + // Construct SenItems from VarRefs in an expression + AstSenItem* varRefpsToSenItemsp(AstNode* const nodep) const { + AstNode* senItemsp = nullptr; + const VNUser4InUse user4InUse; + nodep->foreach([&](AstNodeVarRef* refp) { + if (refp->access().isWriteOnly()) return; + AstVarScope* const vscp = refp->varScopep(); + if (vscp->user4SetOnce()) return; + const bool isEvent = vscp->dtypep() && vscp->dtypep()->basicp() + && vscp->dtypep()->basicp()->isEvent(); + const auto edgeType = isEvent ? VEdgeType::ET_EVENT : VEdgeType::ET_CHANGED; + senItemsp = AstNode::addNext( + senItemsp, new AstSenItem{refp->fileline(), edgeType, + new AstVarRef{refp->fileline(), vscp, VAccess::READ}}); + }); + return VN_AS(senItemsp, SenItem); + } + // Creates the global delay scheduler variable + AstVarScope* getCreateDelayScheduler() { + if (m_delaySchedp) return m_delaySchedp; + auto* const dlySchedDtp = new AstBasicDType{ + m_scopeTopp->fileline(), VBasicDTypeKwd::DELAY_SCHEDULER, VSigning::UNSIGNED}; + m_netlistp->typeTablep()->addTypesp(dlySchedDtp); + m_delaySchedp = m_scopeTopp->createTemp("__VdlySched", dlySchedDtp); + // Delay scheduler has to be accessible from top + m_delaySchedp->varp()->sigPublic(true); + m_netlistp->delaySchedulerp(m_delaySchedp->varp()); + return m_delaySchedp; + } + // Creates the delay sentree + AstSenTree* getCreateDelaySenTree() { + if (m_delaySensesp) return m_delaySensesp; + FileLine* const flp = m_scopeTopp->fileline(); + auto* const awaitingCurrentTimep + = new AstCMethodHard{flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::READ}, + "awaitingCurrentTime"}; + awaitingCurrentTimep->dtypeSetBit(); + m_delaySensesp + = new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE, awaitingCurrentTimep}}; + m_netlistp->topScopep()->addSenTreep(m_delaySensesp); + return m_delaySensesp; + } + // Creates a trigger scheduler variable + AstVarScope* getCreateTriggerSchedulerp(AstSenTree* const sensesp) { + if (!sensesp->user1p()) { + if (!m_trigSchedDtp) { + m_trigSchedDtp + = new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::TRIGGER_SCHEDULER, + VSigning::UNSIGNED}; + m_netlistp->typeTablep()->addTypesp(m_trigSchedDtp); + } + AstVarScope* const trigSchedp + = m_scopeTopp->createTemp(m_trigSchedNames.get(sensesp), m_trigSchedDtp); + sensesp->user1p(trigSchedp); + } + return VN_AS(sensesp->user1p(), VarScope); + } + // Creates a string describing the sentree + AstText* createEventDescription(AstSenTree* const sensesp) const { + if (!sensesp->user2p()) { + std::stringstream ss; + ss << '"'; + V3EmitV::verilogForTree(sensesp, ss); + ss << '"'; + auto* const commentp = new AstText{sensesp->fileline(), ss.str()}; + sensesp->user2p(commentp); + return commentp; + } + return VN_AS(sensesp->user2p(), Text)->cloneTree(false); + } + // Creates the fork handle type and returns it + AstBasicDType* getCreateForkSyncDTypep() { + if (m_forkDtp) return m_forkDtp; + m_forkDtp = new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::FORK_SYNC, + VSigning::UNSIGNED}; + m_netlistp->typeTablep()->addTypesp(m_forkDtp); + return m_forkDtp; + } + // Create a temp variable and optionally put it before the specified node (mark local if so) + AstVarScope* createTemp(FileLine* const flp, const std::string& name, + AstNodeDType* const dtypep, AstNode* const insertBeforep = nullptr) { + AstVar* varp; + if (insertBeforep) { + varp = new AstVar{flp, VVarType::BLOCKTEMP, name, dtypep}; + varp->funcLocal(true); + insertBeforep->addHereThisAsNext(varp); + } else { + varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep}; + m_scopep->modp()->addStmtp(varp); + } + AstVarScope* vscp = new AstVarScope{flp, m_scopep, varp}; + m_scopep->addVarp(vscp); + return vscp; + } + // Add a done() call on the fork sync + void addForkDone(AstBegin* const beginp, AstVarScope* const forkVscp) const { + FileLine* const flp = beginp->fileline(); + auto* const donep = new AstCMethodHard{ + beginp->fileline(), new AstVarRef{flp, forkVscp, VAccess::WRITE}, "done"}; + donep->dtypeSetVoid(); + donep->statement(true); + // Add debug info + donep->addPinsp(new AstText{flp, '"' + flp->filename() + '"'}); + donep->addPinsp(new AstText{flp, cvtToStr(flp->lineno())}); + beginp->addStmtsp(donep); + } + // Handle the 'join' part of a fork..join + void makeForkJoin(AstFork* const forkp) { + // Create a fork sync var + FileLine* const flp = forkp->fileline(); + // If we're in a function, insert the sync var directly before the fork + AstNode* const insertBeforep = VN_IS(m_procp, CFunc) ? forkp : nullptr; + AstVarScope* forkVscp + = createTemp(flp, forkp->name() + "__sync", getCreateForkSyncDTypep(), insertBeforep); + unsigned joinCount = 0; // Needed for join counter + // Add a .done() to each begin + for (AstNode* beginp = forkp->stmtsp(); beginp; beginp = beginp->nextp()) { + addForkDone(VN_AS(beginp, Begin), forkVscp); + joinCount++; + } + if (forkp->joinType().joinAny()) joinCount = 1; + // Set the join counter + auto* const initp = new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE}, + "init", new AstConst{flp, joinCount}}; + initp->dtypeSetVoid(); + initp->statement(true); + forkp->addHereThisAsNext(initp); + // Await the join at the end + auto* const joinp + = new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE}, "join"}; + joinp->dtypeSetVoid(); + // Add debug info + joinp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'}); + joinp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())}); + auto* const awaitp = new AstCAwait{flp, joinp}; + awaitp->statement(true); + forkp->addNextHere(awaitp); + } + + // VISITORS + virtual void visit(AstNodeModule* nodep) override { + UASSERT(!m_classp, "Module or class under class"); + VL_RESTORER(m_classp); + m_classp = VN_CAST(nodep, Class); + VL_RESTORER(m_timescaleFactor); + m_timescaleFactor = calculateTimescaleFactor(nodep->timeunit()); + iterateChildren(nodep); + } + virtual void visit(AstScope* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + iterateChildren(nodep); + } + virtual void visit(AstActive* nodep) override { + m_activep = nodep; + iterateChildren(nodep); + m_activep = nullptr; + } + virtual void visit(AstNodeProcedure* nodep) override { + VL_RESTORER(m_procp); + m_procp = nodep; + iterateChildren(nodep); + if (nodep->user2()) nodep->setSuspendable(); + } + virtual void visit(AstAlways* nodep) override { + visit(static_cast(nodep)); + if (nodep->isSuspendable() && !nodep->user1SetOnce()) { + FileLine* const flp = nodep->fileline(); + AstSenTree* const sensesp = m_activep->sensesp(); + if (sensesp->hasClocked()) { + AstNode* bodysp = nodep->bodysp()->unlinkFrBackWithNext(); + auto* const controlp = new AstEventControl{flp, sensesp->cloneTree(false), bodysp}; + nodep->addStmtp(controlp); + iterate(controlp); + } + // Note: The 'while (true)' outer loop will be added in V3Sched + auto* const activep = new AstActive{ + flp, "", new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Initial{}}}}; + activep->sensesStorep(activep->sensesp()); + activep->addStmtsp(nodep->unlinkFrBack()); + m_activep->addNextHere(activep); + } + } + virtual void visit(AstCFunc* nodep) override { + VL_RESTORER(m_procp); + m_procp = nodep; + iterateChildren(nodep); + DependencyVertex* const vxp = getDependencyVertex(nodep); + if (m_classp && nodep->isVirtual() + && !nodep->user1SetOnce()) { // If virtual (only visit once) + // Go over overridden functions + m_classp->repairCache(); + for (auto* cextp = m_classp->extendsp(); cextp; + cextp = VN_AS(cextp->nextp(), ClassExtends)) { + if (auto* const overriddenp + = VN_CAST(cextp->classp()->findMember(nodep->name()), CFunc)) { + if (overriddenp->user2()) { // If suspendable + if (!nodep->user2()) { + // It should be a coroutine but it has no awaits. Add a co_return at + // the end (either that or a co_await is required in a coroutine) + nodep->addStmtsp(new AstCStmt{nodep->fileline(), "co_return;\n"}); + } + nodep->user2(true); + // If it's suspendable already, no need to add it as our dependency or + // self to its dependencies + } else { + DependencyVertex* const overriddenVxp = getDependencyVertex(overriddenp); + new V3GraphEdge{&m_depGraph, vxp, overriddenVxp, 1}; + new V3GraphEdge{&m_depGraph, overriddenVxp, vxp, 1}; + } + } + } + } + if (nodep->user2() && !nodep->isCoroutine()) { // If first marked as suspendable + nodep->rtnType("VlCoroutine"); + // Revisit dependent nodes if needed + for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) { + auto* const depVxp = static_cast(edgep->fromp()); + AstNode* const depp = depVxp->nodep(); + if (!depp->user2()) { // If dependent not suspendable + depp->user2(true); + if (auto* const funcp = VN_CAST(depp, CFunc)) { + // It's a coroutine but has no awaits (a class method that overrides/is + // overridden by a suspendable, but doesn't have any awaits itself). Add a + // co_return at the end (either that or a co_await is required in a + // coroutine) + funcp->addStmtsp(new AstCStmt{funcp->fileline(), "co_return;\n"}); + } + } + iterate(depp); + } + } + } + virtual void visit(AstNodeCCall* nodep) override { + if (nodep->funcp()->user2()) { // If suspendable + auto* const awaitp = new AstCAwait{nodep->fileline(), nullptr}; + nodep->replaceWith(awaitp); + awaitp->exprp(nodep); + } else { + // Add our process/func as the CFunc's dependency as we might have to put an await here + DependencyVertex* const procVxp = getDependencyVertex(m_procp); + DependencyVertex* const funcVxp = getDependencyVertex(nodep->funcp()); + new V3GraphEdge{&m_depGraph, procVxp, funcVxp, 1}; + } + iterateChildren(nodep); + } + virtual void visit(AstCAwait* nodep) override { + v3Global.setUsesTiming(); + m_procp->user2(true); + } + virtual void visit(AstDelay* nodep) override { + FileLine* const flp = nodep->fileline(); + AstNode* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack()); + auto* const constp = VN_CAST(valuep, Const); + if (constp && constp->isZero()) { + nodep->v3warn(ZERODLY, "Unsupported: #0 delays do not schedule process resumption in " + "the Inactive region"); + } else { + // Scale the delay + if (valuep->dtypep()->isDouble()) { + valuep = new AstRToIRoundS{ + flp, + new AstMulD{flp, valuep, + new AstConst{flp, AstConst::RealDouble{}, m_timescaleFactor}}}; + } else { + valuep = new AstMul{flp, valuep, + new AstConst{flp, AstConst::Unsized64(), + static_cast(m_timescaleFactor)}}; + } + } + // Replace self with a 'co_await dlySched.delay()' + auto* const delayMethodp = new AstCMethodHard{ + flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::WRITE}, "delay", valuep}; + delayMethodp->dtypeSetVoid(); + // Add debug info + delayMethodp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'}); + delayMethodp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())}); + // Create the co_await + auto* const awaitp = new AstCAwait{flp, delayMethodp, getCreateDelaySenTree()}; + awaitp->statement(true); + // Relink child statements after the co_await + if (nodep->stmtsp()) awaitp->addNext(nodep->stmtsp()->unlinkFrBackWithNext()); + nodep->replaceWith(awaitp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } + virtual void visit(AstEventControl* nodep) override { + if (m_classp) nodep->v3warn(E_UNSUPPORTED, "Unsupported: event controls in methods"); + auto* const sensesp = m_finder.getSenTree(nodep->sensesp()); + nodep->sensesp()->unlinkFrBack()->deleteTree(); + // Get this sentree's trigger scheduler + FileLine* const flp = nodep->fileline(); + // Replace self with a 'co_await trigSched.trigger()' + auto* const triggerMethodp = new AstCMethodHard{ + flp, new AstVarRef{flp, getCreateTriggerSchedulerp(sensesp), VAccess::WRITE}, + "trigger"}; + triggerMethodp->dtypeSetVoid(); + // Add debug info + triggerMethodp->addPinsp(createEventDescription(sensesp)); + triggerMethodp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'}); + triggerMethodp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())}); + // Create the co_await + auto* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp}; + awaitp->statement(true); + // Relink child statements after the co_await + if (nodep->stmtsp()) awaitp->addNext(nodep->stmtsp()->unlinkFrBackWithNext()); + nodep->replaceWith(awaitp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } + virtual void visit(AstNodeAssign* nodep) override { + iterateChildren(nodep); + // Only process once to avoid infinite loops (due to the net delay) + if (nodep->user1SetOnce()) return; + AstNode* const controlp = factorOutTimingControl(nodep); + if (!controlp) return; + // Handle the intra assignment timing control + FileLine* const flp = nodep->fileline(); + if (VN_IS(nodep, AssignDly)) { + // If it's an NBA with an intra assignment delay, put it in a fork + auto* const forkp = new AstFork{flp, "", nullptr}; + forkp->joinType(VJoinType::JOIN_NONE); + controlp->replaceWith(forkp); + forkp->addStmtsp(controlp); + } + // Insert new vars before the timing control if we're in a function; in a process we can't + // do that. These intra-assignment vars will later be passed to forked processes by value. + AstNode* const insertBeforep = VN_IS(m_procp, CFunc) ? controlp : nullptr; + // Function for replacing values with intermediate variables + const auto replaceWithIntermediate = [&](AstNode* const valuep, const std::string& name) { + AstVarScope* const newvscp = createTemp(flp, name, valuep->dtypep(), insertBeforep); + valuep->replaceWith(new AstVarRef{flp, newvscp, VAccess::READ}); + controlp->addHereThisAsNext( + new AstAssign{flp, new AstVarRef{flp, newvscp, VAccess::WRITE}, valuep}); + }; + // Create the intermediate select vars. Note: because 'foreach' proceeds in + // pre-order, and we replace indices in selects with variables, we cannot + // reach another select under the index position. This is exactly what + // we want as only the top level selects are LValues. As an example, + // this transforms 'x[a[i]][b[j]] = y' + // into 't1 = a[i]; t0 = b[j]; x[t1][t0] = y'. + nodep->lhsp()->foreach([&](AstSel* selp) { + if (VN_IS(selp->lsbp(), Const)) return; + replaceWithIntermediate(selp->lsbp(), m_intraLsbNames.get(nodep)); + // widthp should be const + }); + nodep->lhsp()->foreach([&](AstNodeSel* selp) { + if (VN_IS(selp->bitp(), Const)) return; + replaceWithIntermediate(selp->bitp(), m_intraIndexNames.get(nodep)); + }); + // Replace the RHS with an intermediate value var + replaceWithIntermediate(nodep->rhsp(), m_intraValueNames.get(nodep)); + } + virtual void visit(AstAssignW* nodep) override { + iterateChildren(nodep); + auto* const netDelayp = getLhsNetDelay(nodep); + if (!netDelayp && !nodep->timingControlp()) return; + // This assignment will be converted to an always. In some cases this may generate an + // UNOPTFLAT, e.g.: assign #1 clk = ~clk. We create a temp var for the LHS of this assign, + // to disable the UNOPTFLAT warning for it. + // TODO: Find a way to do this without introducing this var. Perhaps make V3SchedAcyclic + // recognize awaits and prevent it from treating this kind of logic as cyclic + AstNode* const lhsp = nodep->lhsp()->unlinkFrBack(); + std::string varname; + if (auto* const refp = VN_CAST(lhsp, VarRef)) { + varname = m_contAssignVarNames.get(refp->name()); + } else { + varname = m_contAssignVarNames.get(lhsp); + } + auto* const tempvscp = m_scopep->createTemp(varname, lhsp->dtypep()); + tempvscp->varp()->delayp(netDelayp); + FileLine* const flp = nodep->fileline(); + flp->modifyWarnOff(V3ErrorCode::UNOPTFLAT, true); + tempvscp->fileline(flp); + tempvscp->varp()->fileline(flp); + // Remap the LHS to the new temp var + nodep->lhsp(new AstVarRef{flp, tempvscp, VAccess::WRITE}); + // Convert it to an always; the new assign with intra delay will be handled by + // visit(AstNodeAssign*) + AstAlways* const alwaysp = nodep->convertToAlways(); + visit(alwaysp); + // Put the LHS back in the AssignW; put the temp var on the RHS + nodep->lhsp(lhsp); + nodep->rhsp(new AstVarRef{flp, tempvscp, VAccess::READ}); + // Put the AssignW right after the always. Different order can produce UNOPTFLAT on the LHS + // var + alwaysp->addNextHere(nodep); + } + virtual void visit(AstWait* nodep) override { + // Wait on changed events related to the vars in the wait statement + AstSenItem* const senItemsp = varRefpsToSenItemsp(nodep->condp()); + AstNode* const condp = nodep->condp()->unlinkFrBack(); + AstNode* const bodysp = nodep->bodysp(); + if (bodysp) bodysp->unlinkFrBackWithNext(); + FileLine* const flp = nodep->fileline(); + if (senItemsp) { + // Put the event control in a while loop with the wait expression as condition + auto* const loopp + = new AstWhile{flp, new AstLogNot{flp, condp}, + new AstEventControl{flp, new AstSenTree{flp, senItemsp}, nullptr}}; + loopp->addNextNull(bodysp); + nodep->replaceWith(loopp); + } else { + condp->v3warn(WAITCONST, "Wait statement condition is constant"); + auto* constCondp = VN_AS(V3Const::constifyEdit(condp), Const); + if (constCondp->isZero()) { + // We have to await forever instead of simply returning in case we're deep in a + // callstack + auto* const awaitp = new AstCAwait{flp, new AstCStmt{flp, "VlForever{}"}}; + awaitp->statement(true); + nodep->replaceWith(awaitp); + if (bodysp) VL_DO_DANGLING(bodysp->deleteTree(), bodysp); + } else if (bodysp) { + // Just put the body there + nodep->replaceWith(bodysp); + } + VL_DO_DANGLING(constCondp->deleteTree(), condp); + } + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } + virtual void visit(AstFork* nodep) override { + if (nodep->user1SetOnce()) return; + // Create a unique name for this fork + nodep->name(m_forkNames.get(nodep)); + unsigned idx = 0; // Index for naming begins + AstNode* stmtp = nodep->stmtsp(); + // Put each statement in a begin + while (stmtp) { + if (!VN_IS(stmtp, Begin)) { + auto* const beginp = new AstBegin{stmtp->fileline(), "", nullptr}; + stmtp->replaceWith(beginp); + beginp->addStmtsp(stmtp); + stmtp = beginp; + } + auto* const beginp = VN_AS(stmtp, Begin); + stmtp = beginp->nextp(); + VL_RESTORER(m_procp); + m_procp = beginp; + iterate(beginp); + if (!m_procp->user2()) { + // No awaits, we can inline this process + if (auto* const stmtsp = beginp->stmtsp()) { + nodep->addHereThisAsNext(stmtsp->unlinkFrBackWithNext()); + } + VL_DO_DANGLING(beginp->unlinkFrBack()->deleteTree(), beginp); + // We inlined at least one process, so we can consider it joined; convert join_any + // to join_none + if (nodep->joinType().joinAny()) nodep->joinType(VJoinType::JOIN_NONE); + } else { + // Name the begin (later the name will be used for a new function) + beginp->name(nodep->name() + "__" + cvtToStr(idx++)); + } + } + if (!nodep->stmtsp()) { + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + } else if (!nodep->joinType().joinNone()) { + makeForkJoin(nodep); + } + } + + //-------------------- + virtual void visit(AstNodeMath*) override {} // Accelerate + virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + explicit TimingVisitor(AstNetlist* nodep) + : m_netlistp{nodep} { + iterate(nodep); + if (debug() >= 6) m_depGraph.dumpDotFilePrefixed("timing_deps"); + } + virtual ~TimingVisitor() override = default; +}; + +//###################################################################### +// Timing class functions + +void V3Timing::timingAll(AstNetlist* nodep) { + UINFO(2, __FUNCTION__ << ": " << endl); + TimingVisitor{nodep}; + V3Global::dumpCheckGlobalTree("timing", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); +} diff --git a/src/V3Timing.h b/src/V3Timing.h new file mode 100644 index 000000000..2114f1bbb --- /dev/null +++ b/src/V3Timing.h @@ -0,0 +1,32 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Prepare AST for features features +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2022 by Wilson Snyder. This program is free software; you +// can redistribute it and/or modify it under the terms of either the GNU +// Lesser General Public License Version 3 or the Perl Artistic License +// Version 2.0. +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef _V3TIMING_H_ +#define _V3TIMING_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" + +//============================================================================ + +class V3Timing final { +public: + static void timingAll(AstNetlist* nodep); +}; + +#endif // Guard diff --git a/src/V3Unknown.cpp b/src/V3Unknown.cpp index 12f011366..fec087411 100644 --- a/src/V3Unknown.cpp +++ b/src/V3Unknown.cpp @@ -57,6 +57,7 @@ private: AstNodeModule* m_modp = nullptr; // Current module AstAssignW* m_assignwp = nullptr; // Current assignment AstAssignDly* m_assigndlyp = nullptr; // Current assignment + AstNode* m_timingControlp = nullptr; // Current assignment's intra timing control bool m_constXCvt = false; // Convert X's bool m_allowXUnique = true; // Allow unique assignments VDouble0 m_statUnkVars; // Statistic tracking @@ -123,12 +124,14 @@ private: m_modp->addStmtp(varp); AstNode* const abovep = prep->backp(); // Grab above point before we replace 'prep' prep->replaceWith(new AstVarRef(fl, varp, VAccess::WRITE)); + if (m_timingControlp) m_timingControlp->unlinkFrBack(); AstIf* const newp = new AstIf( fl, condp, - (needDly ? static_cast( - new AstAssignDly(fl, prep, new AstVarRef(fl, varp, VAccess::READ))) - : static_cast( - new AstAssign(fl, prep, new AstVarRef(fl, varp, VAccess::READ))))); + (needDly + ? static_cast(new AstAssignDly{ + fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}) + : static_cast(new AstAssign{ + fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}))); newp->branchPred(VBranchPred::BP_LIKELY); newp->isBoundsCheck(true); if (debug() >= 9) newp->dumpTree(cout, " _new: "); @@ -155,18 +158,29 @@ private: } virtual void visit(AstAssignDly* nodep) override { VL_RESTORER(m_assigndlyp); + VL_RESTORER(m_timingControlp); { m_assigndlyp = nodep; + m_timingControlp = nodep->timingControlp(); VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. } } virtual void visit(AstAssignW* nodep) override { VL_RESTORER(m_assignwp); + VL_RESTORER(m_timingControlp); { m_assignwp = nodep; + m_timingControlp = nodep->timingControlp(); VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. } } + virtual void visit(AstNodeAssign* nodep) override { + VL_RESTORER(m_timingControlp); + { + m_timingControlp = nodep->timingControlp(); + iterateChildren(nodep); + } + } virtual void visit(AstCaseItem* nodep) override { VL_RESTORER(m_constXCvt); { diff --git a/src/V3Width.cpp b/src/V3Width.cpp index f938f95d7..d13b15ad6 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -607,8 +607,20 @@ private: VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return; } + if (nodep->fileline()->timingOn()) { + if (v3Global.opt.timing().isSetTrue()) { + userIterate(nodep->lhsp(), WidthVP{nullptr, BOTH}.p()); + iterateNull(nodep->stmtsp()); + return; + } else if (v3Global.opt.timing().isSetFalse()) { + nodep->v3warn(STMTDLY, "Ignoring delay on this statement due to --no-timing"); + } else { + nodep->v3warn( + E_NEEDTIMINGOPT, + "Use --timing or --no-timing to specify how delays should be handled"); + } + } if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBack()); - nodep->v3warn(STMTDLY, "Unsupported: Ignoring delay on this delayed statement."); VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } virtual void visit(AstFork* nodep) override { @@ -618,19 +630,27 @@ private: VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return; } - if (v3Global.opt.bboxUnsup() + if (!nodep->fileline()->timingOn() // With no statements, begin is identical || !nodep->stmtsp() - // With one statement, a begin block does as good as a fork/join or join_any - || (!nodep->stmtsp()->nextp() && !nodep->joinType().joinNone())) { + || (!v3Global.opt.timing().isSetTrue() // If no --timing + && (v3Global.opt.bboxUnsup() + // With one statement and no timing, a begin block does as good as a + // fork/join or join_any + || (!nodep->stmtsp()->nextp() && !nodep->joinType().joinNone())))) { AstNode* stmtsp = nullptr; if (nodep->stmtsp()) stmtsp = nodep->stmtsp()->unlinkFrBack(); AstBegin* const newp = new AstBegin{nodep->fileline(), nodep->name(), stmtsp}; nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); + } else if (v3Global.opt.timing().isSetTrue()) { + iterateChildren(nodep); + } else if (v3Global.opt.timing().isSetFalse()) { + nodep->v3warn(E_NOTIMING, "Fork statements require --timing"); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } else { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: fork statements"); - // TBD might support only normal join, if so complain about other join flavors + nodep->v3warn(E_NEEDTIMINGOPT, + "Use --timing or --no-timing to specify how forks should be handled"); } } virtual void visit(AstDisableFork* nodep) override { @@ -1358,10 +1378,28 @@ private: VL_DO_DANGLING(nodep->deleteTree(), nodep); } virtual void visit(AstEventControl* nodep) override { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: event control statement in this location\n" - << nodep->warnMore() - << "... Suggest have one event control statement " - << "per procedure, at the top of the procedure"); + if (VN_IS(m_ftaskp, Func)) { + nodep->v3error("Event controls are not legal in functions. Suggest use a task " + "(IEEE 1800-2017 13.4.4)"); + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + return; + } + if (nodep->fileline()->timingOn()) { + if (v3Global.opt.timing().isSetTrue()) { + iterateChildren(nodep); + return; + } else if (v3Global.opt.timing().isSetFalse()) { + nodep->v3warn(E_NOTIMING, + "Event control statement in this location requires --timing\n" + << nodep->warnMore() + << "... With --no-timing, suggest have one event control " + << "statement per procedure, at the top of the procedure"); + } else { + nodep->v3warn(E_NEEDTIMINGOPT, "Use --timing or --no-timing to specify how " + "event controls should be handled"); + } + } + if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBack()); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } virtual void visit(AstAttrOf* nodep) override { @@ -4236,10 +4274,26 @@ private: nodep->v3warn(E_UNSUPPORTED, "Unsupported: assignment of event data type"); } } - if (nodep->timingControlp()) { - nodep->timingControlp()->v3warn( - ASSIGNDLY, "Unsupported: Ignoring timing control on this assignment."); - nodep->timingControlp()->unlinkFrBackWithNext()->deleteTree(); + if (auto* const controlp = nodep->timingControlp()) { + if (VN_IS(m_ftaskp, Func)) { + controlp->v3error("Timing controls are not legal in functions. Suggest use a task " + "(IEEE 1800-2017 13.4.4)"); + VL_DO_DANGLING(controlp->unlinkFrBackWithNext()->deleteTree(), controlp); + } else if (nodep->fileline()->timingOn() && v3Global.opt.timing().isSetTrue()) { + iterateNull(controlp); + } else { + if (nodep->fileline()->timingOn()) { + if (v3Global.opt.timing().isSetFalse()) { + controlp->v3warn(ASSIGNDLY, "Ignoring timing control on this " + "assignment/primitive due to --no-timing"); + } else { + controlp->v3warn(E_NEEDTIMINGOPT, + "Use --timing or --no-timing to specify how " + "timing controls should be handled"); + } + } + VL_DO_DANGLING(controlp->unlinkFrBackWithNext()->deleteTree(), controlp); + } } if (VN_IS(nodep->rhsp(), EmptyQueue)) { UINFO(9, "= {} -> .delete(): " << nodep); @@ -5067,6 +5121,35 @@ private: userIterateChildren(nodep, WidthVP(SELF, BOTH).p()); } } + virtual void visit(AstWait* nodep) override { + if (VN_IS(m_ftaskp, Func)) { + nodep->v3error("Wait statements are not legal in functions. Suggest use a task " + "(IEEE 1800-2017 13.4.4)"); + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + return; + } + if (nodep->fileline()->timingOn()) { + if (v3Global.opt.timing().isSetTrue()) { + userIterate(nodep->condp(), WidthVP(SELF, PRELIM).p()); + iterateNull(nodep->bodysp()); + return; + } else if (v3Global.opt.timing().isSetFalse()) { + nodep->v3warn(E_NOTIMING, "Wait statements require --timing"); + } else { + nodep->v3warn(E_NEEDTIMINGOPT, "Use --timing or --no-timing to specify how wait " + "statements should be handled"); + } + } + // If we ignore timing: + // Statements we'll just execute immediately; equivalent to if they followed this + if (AstNode* const bodysp = nodep->bodysp()) { + bodysp->unlinkFrBackWithNext(); + nodep->replaceWith(bodysp); + } else { + nodep->unlinkFrBack(); + } + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } virtual void visit(AstWith* nodep) override { // Should otherwise be underneath a method call AstNodeDType* const vdtypep = m_vup->dtypeNullSkipRefp(); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 46971cc54..a68c7628f 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -89,6 +89,7 @@ #include "V3TSP.h" #include "V3Table.h" #include "V3Task.h" +#include "V3Timing.h" #include "V3Trace.h" #include "V3TraceDecl.h" #include "V3Tristate.h" @@ -362,6 +363,14 @@ static void process() { // Reorder assignments in pipelined blocks if (v3Global.opt.fReorder()) V3Split::splitReorderAll(v3Global.rootp()); + if (v3Global.opt.timing().isSetTrue()) { + // Convert AST for timing if requested + // Needs to be after V3Gate, as that step modifies sentrees + // Needs to be before V3Delayed, as delayed assignments are handled differently in + // suspendable processes + V3Timing::timingAll(v3Global.rootp()); + } + // Create delayed assignments // This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step V3Delayed::delayedAll(v3Global.rootp()); diff --git a/src/config_build.h.in b/src/config_build.h.in index d044c1411..32a0da56f 100644 --- a/src/config_build.h.in +++ b/src/config_build.h.in @@ -82,6 +82,7 @@ using std::endl; // - If defined, the default search path has it, so support is always enabled. // - If undef, not system-wide, user can set SYSTEMC_INCLUDE. #undef HAVE_SYSTEMC +#undef HAVE_COROUTINES //********************************************************************** //**** OS and compiler specifics diff --git a/src/verilog.l b/src/verilog.l index c3eb40f4c..798b44ef3 100644 --- a/src/verilog.l +++ b/src/verilog.l @@ -131,6 +131,8 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} "sc_bv" { FL; return yVLT_SC_BV; } "sformat" { FL; return yVLT_SFORMAT; } "split_var" { FL; return yVLT_SPLIT_VAR; } + "timing_off" { FL; return yVLT_TIMING_OFF; } + "timing_on" { FL; return yVLT_TIMING_ON; } "tracing_off" { FL; return yVLT_TRACING_OFF; } "tracing_on" { FL; return yVLT_TRACING_ON; } @@ -748,6 +750,8 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} "/*verilator split_var*/" { FL; return yVL_SPLIT_VAR; } "/*verilator tag"[^*]*"*/" { FL; yylval.strp = PARSEP->newString(V3ParseImp::lexParseTag(yytext)); return yVL_TAG; } + "/*verilator timing_off*/" { FL_FWD; PARSEP->lexFileline()->timingOn(false); FL_BRK; } + "/*verilator timing_on*/" { FL_FWD; PARSEP->lexFileline()->timingOn(true); FL_BRK; } "/*verilator trace_init_task*/" { FL; return yVL_TRACE_INIT_TASK; } "/*verilator tracing_off*/" { FL_FWD; PARSEP->lexFileline()->tracingOn(false); FL_BRK; } "/*verilator tracing_on*/" { FL_FWD; PARSEP->lexFileline()->tracingOn(true); FL_BRK; } diff --git a/src/verilog.y b/src/verilog.y index 71e97120c..938a2e82f 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -40,11 +40,22 @@ #define BBUNSUP(fl, msg) (fl)->v3warn(E_UNSUPPORTED, msg) #define GATEUNSUP(fl, tok) \ { BBUNSUP((fl), "Unsupported: Verilog 1995 gate primitive: " << (tok)); } -#define PRIMDLYUNSUP(nodep) \ - { \ - if (nodep) { \ - nodep->v3warn(ASSIGNDLY, "Unsupported: Ignoring delay on this primitive."); \ - nodep->deleteTree(); \ +#define RISEFALLDLYUNSUP(nodep) \ + if (nodep->fileline()->timingOn() && v3Global.opt.timing().isSetTrue()) { \ + nodep->v3warn(RISEFALLDLY, \ + "Unsupported: rising/falling/turn-off delays. Using the first delay"); \ + } +#define MINTYPMAXDLYUNSUP(nodep) \ + if (nodep->fileline()->timingOn() && v3Global.opt.timing().isSetTrue()) { \ + nodep->v3warn( \ + MINTYPMAXDLY, \ + "Unsupported: minimum/typical/maximum delay expressions. Using the typical delay"); \ + } +#define PUT_DLYS_IN_ASSIGNS(delayp, assignsp) \ + if (delayp) { \ + for (auto* nodep = assignsp; nodep; nodep = nodep->nextp()) { \ + auto* const assignp = VN_AS(nodep, NodeAssign); \ + assignp->timingControlp(nodep == assignsp ? delayp : delayp->cloneTree(false)); \ } \ } @@ -393,6 +404,8 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) %token yVLT_SC_BV "sc_bv" %token yVLT_SFORMAT "sformat" %token yVLT_SPLIT_VAR "split_var" +%token yVLT_TIMING_OFF "timing_off" +%token yVLT_TIMING_ON "timing_on" %token yVLT_TRACING_OFF "tracing_off" %token yVLT_TRACING_ON "tracing_on" @@ -1742,13 +1755,13 @@ net_dataTypeE: // // Otherwise #(...) can't be determined to be a delay or parameters // // Submit this as a footnote to the committee var_data_type { $$ = $1; } - | signingE rangeList delayE + | signingE rangeList delay_controlE { $$ = GRAMMARP->addRange(new AstBasicDType{$2->fileline(), LOGIC, $1}, $2, true); GRAMMARP->setNetDelay($3); } // not implicit | signing { $$ = new AstBasicDType{$1, LOGIC, $1}; } // not implicit - | /*implicit*/ delayE + | /*implicit*/ delay_controlE { $$ = new AstBasicDType{CRELINE(), LOGIC}; GRAMMARP->setNetDelay($1); } // not implicit ; @@ -2428,14 +2441,10 @@ module_common_item: // ==IEEE: module_common_item ; continuous_assign: // IEEE: continuous_assign - yASSIGN strengthSpecE delayE assignList ';' + yASSIGN strengthSpecE delay_controlE assignList ';' { $$ = $4; - if ($3) - for (auto* nodep = $$; nodep; nodep = nodep->nextp()) { - auto* const assignp = VN_AS(nodep, NodeAssign); - assignp->addTimingControlp(nodep == $$ ? $3 : $3->cloneTree(false)); - } + PUT_DLYS_IN_ASSIGNS($3, $$); } ; @@ -2678,27 +2687,22 @@ assignOne: ; delay_or_event_controlE: // IEEE: delay_or_event_control plus empty + /* empty */ { $$ = nullptr; } + | delay_control { $$ = $1; } + | event_control { $$ = $1; } +//UNSUP | yREPEAT '(' expr ')' event_control { } + ; + +delay_controlE: /* empty */ { $$ = nullptr; } | delay_control { $$ = $1; } - | event_control { $$ = $1; } -//UNSUP | yREPEAT '(' expr ')' event_control { } - ; - -delayE: - /* empty */ { $$ = nullptr; } - | delay { $$ = $1; } - ; - -delay: - delay_control - { $$ = $1; } ; delay_control: //== IEEE: delay_control '#' delay_value { $$ = $2; } | '#' '(' minTypMax ')' { $$ = $3; } - | '#' '(' minTypMax ',' minTypMax ')' { $$ = $3; DEL($5); } - | '#' '(' minTypMax ',' minTypMax ',' minTypMax ')' { $$ = $3; DEL($5); DEL($7); } + | '#' '(' minTypMax ',' minTypMax ')' { $$ = $3; RISEFALLDLYUNSUP($3); DEL($5); } + | '#' '(' minTypMax ',' minTypMax ',' minTypMax ')' { $$ = $3; RISEFALLDLYUNSUP($3); DEL($5); DEL($7); } ; delay_value: // ==IEEE:delay_value @@ -2715,7 +2719,7 @@ delayExpr: minTypMax: // IEEE: mintypmax_expression and constant_mintypmax_expression delayExpr { $$ = $1; } - | delayExpr ':' delayExpr ':' delayExpr { $$ = $3; DEL($1); DEL($5); } + | delayExpr ':' delayExpr ':' delayExpr { $$ = $3; MINTYPMAXDLYUNSUP($3); DEL($1); DEL($5); } ; netSigList: // IEEE: list_of_port_identifiers @@ -2729,8 +2733,9 @@ netSig: // IEEE: net_decl_assignment - one element from | netId sigAttrListE '=' expr { $$ = VARDONEA($1, *$1, nullptr, $2); auto* const assignp = new AstAssignW{$3, new AstVarRef{$1, *$1, VAccess::WRITE}, $4}; - if ($$->delayp()) assignp->addTimingControlp($$->delayp()->unlinkFrBack()); // IEEE 1800-2017 10.3.3 - $$->addNext(assignp); } | netId variable_dimensionList sigAttrListE + if ($$->delayp()) assignp->timingControlp($$->delayp()->unlinkFrBack()); // IEEE 1800-2017 10.3.3 + $$->addNext(assignp); } + | netId variable_dimensionList sigAttrListE { $$ = VARDONEA($1,*$1, $2, $3); } ; @@ -3270,8 +3275,14 @@ statement_item: // IEEE: statement_item // | par_block { $$ = $1; } // // IEEE: procedural_timing_control_statement + procedural_timing_control - | delay_control stmtBlock { $$ = new AstDelay{$1->fileline(), $1, $2}; } - | event_control stmtBlock { $$ = new AstEventControl(FILELINE_OR_CRE($1), $1, $2); } + | delay_control stmtBlock { AstNode* nextp = nullptr; + if ($2 && $2->nextp()) nextp = $2->nextp()->unlinkFrBackWithNext(); + $$ = new AstDelay{$1->fileline(), $1, $2}; + $$->addNextNull(nextp); } + | event_control stmtBlock { AstNode* nextp = nullptr; + if ($2 && $2->nextp()) nextp = $2->nextp()->unlinkFrBackWithNext(); + $$ = new AstEventControl{FILELINE_OR_CRE($1), $1, $2}; + $$->addNextNull(nextp); } //UNSUP cycle_delay stmtBlock { UNSUP } // | seq_block { $$ = $1; } @@ -4704,33 +4715,33 @@ stream_expressionOrDataType: // IEEE: from streaming_concatenation // Gate declarations gateDecl: - yBUF delayE gateBufList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yBUFIF0 delayE gateBufif0List ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yBUFIF1 delayE gateBufif1List ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yNOT delayE gateNotList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yNOTIF0 delayE gateNotif0List ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yNOTIF1 delayE gateNotif1List ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yAND delayE gateAndList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yNAND delayE gateNandList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yOR delayE gateOrList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yNOR delayE gateNorList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yXOR delayE gateXorList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yXNOR delayE gateXnorList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yPULLUP delayE gatePullupList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yPULLDOWN delayE gatePulldownList ';' { $$ = $3; PRIMDLYUNSUP($2); } - | yNMOS delayE gateBufif1List ';' { $$ = $3; PRIMDLYUNSUP($2); } // ~=bufif1, as don't have strengths yet - | yPMOS delayE gateBufif0List ';' { $$ = $3; PRIMDLYUNSUP($2); } // ~=bufif0, as don't have strengths yet + yBUF delay_controlE gateBufList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yBUFIF0 delay_controlE gateBufif0List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yBUFIF1 delay_controlE gateBufif1List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yNOT delay_controlE gateNotList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yNOTIF0 delay_controlE gateNotif0List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yNOTIF1 delay_controlE gateNotif1List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yAND delay_controlE gateAndList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yNAND delay_controlE gateNandList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yOR delay_controlE gateOrList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yNOR delay_controlE gateNorList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yXOR delay_controlE gateXorList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yXNOR delay_controlE gateXnorList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yPULLUP delay_controlE gatePullupList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yPULLDOWN delay_controlE gatePulldownList ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } + | yNMOS delay_controlE gateBufif1List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } // ~=bufif1, as don't have strengths yet + | yPMOS delay_controlE gateBufif0List ';' { $$ = $3; PUT_DLYS_IN_ASSIGNS($2, $3); } // ~=bufif0, as don't have strengths yet // - | yTRAN delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tran"); } // Unsupported - | yRCMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rcmos"); } // Unsupported - | yCMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"cmos"); } // Unsupported - | yRNMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rmos"); } // Unsupported - | yRPMOS delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"pmos"); } // Unsupported - | yRTRAN delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtran"); } // Unsupported - | yRTRANIF0 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif0"); } // Unsupported - | yRTRANIF1 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif1"); } // Unsupported - | yTRANIF0 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif0"); } // Unsupported - | yTRANIF1 delayE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif1"); } // Unsupported + | yTRAN delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tran"); } // Unsupported + | yRCMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rcmos"); } // Unsupported + | yCMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"cmos"); } // Unsupported + | yRNMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rmos"); } // Unsupported + | yRPMOS delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"pmos"); } // Unsupported + | yRTRAN delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtran"); } // Unsupported + | yRTRANIF0 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif0"); } // Unsupported + | yRTRANIF1 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"rtranif1"); } // Unsupported + | yTRANIF0 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif0"); } // Unsupported + | yTRANIF1 delay_controlE gateUnsupList ';' { $$ = $3; GATEUNSUP($3,"tranif1"); } // Unsupported ; gateBufList: @@ -6541,6 +6552,7 @@ vltItem: vltOffFront: yVLT_COVERAGE_OFF { $$ = V3ErrorCode::I_COVERAGE; } + | yVLT_TIMING_OFF { $$ = V3ErrorCode::I_TIMING; } | yVLT_TRACING_OFF { $$ = V3ErrorCode::I_TRACING; } | yVLT_LINT_OFF { $$ = V3ErrorCode::I_LINT; } | yVLT_LINT_OFF yVLT_D_RULE idAny @@ -6550,6 +6562,7 @@ vltOffFront: vltOnFront: yVLT_COVERAGE_ON { $$ = V3ErrorCode::I_COVERAGE; } + | yVLT_TIMING_ON { $$ = V3ErrorCode::I_TIMING; } | yVLT_TRACING_ON { $$ = V3ErrorCode::I_TRACING; } | yVLT_LINT_ON { $$ = V3ErrorCode::I_LINT; } | yVLT_LINT_ON yVLT_D_RULE idAny diff --git a/test_regress/driver.pl b/test_regress/driver.pl index 22a2a67f8..5f33c65eb 100755 --- a/test_regress/driver.pl +++ b/test_regress/driver.pl @@ -1125,7 +1125,7 @@ sub compile { } if (!$param{fails} && $param{make_main}) { - $self->_make_main(); + $self->_make_main($param{timing_loop}); } if ($param{verilator_make_gmake} @@ -1480,6 +1480,12 @@ sub have_sc { return 0; } +sub have_coroutines { + my $self = (ref $_[0] ? shift : $Self); + return 1 if $self->verilator_version =~ /coroutine support *= *1/i; + return 0; +} + sub make_version { my $ver = `$ENV{MAKE} --version`; if ($ver =~ /make ([0-9]+\.[0-9]+)/i) { @@ -1731,6 +1737,11 @@ sub _try_regex { sub _make_main { my $self = shift; + my $timing_loop = shift; + + if ($timing_loop && $self->sc) { + $self->error("Cannot use timing loop and SystemC together!\n"); + } if ($self->vhdl) { $self->_read_inputs_vhdl(); @@ -1859,33 +1870,61 @@ sub _make_main { } print $fh " ${set}fastclk = false;\n" if $self->{inputs}{fastclk}; print $fh " ${set}clk = false;\n" if $self->{inputs}{clk}; - _print_advance_time($self, $fh, 10); + if (!$timing_loop) { + _print_advance_time($self, $fh, 10); + } print $fh " }\n"; my $time = $self->sc ? "sc_time_stamp()" : "contextp->time()"; - print $fh " while ((${time} < sim_time * MAIN_TIME_MULTIPLIER)\n"; - print $fh " && !contextp->gotFinish()) {\n"; + print $fh " while ("; + if (!$timing_loop || $self->{inputs}{clk}) { + print $fh "(${time} < sim_time * MAIN_TIME_MULTIPLIER) && "; + } + print $fh "!contextp->gotFinish()) {\n"; - for (my $i = 0; $i < 5; $i++) { - my $action = 0; - if ($self->{inputs}{fastclk}) { - print $fh " ${set}fastclk = !${set}fastclk;\n"; - $action = 1; + if ($timing_loop) { + print $fh " topp->eval();\n"; + if ($self->{trace}) { + $fh->print("#if VM_TRACE\n"); + $fh->print(" if (tfp) tfp->dump(contextp->time());\n"); + $fh->print("#endif // VM_TRACE\n"); } - if ($i == 0 && $self->{inputs}{clk}) { - print $fh " ${set}clk = !${set}clk;\n"; - $action = 1; + if ($self->{inputs}{clk}) { + print $fh " uint64_t cycles = contextp->time() / MAIN_TIME_MULTIPLIER;\n"; + print $fh " uint64_t new_time = (cycles + 1) * MAIN_TIME_MULTIPLIER;\n"; + print $fh " if (topp->eventsPending() &&\n"; + print $fh " topp->nextTimeSlot() / MAIN_TIME_MULTIPLIER <= cycles) {\n"; + print $fh " new_time = topp->nextTimeSlot();\n"; + print $fh " } else {\n"; + print $fh " ${set}clk = !${set}clk;\n"; + print $fh " }\n"; + print $fh " contextp->time(new_time);\n"; + } else { + print $fh " if (!topp->eventsPending()) break;\n"; + print $fh " contextp->time(topp->nextTimeSlot());\n"; } - if ($self->{savable}) { - $fh->print(" if (save_time && ${time} == save_time) {\n"); - $fh->print(" save_model(\"$self->{obj_dir}/saved.vltsv\");\n"); - $fh->print(" printf(\"Exiting after save_model\\n\");\n"); - $fh->print(" topp.reset(nullptr);\n"); - $fh->print(" return 0;\n"); - $fh->print(" }\n"); + } else { + for (my $i = 0; $i < 5; $i++) { + my $action = 0; + if ($self->{inputs}{fastclk}) { + print $fh " ${set}fastclk = !${set}fastclk;\n"; + $action = 1; + } + if ($i == 0 && $self->{inputs}{clk}) { + print $fh " ${set}clk = !${set}clk;\n"; + $action = 1; + } + if ($self->{savable}) { + $fh->print(" if (save_time && ${time} == save_time) {\n"); + $fh->print(" save_model(\"$self->{obj_dir}/saved.vltsv\");\n"); + $fh->print(" printf(\"Exiting after save_model\\n\");\n"); + $fh->print(" topp.reset(nullptr);\n"); + $fh->print(" return 0;\n"); + $fh->print(" }\n"); + } + _print_advance_time($self, $fh, 1, $action); } - _print_advance_time($self, $fh, 1, $action); } if ($self->{benchmarksim}) { $fh->print(" if (VL_UNLIKELY(!warm)) {\n"); @@ -1941,7 +1980,7 @@ sub _print_advance_time { else { $set = "topp->"; } if ($self->sc) { - print $fh " sc_start(${time}, $Self->{sc_time_resolution});\n"; + print $fh " sc_start(${time} * MAIN_TIME_MULTIPLIER, $Self->{sc_time_resolution});\n"; } else { if ($action) { print $fh " ${set}eval();\n"; diff --git a/test_regress/t/t_altera_lpm_counter.pl b/test_regress/t/t_altera_lpm_counter.pl index e079ef062..78ec8c557 100755 --- a/test_regress/t/t_altera_lpm_counter.pl +++ b/test_regress/t/t_altera_lpm_counter.pl @@ -14,7 +14,7 @@ top_filename("t/t_altera_lpm.v"); (my $module = $Self->{name}) =~ s/.*t_altera_//; compile( - verilator_flags2 => ["--top-module ${module}"] + verilator_flags2 => ["--top-module ${module}", "--no-timing"] ); ok(1); diff --git a/test_regress/t/t_delay.pl b/test_regress/t/t_delay.pl index cf79784fb..3e8a3c919 100755 --- a/test_regress/t/t_delay.pl +++ b/test_regress/t/t_delay.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ['-Wno-STMTDLY -Wno-ASSIGNDLY'], + verilator_flags2 => ['-Wno-STMTDLY -Wno-ASSIGNDLY --no-timing'], ); execute( diff --git a/test_regress/t/t_delay.v b/test_regress/t/t_delay.v index c391c5b61..161362387 100644 --- a/test_regress/t/t_delay.v +++ b/test_regress/t/t_delay.v @@ -4,6 +4,8 @@ // any use, without warranty, 2003 by Wilson Snyder. // SPDX-License-Identifier: CC0-1.0 +`timescale 100ns/1ns + module t (/*AUTOARG*/ // Inputs clk diff --git a/test_regress/t/t_delay_func_bad.out b/test_regress/t/t_delay_func_bad.out deleted file mode 100644 index dd63449c5..000000000 --- a/test_regress/t/t_delay_func_bad.out +++ /dev/null @@ -1,9 +0,0 @@ -%Error: t/t_delay_func_bad.v:10:8: Delays are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) - : ... In instance t - 10 | #1 $stop; - | ^ -%Error: t/t_delay_func_bad.v:23:8: Delays are not legal in final blocks (IEEE 1800-2017 9.2.3) - : ... In instance t - 23 | #1; - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_delay_func_bad.v b/test_regress/t/t_delay_func_bad.v deleted file mode 100644 index aa7ceddfc..000000000 --- a/test_regress/t/t_delay_func_bad.v +++ /dev/null @@ -1,27 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain, for -// any use, without warranty, 2020 by Wilson Snyder. -// SPDX-License-Identifier: CC0-1.0 - -module t(/*AUTOARG*/); - - function int f; - #1 $stop; - f = 0; - endfunction - - int i; - - initial begin - i = f(); - $write("*-* All Finished *-*\n"); - $finish; - end - - final begin - #1; - $stop; - end - -endmodule diff --git a/test_regress/t/t_delay_stmtdly_bad.out b/test_regress/t/t_delay_stmtdly_bad.out index fe957acdb..90327f0d8 100644 --- a/test_regress/t/t_delay_stmtdly_bad.out +++ b/test_regress/t/t_delay_stmtdly_bad.out @@ -1,31 +1,31 @@ -%Warning-ASSIGNDLY: t/t_delay.v:22:13: Unsupported: Ignoring timing control on this assignment. +%Warning-ASSIGNDLY: t/t_delay.v:24:13: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 22 | assign #(1.2000000000000000) dly1 = dly0 + 32'h1; + 24 | assign #(1.2000000000000000) dly1 = dly0 + 32'h1; | ^~~~~~~~~~~~~~~~~~ ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-ASSIGNDLY: t/t_delay.v:27:19: Unsupported: Ignoring timing control on this assignment. +%Warning-ASSIGNDLY: t/t_delay.v:29:19: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 27 | dly0 <= #0 32'h11; + 29 | dly0 <= #0 32'h11; | ^ -%Warning-ASSIGNDLY: t/t_delay.v:30:19: Unsupported: Ignoring timing control on this assignment. +%Warning-ASSIGNDLY: t/t_delay.v:32:19: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 30 | dly0 <= #0.12 dly0 + 32'h12; + 32 | dly0 <= #0.12 dly0 + 32'h12; | ^~~~ -%Warning-ASSIGNDLY: t/t_delay.v:38:26: Unsupported: Ignoring timing control on this assignment. +%Warning-ASSIGNDLY: t/t_delay.v:40:26: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 38 | dly0 <= #(dly_s.dly) 32'h55; + 40 | dly0 <= #(dly_s.dly) 32'h55; | ^~~ -%Warning-STMTDLY: t/t_delay.v:43:11: Unsupported: Ignoring delay on this delayed statement. +%Warning-STMTDLY: t/t_delay.v:45:11: Ignoring delay on this statement due to --no-timing : ... In instance t - 43 | #100 $finish; + 45 | #100 $finish; | ^~~ -%Warning-UNUSED: t/t_delay.v:20:12: Signal is not used: 'dly_s' +%Warning-UNUSED: t/t_delay.v:22:12: Signal is not used: 'dly_s' : ... In instance t - 20 | dly_s_t dly_s; + 22 | dly_s_t dly_s; | ^~~~~ -%Warning-BLKSEQ: t/t_delay.v:37:20: Blocking assignment '=' in sequential logic process +%Warning-BLKSEQ: t/t_delay.v:39:20: Blocking assignment '=' in sequential logic process : ... Suggest using delayed assignment '<=' - 37 | dly_s.dly = 55; + 39 | dly_s.dly = 55; | ^ %Error: Exiting due to diff --git a/test_regress/t/t_delay_stmtdly_bad.pl b/test_regress/t/t_delay_stmtdly_bad.pl index 85ea0432b..59820b3fb 100755 --- a/test_regress/t/t_delay_stmtdly_bad.pl +++ b/test_regress/t/t_delay_stmtdly_bad.pl @@ -13,7 +13,7 @@ scenarios(vlt => 1); top_filename("t/t_delay.v"); lint( - verilator_flags2 => ['-Wall -Wno-DECLFILENAME'], + verilator_flags2 => ['--no-timing -Wall -Wno-DECLFILENAME'], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_delay_timing.pl b/test_regress/t/t_delay_timing.pl new file mode 100755 index 000000000..505ebf865 --- /dev/null +++ b/test_regress/t/t_delay_timing.pl @@ -0,0 +1,32 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +$Self->{main_time_multiplier} = 10e-7 / 10e-9; + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_delay.v"); + + compile( + timing_loop => 1, + verilator_flags2 => ['--timing -Wno-ZERODLY'], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_event_control.out b/test_regress/t/t_event_control.out new file mode 100644 index 000000000..f04260a95 --- /dev/null +++ b/test_regress/t/t_event_control.out @@ -0,0 +1,12 @@ +%Error-NOTIMING: t/t_event_control.v:14:7: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 14 | @(clk); + | ^ + ... For error description see https://verilator.org/warn/NOTIMING?v=latest +%Error-NOTIMING: t/t_event_control.v:16:7: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 16 | @(clk); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_event_control_unsup.pl b/test_regress/t/t_event_control.pl similarity index 90% rename from test_regress/t/t_event_control_unsup.pl rename to test_regress/t/t_event_control.pl index be66c40e6..1047a4907 100755 --- a/test_regress/t/t_event_control_unsup.pl +++ b/test_regress/t/t_event_control.pl @@ -11,13 +11,10 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( + verilator_flags2 => ['--no-timing'], fails => $Self->{vlt_all}, expect_filename => $Self->{golden_filename}, ); -execute( - check_finished => 1, - ) if !$Self->{vlt_all}; - ok(1); 1; diff --git a/test_regress/t/t_event_control_unsup.v b/test_regress/t/t_event_control.v similarity index 100% rename from test_regress/t/t_event_control_unsup.v rename to test_regress/t/t_event_control.v diff --git a/test_regress/t/t_event_control_timing.out b/test_regress/t/t_event_control_timing.out new file mode 100644 index 000000000..35690cce5 --- /dev/null +++ b/test_regress/t/t_event_control_timing.out @@ -0,0 +1,3 @@ +[10] Got +[15] Got +*-* All Finished *-* diff --git a/test_regress/t/t_event_control_timing.pl b/test_regress/t/t_event_control_timing.pl new file mode 100755 index 000000000..6a4829740 --- /dev/null +++ b/test_regress/t/t_event_control_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_event_control.v"); + + compile( + verilator_flags2 => ["--timing"], + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_event_control_unsup.out b/test_regress/t/t_event_control_unsup.out deleted file mode 100644 index 7c40c182e..000000000 --- a/test_regress/t/t_event_control_unsup.out +++ /dev/null @@ -1,12 +0,0 @@ -%Error-UNSUPPORTED: t/t_event_control_unsup.v:14:7: Unsupported: event control statement in this location - : ... In instance t - : ... Suggest have one event control statement per procedure, at the top of the procedure - 14 | @(clk); - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_event_control_unsup.v:16:7: Unsupported: event control statement in this location - : ... In instance t - : ... Suggest have one event control statement per procedure, at the top of the procedure - 16 | @(clk); - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_fork.out b/test_regress/t/t_fork.out index f0f3b3489..6070aeb85 100644 --- a/test_regress/t/t_fork.out +++ b/test_regress/t/t_fork.out @@ -1,6 +1,6 @@ -%Error-UNSUPPORTED: t/t_fork.v:10:14: Unsupported: fork statements - : ... In instance t +%Error-NOTIMING: t/t_fork.v:10:14: Fork statements require --timing + : ... In instance t 10 | fork : fblk | ^~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest + ... For error description see https://verilator.org/warn/NOTIMING?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_fork.pl b/test_regress/t/t_fork.pl index a5846c699..9459580d1 100755 --- a/test_regress/t/t_fork.pl +++ b/test_regress/t/t_fork.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); lint( + verilator_flags2 => ['--no-timing'], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_fork_bbox.pl b/test_regress/t/t_fork_bbox.pl index 3a83673be..72f18add1 100755 --- a/test_regress/t/t_fork_bbox.pl +++ b/test_regress/t/t_fork_bbox.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); lint( - verilator_flags2 => ['--lint-only --bbox-unsup'], + verilator_flags2 => ['--lint-only --no-timing --bbox-unsup'], ); ok(1); diff --git a/test_regress/t/t_fork_disable.out b/test_regress/t/t_fork_disable.out index 9c589b372..62ba6a34f 100644 --- a/test_regress/t/t_fork_disable.out +++ b/test_regress/t/t_fork_disable.out @@ -1,12 +1,8 @@ -%Error-UNSUPPORTED: t/t_fork_disable.v:12:7: Unsupported: fork statements - : ... In instance t - 12 | fork - | ^~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error-UNSUPPORTED: t/t_fork_disable.v:16:7: Unsupported: disable fork statements : ... In instance t 16 | disable fork; | ^~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error-UNSUPPORTED: t/t_fork_disable.v:17:7: Unsupported: wait fork statements : ... In instance t 17 | wait fork; diff --git a/test_regress/t/t_fork_disable.pl b/test_regress/t/t_fork_disable.pl index 89ffd046b..0ca21a5ca 100755 --- a/test_regress/t/t_fork_disable.pl +++ b/test_regress/t/t_fork_disable.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(linter => 1); lint( - verilator_flags2 => ['--lint-only'], + verilator_flags2 => ['--lint-only --timing'], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_fork_label.pl b/test_regress/t/t_fork_label.pl index 1c4ba9485..8bb4480e1 100755 --- a/test_regress/t/t_fork_label.pl +++ b/test_regress/t/t_fork_label.pl @@ -10,7 +10,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -compile(); +compile( + verilator_flags2 => ['--no-timing'], + ); execute(); diff --git a/test_regress/t/t_fork_label_timing.pl b/test_regress/t/t_fork_label_timing.pl new file mode 100755 index 000000000..8d67932b9 --- /dev/null +++ b/test_regress/t/t_fork_label_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_fork_label.v"); + + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_fork_timing.pl b/test_regress/t/t_fork_timing.pl new file mode 100755 index 000000000..f31aef7c3 --- /dev/null +++ b/test_regress/t/t_fork_timing.pl @@ -0,0 +1,29 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_fork.v"); + + compile( + verilator_flags2 => ["--timing"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_func_lib_sub.pl b/test_regress/t/t_func_lib_sub.pl index 6c38477cd..8651e4fd4 100755 --- a/test_regress/t/t_func_lib_sub.pl +++ b/test_regress/t/t_func_lib_sub.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt_all => 1); compile( + verilator_flags2 => ['--no-timing'], ); # No execute ok(1); diff --git a/test_regress/t/t_func_lib_sub_timing.pl b/test_regress/t/t_func_lib_sub_timing.pl new file mode 100755 index 000000000..0acb617a6 --- /dev/null +++ b/test_regress/t/t_func_lib_sub_timing.pl @@ -0,0 +1,25 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); # UNOPTTHREADS in vltmt + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_func_lib_sub.v"); + + compile( + verilator_flags2 => ["--timing"], + ); +} +# No execute +ok(1); +1; diff --git a/test_regress/t/t_func_rand.pl b/test_regress/t/t_func_rand.pl index ce5a6dd80..32375a0ef 100755 --- a/test_regress/t/t_func_rand.pl +++ b/test_regress/t/t_func_rand.pl @@ -13,7 +13,7 @@ scenarios(vlt_all => 1); compile( make_top_shell => 0, make_main => 0, - verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp"], + verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp", "--no-timing"], ); execute( diff --git a/test_regress/t/t_gate_basic.pl b/test_regress/t/t_gate_basic.pl index a17622844..b56aa9309 100755 --- a/test_regress/t/t_gate_basic.pl +++ b/test_regress/t/t_gate_basic.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( + verilator_flags2 => ["--no-timing"], ); execute( diff --git a/test_regress/t/t_gate_basic_timing.pl b/test_regress/t/t_gate_basic_timing.pl new file mode 100755 index 000000000..713929427 --- /dev/null +++ b/test_regress/t/t_gate_basic_timing.pl @@ -0,0 +1,32 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +$Self->{main_time_multiplier} = 10e-7 / 10e-9; + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_gate_basic.v"); + + compile( + timing_loop => 1, + verilator_flags2 => ["--timing --timescale 10ns/1ns -Wno-RISEFALLDLY"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_gate_delay_unsup.out b/test_regress/t/t_gate_delay_unsup.out index 7a1697074..c3e3e70fd 100644 --- a/test_regress/t/t_gate_delay_unsup.out +++ b/test_regress/t/t_gate_delay_unsup.out @@ -1,12 +1,6 @@ -%Warning-ASSIGNDLY: t/t_gate_basic.v:23:12: Unsupported: Ignoring delay on this primitive. - 23 | not #(0.108) NT0 (nt0, a[0]); - | ^~~~~ - ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest - ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-ASSIGNDLY: t/t_gate_basic.v:24:11: Unsupported: Ignoring delay on this primitive. - 24 | and #1 AN0 (an0, a[0], b[0]); - | ^ -%Warning-ASSIGNDLY: t/t_gate_basic.v:25:12: Unsupported: Ignoring delay on this primitive. +%Warning-RISEFALLDLY: t/t_gate_basic.v:25:12: Unsupported: rising/falling/turn-off delays. Using the first delay 25 | nand #(2,3) ND0 (nd0, a[0], b[0], b[1]); | ^ + ... For warning description see https://verilator.org/warn/RISEFALLDLY?v=latest + ... Use "/* verilator lint_off RISEFALLDLY */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_gate_delay_unsup.pl b/test_regress/t/t_gate_delay_unsup.pl index 2f885e5ae..ecb0ab324 100755 --- a/test_regress/t/t_gate_delay_unsup.pl +++ b/test_regress/t/t_gate_delay_unsup.pl @@ -13,7 +13,7 @@ scenarios(linter => 1); top_filename("t/t_gate_basic.v"); lint( - verilator_flags2 => ["--lint-only -Wall -Wno-DECLFILENAME -Wno-UNUSED"], + verilator_flags2 => ["--lint-only -Wall -Wno-DECLFILENAME -Wno-UNUSED --timing"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_gen_forif.pl b/test_regress/t/t_gen_forif.pl index bc8663a80..4017016a5 100755 --- a/test_regress/t/t_gen_forif.pl +++ b/test_regress/t/t_gen_forif.pl @@ -12,6 +12,7 @@ scenarios(simulator => 1); compile( nc_flags2 => ['+access+r'], + verilator_flags2 => ["--no-timing"], ); execute( diff --git a/test_regress/t/t_gen_intdot.pl b/test_regress/t/t_gen_intdot.pl index b46d46042..e245a66a7 100755 --- a/test_regress/t/t_gen_intdot.pl +++ b/test_regress/t/t_gen_intdot.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( + verilator_flags2 => ["--no-timing"], ); execute( diff --git a/test_regress/t/t_lib.pl b/test_regress/t/t_lib.pl index 93aa4bfd8..ef6fcd3b2 100755 --- a/test_regress/t/t_lib.pl +++ b/test_regress/t/t_lib.pl @@ -30,6 +30,7 @@ while (1) { run(logfile => "$secret_dir/vlt_compile.log", cmd => ["perl", "$ENV{VERILATOR_ROOT}/bin/verilator", + '--no-timing', "--prefix", "Vt_lib_prot_secret", "-cc", @@ -52,6 +53,7 @@ while (1) { compile( verilator_flags2 => ["$secret_dir/secret.sv", + '--no-timing', "-LDFLAGS", "$secret_prefix/libsecret.a"], xsim_flags2 => ["$secret_dir/secret.sv"], diff --git a/test_regress/t/t_lib_nolib.pl b/test_regress/t/t_lib_nolib.pl index d4b9b47e9..0a385f0cb 100755 --- a/test_regress/t/t_lib_nolib.pl +++ b/test_regress/t/t_lib_nolib.pl @@ -21,7 +21,7 @@ top_filename("t/t_lib_prot.v"); # Tests the same code as t_lib_prot.pl but without --protect-lib compile( - verilator_flags2 => ["t/t_lib_prot_secret.v"], + verilator_flags2 => ["t/t_lib_prot_secret.v", '--no-timing'], xsim_flags2 => ["t/t_lib_prot_secret.v"], ); diff --git a/test_regress/t/t_lib_prot.pl b/test_regress/t/t_lib_prot.pl index 2df0816e8..14c01dc7c 100755 --- a/test_regress/t/t_lib_prot.pl +++ b/test_regress/t/t_lib_prot.pl @@ -28,6 +28,7 @@ while (1) { run(logfile => "$secret_dir/vlt_compile.log", cmd => ["perl", "$ENV{VERILATOR_ROOT}/bin/verilator", + '--no-timing', "--prefix", "Vt_lib_prot_secret", "-cc", @@ -52,6 +53,7 @@ while (1) { compile( verilator_flags2 => ["$secret_dir/secret.sv", + '--no-timing', "-LDFLAGS", "$secret_prefix/libsecret.a"], xsim_flags2 => ["$secret_dir/secret.sv"], diff --git a/test_regress/t/t_lib_prot_clk_gated.pl b/test_regress/t/t_lib_prot_clk_gated.pl index 4a290d26d..b1286cca2 100755 --- a/test_regress/t/t_lib_prot_clk_gated.pl +++ b/test_regress/t/t_lib_prot_clk_gated.pl @@ -29,6 +29,7 @@ while (1) { run(logfile => "$secret_dir/vlt_compile.log", cmd => ["perl", "$ENV{VERILATOR_ROOT}/bin/verilator", + '--no-timing', "--prefix", "Vt_lib_prot_secret", "-cc", @@ -54,6 +55,7 @@ while (1) { compile( verilator_flags2 => ["$secret_dir/secret.sv", + '--no-timing', "-GGATED_CLK=1", "-LDFLAGS", "$secret_prefix/libsecret.a"], diff --git a/test_regress/t/t_lib_prot_shared.pl b/test_regress/t/t_lib_prot_shared.pl index cc0c2f977..cd8b2a1c6 100755 --- a/test_regress/t/t_lib_prot_shared.pl +++ b/test_regress/t/t_lib_prot_shared.pl @@ -32,6 +32,7 @@ while (1) { cmd => ["perl", "$ENV{VERILATOR_ROOT}/bin/verilator", ($Self->{vltmt} ? ' --threads 6' : ''), + '--no-timing', "--prefix", "Vt_lib_prot_secret", "-cc", @@ -56,6 +57,7 @@ while (1) { compile( verilator_flags2 => ["$secret_dir/secret.sv", + '--no-timing', "-LDFLAGS", "'-Wl,-rpath,$abs_secret_dir -L$abs_secret_dir -l$secret_prefix'"], xsim_flags2 => ["$secret_dir/secret.sv"], diff --git a/test_regress/t/t_lint_stmtdly_bad.out b/test_regress/t/t_lint_stmtdly_bad.out index f23238765..b47faf8ef 100644 --- a/test_regress/t/t_lint_stmtdly_bad.out +++ b/test_regress/t/t_lint_stmtdly_bad.out @@ -1,4 +1,4 @@ -%Warning-STMTDLY: t/t_lint_stmtdly_bad.v:10:8: Unsupported: Ignoring delay on this delayed statement. +%Warning-STMTDLY: t/t_lint_stmtdly_bad.v:10:8: Ignoring delay on this statement due to --no-timing : ... In instance t 10 | #100 $finish; | ^~~ diff --git a/test_regress/t/t_lint_stmtdly_bad.pl b/test_regress/t/t_lint_stmtdly_bad.pl index 548dab0af..bcb7303da 100755 --- a/test_regress/t/t_lint_stmtdly_bad.pl +++ b/test_regress/t/t_lint_stmtdly_bad.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); compile( + verilator_flags2 => ["--no-timing"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_lint_wait_bad.out b/test_regress/t/t_lint_wait_bad.out new file mode 100644 index 000000000..5ceecf26d --- /dev/null +++ b/test_regress/t/t_lint_wait_bad.out @@ -0,0 +1,12 @@ +%Warning-WAITCONST: t/t_timing_wait.v:47:13: Wait statement condition is constant + 47 | wait(0 < 1) $write("*-* All Finished *-*\n"); + | ^ + ... For warning description see https://verilator.org/warn/WAITCONST?v=latest + ... Use "/* verilator lint_off WAITCONST */" and lint_on around source to disable this message. +%Warning-WAITCONST: t/t_timing_wait.v:51:17: Wait statement condition is constant + 51 | initial wait(0) $stop; + | ^ +%Warning-WAITCONST: t/t_timing_wait.v:52:19: Wait statement condition is constant + 52 | initial wait(1 == 0) $stop; + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_lint_wait_bad.pl b/test_regress/t/t_lint_wait_bad.pl new file mode 100755 index 000000000..7500d4750 --- /dev/null +++ b/test_regress/t/t_lint_wait_bad.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +top_filename("t/t_timing_wait.v"); + +lint( + verilator_flags2 => ["--timing"], + expect_filename => $Self->{golden_filename}, + fails => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_math_signed5.pl b/test_regress/t/t_math_signed5.pl index b46d46042..f5debdb98 100755 --- a/test_regress/t/t_math_signed5.pl +++ b/test_regress/t/t_math_signed5.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( + verilator_flags2 => ['--no-timing'], ); execute( diff --git a/test_regress/t/t_math_signed5_timing.pl b/test_regress/t/t_math_signed5_timing.pl new file mode 100755 index 000000000..2365fef9a --- /dev/null +++ b/test_regress/t/t_math_signed5_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_math_signed5.v"); + + compile( + verilator_flags2 => ['--timing'], + timing_loop => 1, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_mem_slot.pl b/test_regress/t/t_mem_slot.pl index 19193485f..9e97f1977 100755 --- a/test_regress/t/t_mem_slot.pl +++ b/test_regress/t/t_mem_slot.pl @@ -13,7 +13,7 @@ scenarios(vlt_all => 1); compile( make_top_shell => 0, make_main => 0, - verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp"], + verilator_flags2 => ["--exe $Self->{t_dir}/$Self->{name}.cpp --no-timing"], ); execute( diff --git a/test_regress/t/t_mod_dollar$.pl b/test_regress/t/t_mod_dollar$.pl index 2d8172ca7..7441e7b09 100755 --- a/test_regress/t/t_mod_dollar$.pl +++ b/test_regress/t/t_mod_dollar$.pl @@ -14,7 +14,7 @@ scenarios(vlt => 1); # prefix properly using post-escaped identifiers run(cmd => ["../bin/verilator", "--cc", - "--Mdir obj_vlt/t_mod_dollar", + "--Mdir " . $Self->{obj_dir} . "/t_mod_dollar", "--exe --build --main", 't/t_mod_dollar$.v', ], diff --git a/test_regress/t/t_net_delay.out b/test_regress/t/t_net_delay.out new file mode 100644 index 000000000..1b79c6bac --- /dev/null +++ b/test_regress/t/t_net_delay.out @@ -0,0 +1,11 @@ +%Warning-ASSIGNDLY: t/t_net_delay.v:13:15: Ignoring timing control on this assignment/primitive due to --no-timing + : ... In instance t + 13 | wire[3:0] #4 val1 = cyc; + | ^ + ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest + ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. +%Warning-ASSIGNDLY: t/t_net_delay.v:17:12: Ignoring timing control on this assignment/primitive due to --no-timing + : ... In instance t + 17 | assign #4 val2 = cyc; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_intra_assign_event.pl b/test_regress/t/t_net_delay.pl similarity index 89% rename from test_regress/t/t_timing_intra_assign_event.pl rename to test_regress/t/t_net_delay.pl index d61820774..09ee1bbae 100755 --- a/test_regress/t/t_timing_intra_assign_event.pl +++ b/test_regress/t/t_net_delay.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); lint( - verilator_flags2 => ['-Wall -Wno-DECLFILENAME'], + verilator_flags2 => ['-Wall -Wno-DECLFILENAME --no-timing'], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_timing_net_delay.v b/test_regress/t/t_net_delay.v similarity index 91% rename from test_regress/t/t_timing_net_delay.v rename to test_regress/t/t_net_delay.v index aa125ca8c..c9b524005 100644 --- a/test_regress/t/t_timing_net_delay.v +++ b/test_regress/t/t_net_delay.v @@ -21,7 +21,7 @@ module t (/*AUTOARG*/ `ifdef TEST_VERBOSE $write("[%0t] cyc=%0d, val1=%0d, val2=%0d\n", $time, cyc, val1, val2); `endif - if (cyc >= 4 && val1 != cyc-1 && val2 != cyc-3) $stop; + if (cyc >= 7 && val1 != cyc-1 && val2 != cyc-7) $stop; if (cyc == 15) begin $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_net_delay_timing.pl b/test_regress/t/t_net_delay_timing.pl new file mode 100755 index 000000000..63a94fdbd --- /dev/null +++ b/test_regress/t/t_net_delay_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_net_delay.v"); + + compile( + timing_loop => 1, + verilator_flags2 => ["--timing"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_net_delay_timing_sc.pl b/test_regress/t/t_net_delay_timing_sc.pl new file mode 100755 index 000000000..1fd75cffb --- /dev/null +++ b/test_regress/t/t_net_delay_timing_sc.pl @@ -0,0 +1,34 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +$Self->{main_time_multiplier} = 2; + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +elsif (!$Self->have_sc) { + skip("No SystemC installed"); +} +else { + top_filename("t/t_net_delay.v"); + + compile( + verilator_flags2 => ["--sc --exe --timing --timescale 10ps/1ps"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_notiming.out b/test_regress/t/t_notiming.out new file mode 100644 index 000000000..cb2b6017c --- /dev/null +++ b/test_regress/t/t_notiming.out @@ -0,0 +1,33 @@ +%Warning-STMTDLY: t/t_notiming.v:12:9: Ignoring delay on this statement due to --no-timing + : ... In instance t + 12 | #1 + | ^ + ... For warning description see https://verilator.org/warn/STMTDLY?v=latest + ... Use "/* verilator lint_off STMTDLY */" and lint_on around source to disable this message. +%Error-NOTIMING: t/t_notiming.v:13:8: Fork statements require --timing + : ... In instance t + 13 | fork @e; @e; join; + | ^~~~ +%Error-NOTIMING: t/t_notiming.v:14:8: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 14 | @e + | ^ +%Error-NOTIMING: t/t_notiming.v:15:8: Wait statements require --timing + : ... In instance t + 15 | wait(x == 4) + | ^~~~ +%Error-NOTIMING: t/t_notiming.v:19:8: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 19 | @e + | ^ +%Warning-STMTDLY: t/t_notiming.v:26:13: Ignoring delay on this statement due to --no-timing + : ... In instance t + 26 | initial #1 ->e; + | ^ +%Warning-STMTDLY: t/t_notiming.v:27:13: Ignoring delay on this statement due to --no-timing + : ... In instance t + 27 | initial #2 $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_net_delay.pl b/test_regress/t/t_notiming.pl similarity index 90% rename from test_regress/t/t_timing_net_delay.pl rename to test_regress/t/t_notiming.pl index d61820774..5a683788e 100755 --- a/test_regress/t/t_timing_net_delay.pl +++ b/test_regress/t/t_notiming.pl @@ -10,8 +10,8 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -lint( - verilator_flags2 => ['-Wall -Wno-DECLFILENAME'], +compile( + verilator_flags2 => ["--no-timing"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_notiming.v b/test_regress/t/t_notiming.v new file mode 100644 index 000000000..8c822d4d2 --- /dev/null +++ b/test_regress/t/t_notiming.v @@ -0,0 +1,28 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event e; + + initial begin + int x; + #1 + fork @e; @e; join; + @e + wait(x == 4) + x = #1 8; + if (x != 8) $stop; + if ($time != 0) $stop; + @e + if (!e.triggered) $stop; + if ($time != 1) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + + initial #1 ->e; + initial #2 $stop; // timeout +endmodule diff --git a/test_regress/t/t_notiming_off.out b/test_regress/t/t_notiming_off.out new file mode 100644 index 000000000..fc269af87 --- /dev/null +++ b/test_regress/t/t_notiming_off.out @@ -0,0 +1,29 @@ +%Error-NOTIMING: t/t_timing_off.v:25:8: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 25 | @e1; + | ^ + ... For error description see https://verilator.org/warn/NOTIMING?v=latest +%Warning-STMTDLY: t/t_timing_off.v:33:13: Ignoring delay on this statement due to --no-timing + : ... In instance t + 33 | initial #2 ->e1; + | ^ + ... Use "/* verilator lint_off STMTDLY */" and lint_on around source to disable this message. +%Warning-STMTDLY: t/t_timing_off.v:37:13: Ignoring delay on this statement due to --no-timing + : ... In instance t + 37 | initial #3 $stop; + | ^ +%Warning-STMTDLY: t/t_timing_off.v:38:13: Ignoring delay on this statement due to --no-timing + : ... In instance t + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Error-NOTIMING: t/t_timing_off.v:38:15: Event control statement in this location requires --timing + : ... In instance t + : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Warning-STMTDLY: t/t_timing_off.v:38:26: Ignoring delay on this statement due to --no-timing + : ... In instance t + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_notiming_off.pl b/test_regress/t/t_notiming_off.pl new file mode 100755 index 000000000..cca2c7651 --- /dev/null +++ b/test_regress/t/t_notiming_off.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_timing_off.v"); + +compile( + verilator_flags2 => ["--no-timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_order.pl b/test_regress/t/t_order.pl index b46d46042..800b5bdc4 100755 --- a/test_regress/t/t_order.pl +++ b/test_regress/t/t_order.pl @@ -10,7 +10,10 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); +$Self->{main_time_multiplier} = 1e-8 / 1e-9; + compile( + verilator_flags2 => ["--timescale 10ns/1ns --no-timing"], ); execute( diff --git a/test_regress/t/t_order_timing.pl b/test_regress/t/t_order_timing.pl new file mode 100755 index 000000000..ee807ce82 --- /dev/null +++ b/test_regress/t/t_order_timing.pl @@ -0,0 +1,32 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +$Self->{main_time_multiplier} = 1e-8 / 1e-9; + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_order.v"); + + compile( + timing_loop => 1, + verilator_flags2 => ["--timescale 10ns/1ns --timing"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_package_ddecl.pl b/test_regress/t/t_package_ddecl.pl index b46d46042..f5debdb98 100755 --- a/test_regress/t/t_package_ddecl.pl +++ b/test_regress/t/t_package_ddecl.pl @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( + verilator_flags2 => ['--no-timing'], ); execute( diff --git a/test_regress/t/t_package_ddecl_timing.pl b/test_regress/t/t_package_ddecl_timing.pl new file mode 100755 index 000000000..d471a7adb --- /dev/null +++ b/test_regress/t/t_package_ddecl_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_package_ddecl.v"); + + compile( + verilator_flags2 => ['--timing'], + timing_loop => 1, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_parse_delay.pl b/test_regress/t/t_parse_delay.pl index b46d46042..40a477a35 100755 --- a/test_regress/t/t_parse_delay.pl +++ b/test_regress/t/t_parse_delay.pl @@ -11,10 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - ); - -execute( - check_finished => 1, + verilator_flags2 => ['--no-timing'], ); ok(1); diff --git a/test_regress/t/t_parse_delay_timing.pl b/test_regress/t/t_parse_delay_timing.pl new file mode 100755 index 000000000..f4847bf8c --- /dev/null +++ b/test_regress/t/t_parse_delay_timing.pl @@ -0,0 +1,25 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_parse_delay.v"); + + compile( + verilator_flags2 => ['--timing'], + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_always.pl b/test_regress/t/t_timing_always.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_always.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_always.v b/test_regress/t/t_timing_always.v new file mode 100644 index 000000000..e92bc2539 --- /dev/null +++ b/test_regress/t/t_timing_always.v @@ -0,0 +1,43 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +module t; + logic clk = 0; + always #3 clk = ~clk; + + logic flag_a; + logic flag_b; + always @(posedge clk) + begin + `WRITE_VERBOSE(("[%0t] b <= 0\n", $time)); + flag_b <= 1'b0; + #2 + `WRITE_VERBOSE(("[%0t] a <= 1\n", $time)); + flag_a <= 1'b1; + #2 + `WRITE_VERBOSE(("[%0t] b <= 1\n", $time)); + flag_b <= 1'b1; + end + always @(flag_a) if ($time > 0) + begin + #1 + `WRITE_VERBOSE(("[%0t] Checking if b == 0\n", $time)); + if (flag_b !== 1'b0) $stop; + #2 + `WRITE_VERBOSE(("[%0t] Checking if b == 1\n", $time)); + if (flag_b !== 1'b1) $stop; + #10 + $write("*-* All Finished *-*\n"); + $finish; + end + initial #20 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_class.pl b/test_regress/t/t_timing_class.pl new file mode 100755 index 000000000..c469d3de3 --- /dev/null +++ b/test_regress/t/t_timing_class.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_class.v b/test_regress/t/t_timing_class.v new file mode 100644 index 000000000..4b8480ced --- /dev/null +++ b/test_regress/t/t_timing_class.v @@ -0,0 +1,166 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +module t; + // ============================================= + // EVENTS + class EventClass; + event e; + + task sleep; /* @e; */ endtask // Unsupported + task wake; ->e; endtask + endclass + + EventClass ec = new; + int event_trig_count = 0; + + initial begin + @ec.e; + ec.sleep; + end + + initial #25 ec.wake; + initial #50 ->ec.e; + + always @ec.e begin + event_trig_count++; + `WRITE_VERBOSE(("Event in class triggered at time %0t!\n", $time)); + end + + // ============================================= + // DELAYS + virtual class DelayClass; + pure virtual task do_delay; + pure virtual task do_sth_else; + endclass + + `ifdef TEST_VERBOSE + `define DELAY_CLASS(dt) \ + class Delay``dt extends DelayClass; \ + virtual task do_delay; \ + $write("Starting a #%0d delay\n", dt); \ + #dt \ + $write("Ended a #%0d delay\n", dt); \ + endtask \ + virtual task do_sth_else; \ + $write("Task with no delay (in Delay%0d)\n", dt); \ + endtask \ + endclass + `else + `define DELAY_CLASS(dt) \ + class Delay``dt extends DelayClass; \ + virtual task do_delay; \ + #dt; \ + endtask \ + virtual task do_sth_else; \ + endtask \ + endclass + `endif + + `DELAY_CLASS(10); + `DELAY_CLASS(20); + `DELAY_CLASS(40); + + class NoDelay extends DelayClass; + virtual task do_delay; + `WRITE_VERBOSE(("Task with no delay\n")); + endtask + virtual task do_sth_else; + `WRITE_VERBOSE(("Task with no delay (in NoDelay)\n")); + endtask + endclass + + class AssignDelayClass; + logic x; + logic y; + task do_assign; + y = #10 x; + endtask + endclass + + initial begin + DelayClass dc; + Delay10 d10 = new; + Delay20 d20 = new; + Delay40 d40 = new; + NoDelay dNo = new; + AssignDelayClass dAsgn = new; + `WRITE_VERBOSE(("I'm at time %0t\n", $time)); + dc = d10; + dc.do_delay; + dc.do_sth_else; + `WRITE_VERBOSE(("I'm at time %0t\n", $time)); + if ($time != 10) $stop; + dc = d20; + dc.do_delay; + dc.do_sth_else; + `WRITE_VERBOSE(("I'm at time %0t\n", $time)); + if ($time != 30) $stop; + dc = d40; + dc.do_delay; + dc.do_sth_else; + `WRITE_VERBOSE(("I'm at time %0t\n", $time)); + if ($time != 70) $stop; + dc = dNo; + dc.do_delay; + dc.do_sth_else; + `WRITE_VERBOSE(("I'm at time %0t\n", $time)); + dAsgn.x = 1; + dAsgn.y = 0; + fork #5 dAsgn.x = 0; join_none + dAsgn.do_assign; + if ($time != 80) $stop; + if (event_trig_count != 2) $stop; + if (dAsgn.y != 1) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + + // ============================================= + // FORKS + class ForkDelayClass; + task do_delay; #40; endtask + endclass + + class ForkClass; + int done = 0; + task do_fork(); + ForkDelayClass d; + fork + begin + #10 done++; + `WRITE_VERBOSE(("Forked process %0d ending at time %0t\n", done, $time)); + end + begin + #20 done++; + `WRITE_VERBOSE(("Forked process %0d ending at time %0t\n", done, $time)); + d = new; + end + begin + #30 d.do_delay; + done++; + `WRITE_VERBOSE(("Forked process %0d ending at time %0t\n", done, $time)); + end + join + done++; + `WRITE_VERBOSE(("All forked processes ended at time %0t\n", $time)); + endtask + endclass + + initial begin + ForkClass fc = new; + fc.do_fork; + if (fc.done != 4 || $time != 70) $stop; + end + + initial #81 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_class_unsup.out b/test_regress/t/t_timing_class_unsup.out new file mode 100644 index 000000000..0cb96600b --- /dev/null +++ b/test_regress/t/t_timing_class_unsup.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_timing_class_unsup.v:10:17: Unsupported: event controls in methods + : ... In instance $unit::EventClass + 10 | task sleep; @e; endtask + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_delay_func_bad.pl b/test_regress/t/t_timing_class_unsup.pl similarity index 82% rename from test_regress/t/t_delay_func_bad.pl rename to test_regress/t/t_timing_class_unsup.pl index 27159da5b..8ab3c0995 100755 --- a/test_regress/t/t_delay_func_bad.pl +++ b/test_regress/t/t_timing_class_unsup.pl @@ -2,7 +2,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2019 by Wilson Snyder. This program is free software; you +# Copyright 2022 by Antmicro Ltd. This program is free software; you # can redistribute it and/or modify it under the terms of either the GNU # Lesser General Public License Version 3 or the Perl Artistic License # Version 2.0. @@ -11,6 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(linter => 1); lint( + verilator_flags2 => ["--timing"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_timing_class_unsup.v b/test_regress/t/t_timing_class_unsup.v new file mode 100644 index 000000000..01795aa00 --- /dev/null +++ b/test_regress/t/t_timing_class_unsup.v @@ -0,0 +1,12 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +class EventClass; + event e; + + task sleep; @e; endtask + task wake; ->e; endtask +endclass diff --git a/test_regress/t/t_timing_clkgen1.pl b/test_regress/t/t_timing_clkgen1.pl index e1998178c..aa7288ef4 100755 --- a/test_regress/t/t_timing_clkgen1.pl +++ b/test_regress/t/t_timing_clkgen1.pl @@ -2,7 +2,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2019 by Wilson Snyder. This program is free software; you +# Copyright 2022 by Antmicro Ltd. This program is free software; you # can redistribute it and/or modify it under the terms of either the GNU # Lesser General Public License Version 3 or the Perl Artistic License # Version 2.0. @@ -10,20 +10,19 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -$Self->{vlt_all} and unsupported("Verilator unsupported, clocking"); +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing -Wno-MINTYPMAXDLY"], + make_main => 0, + ); -compile( - #verilator_flags2 => ['--exe --build --main --timing'], # Unsupported - verilator_flags2 => ['--exe --build --main --bbox-unsup -Wno-STMTDLY -Wno-INITIALDLY'], - verilator_make_cmake => 0, - verilator_make_gmake => 0, - make_main => 0, - make_top => 1, - ); - -execute( - check_finished => 1, - ); + execute( + check_finished => 1, + ); +} ok(1); 1; diff --git a/test_regress/t/t_timing_clkgen2.pl b/test_regress/t/t_timing_clkgen2.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_clkgen2.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_clkgen2.v b/test_regress/t/t_timing_clkgen2.v new file mode 100644 index 000000000..c1ec90fb1 --- /dev/null +++ b/test_regress/t/t_timing_clkgen2.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +module t; + logic clk = 0; + logic clk_inv; + int cnt1 = 0; + int cnt2 = 0; + + always #4 clk = ~clk; + always @(negedge clk) begin + cnt1++; + `WRITE_VERBOSE(("[%0t] NEG clk (%b)\n", $time, clk)); + end + always @(posedge clk) begin + cnt1++; + `WRITE_VERBOSE(("[%0t] POS clk (%b)\n", $time, clk)); + end + + assign #2 clk_inv = ~clk; + initial forever begin + @(posedge clk_inv) cnt2++; + `WRITE_VERBOSE(("[%0t] POS clk_inv (%b)\n", $time, clk_inv)); + @(negedge clk_inv) cnt2++; + `WRITE_VERBOSE(("[%0t] NEG clk_inv (%b)\n", $time, clk_inv)); + end + + initial #41 begin + if (cnt1 != 10 && cnt2 != 10) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_clkgen3.pl b/test_regress/t/t_timing_clkgen3.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_clkgen3.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_clkgen3.v b/test_regress/t/t_timing_clkgen3.v new file mode 100644 index 000000000..28da638a3 --- /dev/null +++ b/test_regress/t/t_timing_clkgen3.v @@ -0,0 +1,46 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`timescale 10ns / 1ns + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +module t; + logic clk = 0; + logic clk_copy; + int cyc = 0; + int cnt1 = 0; + int cnt2 = 0; + + initial forever #1 clk = ~clk; + + always @(negedge clk) begin + #0.75 cnt1++; + `WRITE_VERBOSE(("[%0t] NEG clk (%b)\n", $time, clk)); + end + + always @(posedge clk) begin + cyc <= cyc + 1; + #0.5 `WRITE_VERBOSE(("[%0t] POS clk (%b)\n", $time, clk)); + if (cyc == 5) begin + if (cnt1 != 4 && cnt2 != 9) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + + assign clk_copy = clk; + always @(posedge clk_copy or negedge clk_copy) begin + #0.25 cnt2++; + `WRITE_VERBOSE(("[%0t] POS/NEG clk_copy (%b)\n", $time, clk_copy)); + end + + initial #100 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_clkgen_sc.pl b/test_regress/t/t_timing_clkgen_sc.pl new file mode 100755 index 000000000..a5fe43850 --- /dev/null +++ b/test_regress/t/t_timing_clkgen_sc.pl @@ -0,0 +1,32 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +elsif (!$Self->have_sc) { + skip("No SystemC installed"); +} +else { + top_filename("t/t_timing_clkgen2.v"); + + compile( + verilator_flags2 => ["--sc --exe --timing --timescale 10ps/1ps"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_clkgen_unsup.out b/test_regress/t/t_timing_clkgen_unsup.out new file mode 100644 index 000000000..8ee56209f --- /dev/null +++ b/test_regress/t/t_timing_clkgen_unsup.out @@ -0,0 +1,6 @@ +%Warning-MINTYPMAXDLY: t/t_timing_clkgen1.v:9:13: Unsupported: minimum/typical/maximum delay expressions. Using the typical delay + 9 | #(8.0:5:3) clk = 1; + | ^ + ... For warning description see https://verilator.org/warn/MINTYPMAXDLY?v=latest + ... Use "/* verilator lint_off MINTYPMAXDLY */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_timing_clkgen_unsup.pl b/test_regress/t/t_timing_clkgen_unsup.pl new file mode 100755 index 000000000..b380d0484 --- /dev/null +++ b/test_regress/t/t_timing_clkgen_unsup.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +top_filename("t/t_timing_clkgen1.v"); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_cmake.pl b/test_regress/t/t_timing_cmake.pl new file mode 100755 index 000000000..acd0f0d36 --- /dev/null +++ b/test_regress/t/t_timing_cmake.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} elsif (!$Self->have_cmake) { + skip("cmake is not installed"); +} else { + top_filename("t/t_timing_events.v"); + + compile( + verilator_flags2 => ["--timescale 10ns/1ns --main --timing"], + verilator_make_gmake => 0, + verilator_make_cmake => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_debug1.out b/test_regress/t/t_timing_debug1.out new file mode 100644 index 000000000..0ad01cbe5 --- /dev/null +++ b/test_regress/t/t_timing_debug1.out @@ -0,0 +1,1909 @@ +-V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. +-V{t#,#}+ Vt_timing_debug1___024root___ctor_var_reset +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Initial +-V{t#,#}+ Vt_timing_debug1___024root___eval_static +-V{t#,#}+ Vt_timing_debug1___024root___eval_static__TOP +-V{t#,#}+ Vt_timing_debug1___024root___eval_initial +-V{t#,#}+ Vt_timing_debug1___024root___eval_initial__TOP__0 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_initial__TOP__1 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_initial__TOP__2 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_initial__TOP__3 +-V{t#,#}+ Vt_timing_debug1___024root___eval_settle +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__stl +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__stl +-V{t#,#} 'stl' region trigger index 0 is active: Internal 'stl' trigger - first iteration +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'stl' region trigger index 2 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#} 'stl' region trigger index 3 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___eval_stl +-V{t#,#}+ Vt_timing_debug1___024root___stl_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___stl_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___stl_sequent__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___stl_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___stl_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__stl +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__stl +-V{t#,#} No triggers active +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#} 'act' region trigger index 2 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 3: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 3: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:18 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 6: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 7: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 7: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 9: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 9: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 11: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 12: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 13: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 5 is active: @(posedge t.clk2) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Resuming processes waiting for @(posedge t.clk2) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 12: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 12: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 13: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 13: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 15: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 15: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:18 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 18: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 19: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 19: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 21: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 21: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 22: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 25: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 24: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 24: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 25: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 25: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 27: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 27: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 30: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 31: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 31: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 33: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#} 'act' region trigger index 5 is active: @(posedge t.clk2) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Resuming processes waiting for @(posedge t.clk2) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 34: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 36: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Awaiting time 37: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 36: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 37: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 37: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 39: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 39: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:18 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 42: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 43: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 43: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 45: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 44: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 45: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 45: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 48: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 49: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 49: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 51: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 51: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 54: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 55: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 57: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 5 is active: @(posedge t.clk2) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Resuming processes waiting for @(posedge t.clk2) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 56: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 57: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 57: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 60: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 61: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 61: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 63: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 63: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 67: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 67: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 69: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 69: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 72: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 73: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 73: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 75: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 75: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming processes waiting for @(posedge t.clk1) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___nba_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 77: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 79: Process waiting at t/t_timing_sched.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:17 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 5 is active: @(posedge t.clk2) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Ready processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Resuming processes waiting for @(posedge t.clk2) +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:18 +-V{t#,#} Suspending process waiting for @(posedge t.clk1) at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__1 +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([hybrid] t.c1) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk1): +-V{t#,#} - Process waiting at t/t_timing_sched.v:18 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step +-V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug1___024root___eval +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:48 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 79: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Awaiting time 88: Process waiting at t/t_timing_sched.v:13 +-V{t#,#} Awaiting time 78: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:46 +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:13 +*-* All Finished *-* +-V{t#,#} Resuming: Process waiting at t/t_timing_sched.v:10 +-V{t#,#} Suspending process waiting for @(posedge t.clk2) at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#} Committing processes waiting for @(posedge t.clk2): +-V{t#,#} - Process waiting at t/t_timing_sched.v:46 +-V{t#,#}+ Vt_timing_debug1___024root___timing_resume +-V{t#,#}+ Vt_timing_debug1___024root___eval_act +-V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_nba +-V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 +-V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug1___024root___timing_commit +-V{t#,#}+ Vt_timing_debug1___024root___eval_final diff --git a/test_regress/t/t_timing_debug1.pl b/test_regress/t/t_timing_debug1.pl new file mode 100755 index 000000000..0d4523725 --- /dev/null +++ b/test_regress/t/t_timing_debug1.pl @@ -0,0 +1,35 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_timing_sched.v"); + + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + all_run_flags => ["+verilator+debug"], + check_finished => 1, + ); + + if (!$Self->{vltmt}) { # vltmt output may vary between thread exec order + files_identical("$Self->{obj_dir}/vlt_sim.log", $Self->{golden_filename}, "logfile"); + } +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out new file mode 100644 index 000000000..a206663b2 --- /dev/null +++ b/test_regress/t/t_timing_debug2.out @@ -0,0 +1,616 @@ +-V{t#,#}- Verilated::debug is on. Message prefix indicates {,}. +-V{t#,#}+ Vt_timing_debug2___024root___ctor_var_reset +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Initial +-V{t#,#}+ Vt_timing_debug2___024root___eval_static +-V{t#,#}+ Vt_timing_debug2___024root___eval_initial +-V{t#,#}+ Vt_timing_debug2___024root___eval_initial__TOP__0 +[0] fork..join process 4 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__2 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__3 +-V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:13-V{t0,14}+ Vt_timing_debug2___024root___eval_initial__TOP__1 +-V{t#,#}+ Vt_timing_debug2___024root___eval_settle +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 2: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 8: Process waiting at t/t_timing_fork_join.v:16 +-V{t#,#} Awaiting time 4: Process waiting at t/t_timing_fork_join.v:17 +-V{t#,#} Awaiting time 16: Process waiting at t/t_timing_fork_join.v:19 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:79 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:79 +[2] fork..join process 3 +-V{t#,#} Process forked at t/t_timing_fork_join.v:17 finished-V{t0,48}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 4: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 8: Process waiting at t/t_timing_fork_join.v:16 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:17 +-V{t#,#} Awaiting time 16: Process waiting at t/t_timing_fork_join.v:19 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:19 +[4] fork..join process 2 +-V{t#,#} Process forked at t/t_timing_fork_join.v:16 finished-V{t0,75}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 8: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 16: Process waiting at t/t_timing_fork_join.v:16 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:17 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:17 +[8] fork..join process 1 +-V{t#,#} Process forked at t/t_timing_fork_join.v:15 finished-V{t0,101}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 16: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:16 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:16 +[16] fork in fork starts +[16] fork..join process 8 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__2 +-V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:21-V{t0,129}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 20: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 24: Process waiting at t/t_timing_fork_join.v:22 +-V{t#,#} Awaiting time 32: Process waiting at t/t_timing_fork_join.v:23 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:24 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:24 +[20] fork..join process 7 +-V{t#,#} Process forked at t/t_timing_fork_join.v:24 finished-V{t0,156}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 24: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:22 +-V{t#,#} Awaiting time 32: Process waiting at t/t_timing_fork_join.v:23 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:23 +[24] fork..join process 6 +-V{t#,#} Process forked at t/t_timing_fork_join.v:23 finished-V{t0,182}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 32: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:22 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:22 +[32] fork..join process 5 +-V{t#,#} Process forked at t/t_timing_fork_join.v:22 finished-V{t0,207} Resuming: Process waiting at (null):0 +[32] fork..join in fork ends +-V{t#,#} Process forked at t/t_timing_fork_join.v:19 finished-V{t0,209} Resuming: Process waiting at (null):0 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 64: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:30 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:30 +[64] main process +fork..join_any process 2 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 +-V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:33 +back in main process +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#} Committing processes waiting for @([event] t.e1): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:33 +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 65: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:40 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:40 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([event] t.e1) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Ready processes waiting for @([event] t.e1): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:33 +-V{t#,#} Resuming processes waiting for @([event] t.e1) +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:33 +fork..join_any process 1 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([event] t.e1) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} No ready processes waiting for @([event] t.e1) +-V{t#,#} Resuming processes waiting for @([event] t.e1) +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 66: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:41 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:41 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 +-V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:44 +-V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:41-V{t0,308}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#} Committing processes waiting for @([event] t.e1): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:44 +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 68: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:42 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:42 +fork..join_any process 1 +-V{t#,#} Process forked at t/t_timing_fork_join.v:42 finished-V{t0,335} Resuming: Process waiting at (null):0 +back in main process +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 69: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:50 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:50 +-V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:51 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([event] t.e1) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Ready processes waiting for @([event] t.e1): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:44 +-V{t#,#} Uncommitted processes waiting for @([event] t.e1): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:51 +-V{t#,#} Resuming processes waiting for @([event] t.e1) +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:44 +fork..join_any process 2 +-V{t#,#} Process forked at t/t_timing_fork_join.v:43 finished-V{t0,374} Committing processes waiting for @([event] t.e1): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:51 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([event] t.e1) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Ready processes waiting for @([event] t.e1): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:51 +-V{t#,#} Resuming processes waiting for @([event] t.e1) +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:51 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 +-V{t#,#} Suspending process waiting for @([event] t.e2) at t/t_timing_fork_join.v:62 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__2 +-V{t#,#} Suspending process waiting for @([event] t.e3) at t/t_timing_fork_join.v:68 +in main process +-V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:75 +-V{t#,#} Committing processes waiting for @([event] t.e1): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:75 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#} Committing processes waiting for @([event] t.e2): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:62 +-V{t#,#} Committing processes waiting for @([event] t.e3): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:68 +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 70: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:56 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:56 +fork..join_none process 1 +-V{t#,#} Suspending process waiting for @([event] t.e2) at t/t_timing_fork_join.v:58 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([event] t.e2) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Ready processes waiting for @([event] t.e2): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:62 +-V{t#,#} Uncommitted processes waiting for @([event] t.e2): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:58 +-V{t#,#} Resuming processes waiting for @([event] t.e2) +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:62 +fork..join_none process 2 +-V{t#,#} Committing processes waiting for @([event] t.e2): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:58 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 71: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:63 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:63 +-V{t#,#} Suspending process waiting for @([event] t.e3) at t/t_timing_fork_join.v:64 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @([event] t.e3) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Ready processes waiting for @([event] t.e3): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:68 +-V{t#,#} Uncommitted processes waiting for @([event] t.e3): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:64 +-V{t#,#} Resuming processes waiting for @([event] t.e3) +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:68 +fork..join_none process 3 +-V{t#,#} Committing processes waiting for @([event] t.e3): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:64 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 72: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:69 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:69 +-V{t#,#} Suspending process waiting for @([event] t.e3) at t/t_timing_fork_join.v:70 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @([event] t.e3) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Ready processes waiting for @([event] t.e3): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:64 +-V{t#,#} Uncommitted processes waiting for @([event] t.e3): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:70 +-V{t#,#} Resuming processes waiting for @([event] t.e3) +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:64 +fork..join_none process 2 again +-V{t#,#} Committing processes waiting for @([event] t.e3): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:70 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 73: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:65 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:65 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 2 is active: @([event] t.e2) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Ready processes waiting for @([event] t.e2): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:58 +-V{t#,#} Resuming processes waiting for @([event] t.e2) +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:58 +fork..join_none process 1 again +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step +-V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions +-V{t#,#}+ Eval +-V{t#,#}+ Vt_timing_debug2___024root___eval +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Delayed processes: +-V{t#,#} Awaiting time 74: Process waiting at t/t_timing_fork_join.v:15 +-V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:59 +-V{t#,#} Resuming delayed processes +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:59 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 3 is active: @([event] t.e3) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Ready processes waiting for @([event] t.e3): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:70 +-V{t#,#} Resuming processes waiting for @([event] t.e3) +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:70 +fork..join_none process 3 again +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} 'act' region trigger index 1 is active: @([event] t.e1) +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___timing_resume +-V{t#,#} Ready processes waiting for @([event] t.e1): +-V{t#,#} - Process waiting at t/t_timing_fork_join.v:75 +-V{t#,#} Resuming processes waiting for @([event] t.e1) +-V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:75 +*-* All Finished *-* +-V{t#,#}+ Vt_timing_debug2___024root___eval_act +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_nba +-V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act +-V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act +-V{t#,#} No triggers active +-V{t#,#}+ Vt_timing_debug2___024root___timing_commit +-V{t#,#}+ Vt_timing_debug2___024root___eval_final diff --git a/test_regress/t/t_timing_debug2.pl b/test_regress/t/t_timing_debug2.pl new file mode 100755 index 000000000..c5b6b9286 --- /dev/null +++ b/test_regress/t/t_timing_debug2.pl @@ -0,0 +1,35 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt_all => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_timing_fork_join.v"); + + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + all_run_flags => ["+verilator+debug"], + check_finished => 1, + ); + + if (!$Self->{vltmt}) { # vltmt output may vary between thread exec order + files_identical("$Self->{obj_dir}/vlt_sim.log", $Self->{golden_filename}, "logfile"); + } +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_delay_callstack.pl b/test_regress/t/t_timing_delay_callstack.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_delay_callstack.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_delay_callstack.v b/test_regress/t/t_timing_delay_callstack.v new file mode 100644 index 000000000..995f25d87 --- /dev/null +++ b/test_regress/t/t_timing_delay_callstack.v @@ -0,0 +1,61 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + int counter = 0; + + // As Verilator doesn't support recursive calls, let's use macros to + // generate tasks for a deep call stack + `ifdef TEST_VERBOSE + `define DEEP_STACK_DELAY_END(i) \ + task delay``i; \ + counter++; \ + $write("[%0t] at depth %0d\n", $time, i); \ + counter++; \ + endtask + + `define DEEP_STACK_DELAY(i, j) \ + task delay``i; \ + $write("[%0t] entering depth %0d\n", $time, i); \ + #1 delay``j; \ + counter++; \ + #1 $write("[%0t] leaving depth %0d\n", $time, i); \ + counter++; \ + endtask + `else + `define DEEP_STACK_DELAY_END(i) \ + task delay``i; \ + counter += 2; \ + endtask + + `define DEEP_STACK_DELAY(i, j) \ + task delay``i; \ + #1 delay``j; \ + counter++; \ + #1; \ + counter++; \ + endtask + `endif + + `DEEP_STACK_DELAY_END(10); + `DEEP_STACK_DELAY(9, 10); + `DEEP_STACK_DELAY(8, 9); + `DEEP_STACK_DELAY(7, 8); + `DEEP_STACK_DELAY(6, 7); + `DEEP_STACK_DELAY(5, 6); + `DEEP_STACK_DELAY(4, 5); + `DEEP_STACK_DELAY(3, 4); + `DEEP_STACK_DELAY(2, 3); + `DEEP_STACK_DELAY(1, 2); + + initial begin + delay1; + if ($time != 9*2) $stop; + if (counter != 10*2) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_events.pl b/test_regress/t/t_timing_events.pl new file mode 100755 index 000000000..c469d3de3 --- /dev/null +++ b/test_regress/t/t_timing_events.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_events.v b/test_regress/t/t_timing_events.v new file mode 100644 index 000000000..583c66808 --- /dev/null +++ b/test_regress/t/t_timing_events.v @@ -0,0 +1,32 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event e1; + event e2; + event e3; + initial forever begin + #2 + ->e1; + #2 + ->e2; + #2 + ->e3; + end + initial begin + for (int i = 0; i < 10; i++) begin + @(e1, e2, e3) + if (!e1.triggered && !e2.triggered && !e3.triggered) $stop; +`ifdef TEST_VERBOSE + $write("got event %0d\n", i); +`endif + end + $write("*-* All Finished *-*\n"); + $finish; + end + + initial #21 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_fork_comb.pl b/test_regress/t/t_timing_fork_comb.pl new file mode 100755 index 000000000..d96687ccb --- /dev/null +++ b/test_regress/t/t_timing_fork_comb.pl @@ -0,0 +1,36 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + # Should convert the first always into combo and detect cycle + compile( + fails => 1, + verilator_flags2 => ["--timing"], + expect => + '%Warning-UNOPTFLAT: t/t_timing_fork_comb.v:\d+:\d+: Signal unoptimizable: Circular combinational logic:' + ); + + compile( + verilator_flags2 => ["--exe --main --timing -Wno-UNOPTFLAT"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_fork_comb.v b/test_regress/t/t_timing_fork_comb.v new file mode 100644 index 000000000..9463698c5 --- /dev/null +++ b/test_regress/t/t_timing_fork_comb.v @@ -0,0 +1,58 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk = 0; + + assign #5 clk = ~clk; + + int a = 0; + always @(posedge clk) begin + a <= a + 1; +`ifdef TEST_VERBOSE + $display("a=%0d, b=%0d, c=%0d, d=%0d, e=%0d, f=%0d, v=%b", a, b, c, d, e, f, v); +`endif + end + + int b = 0, c = 0, d = 0, e = 0, f = 0; + always @a begin + b = a << 1; + fork + #10 d = b + c; + e = c + d; + #5 f = d + e; + join_none + c = a + b; + end + + logic[5:0] v; + always @a begin + v[0] = a[0]; + fork + begin + v[1] = a[1]; + #5 v[2] = a[2]; + end + #10 v[3] = a[3]; + join_none + v[4] = a[4]; + end + + initial #100 begin +`ifdef TEST_VERBOSE + $display("a=%0d, b=%0d, c=%0d, d=%0d, e=%0d, f=%0d, v=%b", a, b, c, d, e, f, v); +`endif + if (a != 10) $stop; + if (b != 20) $stop; + if (c != 30) $stop; + if (d != 50) $stop; + if (e != 75) $stop; + if (f != 125) $stop; + if (v != 'b001010) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_fork_join.out b/test_regress/t/t_timing_fork_join.out new file mode 100644 index 000000000..46f216b1e --- /dev/null +++ b/test_regress/t/t_timing_fork_join.out @@ -0,0 +1,25 @@ +[0] fork..join process 4 +[2] fork..join process 3 +[4] fork..join process 2 +[8] fork..join process 1 +[16] fork in fork starts +[16] fork..join process 8 +[20] fork..join process 7 +[24] fork..join process 6 +[32] fork..join process 5 +[32] fork..join in fork ends +[64] main process +fork..join_any process 2 +back in main process +fork..join_any process 1 +fork..join_any process 1 +back in main process +fork..join_any process 2 +in main process +fork..join_none process 1 +fork..join_none process 2 +fork..join_none process 3 +fork..join_none process 2 again +fork..join_none process 1 again +fork..join_none process 3 again +*-* All Finished *-* diff --git a/test_regress/t/t_timing_fork_join.pl b/test_regress/t/t_timing_fork_join.pl new file mode 100755 index 000000000..4ab3c9652 --- /dev/null +++ b/test_regress/t/t_timing_fork_join.pl @@ -0,0 +1,29 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_fork_join.v b/test_regress/t/t_timing_fork_join.v new file mode 100644 index 000000000..fb5b6c60d --- /dev/null +++ b/test_regress/t/t_timing_fork_join.v @@ -0,0 +1,80 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event e1; + event e2; + event e3; + + initial begin + fork + begin /*empty*/ end + #8 $write("[%0t] fork..join process 1\n", $time); + #4 $write("[%0t] fork..join process 2\n", $time); + #2 $write("[%0t] fork..join process 3\n", $time); + $write("[%0t] fork..join process 4\n", $time); + begin : fork_in_fork #16 + $write("[%0t] fork in fork starts\n", $time); + fork + #16 $write("[%0t] fork..join process 5\n", $time); + #8 $write("[%0t] fork..join process 6\n", $time); + #4 $write("[%0t] fork..join process 7\n", $time); + $write("[%0t] fork..join process 8\n", $time); + join + $write("[%0t] fork..join in fork ends\n", $time); + end + join + #32 $write("[%0t] main process\n", $time); + fork + begin + @e1; + $write("fork..join_any process 1\n"); + ->e1; + end + $write("fork..join_any process 2\n"); + join_any + $write("back in main process\n"); + #1 ->e1; + #1 fork + #2 $write("fork..join_any process 1\n"); + begin + @e1; + $write("fork..join_any process 2\n"); + ->e1; + end + join_any + $write("back in main process\n"); + #1 ->e1; + @e1; + // Order of triggering: + // p1->e2 ==> p2->e3 ==> p3->e3 ==> p2->e2 ==> p1->e3 ==> p3->e1 + fork + begin + #1 $write("fork..join_none process 1\n"); + ->e2; + @e2 $write("fork..join_none process 1 again\n"); + #1 ->e3; + end + begin + @e2 $write("fork..join_none process 2\n"); + #1 ->e3; + @e3 $write("fork..join_none process 2 again\n"); + #1 ->e2; + end + begin + @e3 $write("fork..join_none process 3\n"); + #1 ->e3; + @e3 $write("fork..join_none process 3 again\n"); + ->e1; + end + join_none + $write("in main process\n"); + @e1; + $write("*-* All Finished *-*\n"); + $finish; + end + initial #100 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_fork_many.pl b/test_regress/t/t_timing_fork_many.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_fork_many.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_fork_many.v b/test_regress/t/t_timing_fork_many.v new file mode 100644 index 000000000..555865436 --- /dev/null +++ b/test_regress/t/t_timing_fork_many.v @@ -0,0 +1,43 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + int counter = 0; + + // As Verilator doesn't support recursive calls, let's use macros to generate tasks + `define FORK2_END(i) \ + task fork2_``i; \ + #1 counter++; \ + endtask + + `define FORK2(i, j) \ + task fork2_``i; \ + fork \ + #1 fork2_``j; \ + #1 fork2_``j; \ + join \ + endtask + + `FORK2_END(0); + `FORK2(1, 0); + `FORK2(2, 1); + `FORK2(3, 2); + `FORK2(4, 3); + `FORK2(5, 4); + `FORK2(6, 5); + `FORK2(7, 6); + `FORK2(8, 7); + + initial begin + fork2_8; +`ifdef TEST_VERBOSE + $write("[%0t] process counter == %0d\n", $time, counter); +`endif + if (counter != 1 << 8) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_fork_unsup.out b/test_regress/t/t_timing_fork_unsup.out new file mode 100644 index 000000000..39713d51f --- /dev/null +++ b/test_regress/t/t_timing_fork_unsup.out @@ -0,0 +1,34 @@ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:12:12: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 12 | #6 x = 4; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:13:12: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 13 | #2 x++; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:14:16: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 14 | x = #4 x * 3; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:14:9: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 14 | x = #4 x * 3; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:16:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 16 | #1 if (x != 1) $stop; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:17:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 17 | #2 if (x != 2) $stop; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:18:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 18 | #2 if (x != 3) $stop; + | ^ +%Error-UNSUPPORTED: t/t_timing_fork_unsup.v:19:18: Unsupported: variable local to a forking process accessed in a fork..join_any or fork..join_none + : ... In instance t::C + 19 | #2 if (x != 4) $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_fork_unsup.pl b/test_regress/t/t_timing_fork_unsup.pl new file mode 100755 index 000000000..8ab3c0995 --- /dev/null +++ b/test_regress/t/t_timing_fork_unsup.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_fork_unsup.v b/test_regress/t/t_timing_fork_unsup.v new file mode 100644 index 000000000..39c141452 --- /dev/null +++ b/test_regress/t/t_timing_fork_unsup.v @@ -0,0 +1,31 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + class C; + task f; + int x = 0; + fork + #6 x = 4; + #2 x++; + x = #4 x * 3; + begin + #1 if (x != 1) $stop; + #2 if (x != 2) $stop; + #2 if (x != 3) $stop; + #2 if (x != 4) $stop; + $finish; + end + join_none + x = 1; + endtask + endclass + + initial begin + C o = new; + o.f; + end +endmodule diff --git a/test_regress/t/t_timing_func_bad.out b/test_regress/t/t_timing_func_bad.out new file mode 100644 index 000000000..8bf343ea8 --- /dev/null +++ b/test_regress/t/t_timing_func_bad.out @@ -0,0 +1,25 @@ +%Error: t/t_timing_func_bad.v:10:8: Delays are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) + : ... In instance t + 10 | #1 $stop; + | ^ +%Error: t/t_timing_func_bad.v:15:13: Timing controls are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) + : ... In instance t + 15 | f2 = #5 0; $stop; + | ^ +%Error: t/t_timing_func_bad.v:20:7: Event controls are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) + : ... In instance t + 20 | @e $stop; + | ^ +%Error: t/t_timing_func_bad.v:25:12: Timing controls are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) + : ... In instance t + 25 | f4 = @e 0; $stop; + | ^ +%Error: t/t_timing_func_bad.v:31:7: Wait statements are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) + : ... In instance t + 31 | wait(i == 0) $stop; + | ^~~~ +%Error: t/t_timing_func_bad.v:42:8: Delays are not legal in final blocks (IEEE 1800-2017 9.2.3) + : ... In instance t + 42 | #1; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_func_bad.pl b/test_regress/t/t_timing_func_bad.pl new file mode 100755 index 000000000..0ee17366a --- /dev/null +++ b/test_regress/t/t_timing_func_bad.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + verilator_flags2 => ['--no-timing'], + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_func_bad.v b/test_regress/t/t_timing_func_bad.v new file mode 100644 index 000000000..0e8f13b78 --- /dev/null +++ b/test_regress/t/t_timing_func_bad.v @@ -0,0 +1,46 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/); + + function int f1; + #1 $stop; + f1 = 0; + endfunction + + function int f2; + f2 = #5 0; $stop; + endfunction + + event e; + function int f3; + @e $stop; + f3 = 0; + endfunction + + function int f4; + f4 = @e 0; $stop; + endfunction + + int i; + + function int f5; + wait(i == 0) $stop; + f5 = 0; + endfunction + + initial begin + i = f1(); + $write("*-* All Finished *-*\n"); + $finish; + end + + final begin + #1; + $stop; + end + +endmodule diff --git a/test_regress/t/t_timing_intra_assign.out b/test_regress/t/t_timing_intra_assign.out new file mode 100644 index 000000000..fc870d925 --- /dev/null +++ b/test_regress/t/t_timing_intra_assign.out @@ -0,0 +1,15 @@ +val[0]=0 val[1]=0 val[2]=0 +val[0]=1 val[1]=0 val[2]=0 +val[0]=2 val[1]=0 val[2]=15 +val[0]=3 val[1]=0 val[2]=15 +val[0]=4 val[1]=1 val[2]=14 +val[0]=5 val[1]=1 val[2]=14 +val[0]=6 val[1]=2 val[2]=13 +val[0]=7 val[1]=4 val[2]=11 +val[0]=8 val[1]=7 val[2]=8 +val[0]=9 val[1]=7 val[2]=8 +val[0]=10 val[1]=7 val[2]=8 +val[0]=11 val[1]=7 val[2]=8 +val[0]=12 val[1]=7 val[2]=8 +val[0]=13 val[1]=7 val[2]=8 +*-* All Finished *-* diff --git a/test_regress/t/t_timing_intra_assign.pl b/test_regress/t/t_timing_intra_assign.pl new file mode 100755 index 000000000..6852f73f7 --- /dev/null +++ b/test_regress/t/t_timing_intra_assign.pl @@ -0,0 +1,40 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing -Wno-UNOPTFLAT"], + make_main => 0, + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + + compile( + verilator_flags2 => ["--exe --main --timing -Wno-UNOPTFLAT -fno-localize"], + make_main => 0, + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_intra_assign.v b/test_regress/t/t_timing_intra_assign.v new file mode 100644 index 000000000..35afe20a9 --- /dev/null +++ b/test_regress/t/t_timing_intra_assign.v @@ -0,0 +1,56 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic[3:0] val[3]; + logic[1:0] idx1 = 0; + logic[1:0] idx2 = 0; + logic[0:0] idx3 = 0; + event e; + + always @val[0] $write("val[0]=%0d val[1]=%0d val[2]=%0d\n", val[0], val[1], val[2]); + + assign #10 {val[1], val[2]} = {val[0], 4'hf-val[0]}; + + always #10 begin // always so we can use NBA + val[0] = 1; + #10 val[0] = 2; + fork #5 val[0] = 3; join_none + val[0] = #10 val[0] + 2; + val[idx1] <= #10 val[idx1] + 2; + fork begin #5 + val[0] = 5; + idx1 = 2; + idx2 = 3; + idx3 = 1; + #40 ->e; + end join_none + val[idx1][idx2[idx3+:2]] = #20 1; + @e val[0] = 8; + fork begin + #1 val[0] = 9; + #2 ->e; + end join_none + val[0] = @e val[0] + 2; + val[0] <= @e val[0] + 2; + fork begin + #1 val[0] = 11; + end join_none + #2 ->e; + idx1 = 0; + idx2 = 0; + idx3 = 0; + fork begin #2 + idx1 = 2; + idx2 = 3; + idx3 = 1; + end join_none + #1 val[idx1[idx3+:2]][idx2] <= @e 1; + #1 ->e; + #1 $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_intra_assign_delay.out b/test_regress/t/t_timing_intra_assign_delay.out deleted file mode 100644 index f726ad4b4..000000000 --- a/test_regress/t/t_timing_intra_assign_delay.out +++ /dev/null @@ -1,31 +0,0 @@ -%Warning-ASSIGNDLY: t/t_timing_intra_assign_delay.v:12:11: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 12 | assign #10 val2 = val1; - | ^~ - ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest - ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-STMTDLY: t/t_timing_intra_assign_delay.v:16:6: Unsupported: Ignoring delay on this delayed statement. - : ... In instance t - 16 | #10 val1 = 2; - | ^~ -%Error-UNSUPPORTED: t/t_timing_intra_assign_delay.v:17:5: Unsupported: fork statements - : ... In instance t - 17 | fork #5 val1 = 3; join_none - | ^~~~ -%Warning-ASSIGNDLY: t/t_timing_intra_assign_delay.v:18:13: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 18 | val1 = #10 val1 + 2; - | ^~ -%Warning-ASSIGNDLY: t/t_timing_intra_assign_delay.v:19:14: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 19 | val1 <= #10 val1 + 2; - | ^~ -%Error-UNSUPPORTED: t/t_timing_intra_assign_delay.v:20:5: Unsupported: fork statements - : ... In instance t - 20 | fork #5 val1 = 5; join_none - | ^~~~ -%Warning-STMTDLY: t/t_timing_intra_assign_delay.v:21:6: Unsupported: Ignoring delay on this delayed statement. - : ... In instance t - 21 | #20 $write("*-* All Finished *-*\n"); - | ^~ -%Error: Exiting due to diff --git a/test_regress/t/t_timing_intra_assign_delay.v b/test_regress/t/t_timing_intra_assign_delay.v deleted file mode 100644 index 4ddc2461d..000000000 --- a/test_regress/t/t_timing_intra_assign_delay.v +++ /dev/null @@ -1,24 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain, for -// any use, without warranty, 2022 by Antmicro Ltd. -// SPDX-License-Identifier: CC0-1.0 - -module t; - int val1, val2; - - always @val1 $write("[%0t] val1=%0d val2=%0d\n", $time, val1, val2); - - assign #10 val2 = val1; - - initial begin - val1 = 1; - #10 val1 = 2; - fork #5 val1 = 3; join_none - val1 = #10 val1 + 2; - val1 <= #10 val1 + 2; - fork #5 val1 = 5; join_none - #20 $write("*-* All Finished *-*\n"); - $finish; - end -endmodule diff --git a/test_regress/t/t_timing_intra_assign_event.out b/test_regress/t/t_timing_intra_assign_event.out deleted file mode 100644 index 3fd9349f3..000000000 --- a/test_regress/t/t_timing_intra_assign_event.out +++ /dev/null @@ -1,32 +0,0 @@ -%Error-UNSUPPORTED: t/t_timing_intra_assign_event.v:15:5: Unsupported: event control statement in this location - : ... In instance t - : ... Suggest have one event control statement per procedure, at the top of the procedure - 15 | @e val = 2; - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_timing_intra_assign_event.v:16:5: Unsupported: fork statements - : ... In instance t - 16 | fork begin - | ^~~~ -%Warning-ASSIGNDLY: t/t_timing_intra_assign_event.v:21:11: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 21 | val = @e val + 2; - | ^ - ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-ASSIGNDLY: t/t_timing_intra_assign_event.v:22:12: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 22 | val <= @e val + 2; - | ^ -%Error-UNSUPPORTED: t/t_timing_intra_assign_event.v:23:5: Unsupported: fork statements - : ... In instance t - 23 | fork begin - | ^~~~ -%Warning-STMTDLY: t/t_timing_intra_assign_event.v:29:6: Unsupported: Ignoring delay on this delayed statement. - : ... In instance t - 29 | #1 $write("*-* All Finished *-*\n"); - | ^ -%Warning-STMTDLY: t/t_timing_intra_assign_event.v:33:12: Unsupported: Ignoring delay on this delayed statement. - : ... In instance t - 33 | initial #1 ->e; - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_timing_intra_assign_event.v b/test_regress/t/t_timing_intra_assign_event.v deleted file mode 100644 index d98da3a66..000000000 --- a/test_regress/t/t_timing_intra_assign_event.v +++ /dev/null @@ -1,34 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain, for -// any use, without warranty, 2022 by Antmicro Ltd. -// SPDX-License-Identifier: CC0-1.0 - -module t; - int val; - event e; - - always @val $write("val=%0d\n", val); - - initial begin - val = 1; - @e val = 2; - fork begin - @e #1 val = 3; - ->e; - end join_none - ->e; - val = @e val + 2; - val <= @e val + 2; - fork begin - @e val = 5; - ->e; - end join_none - ->e; - ->e; - #1 $write("*-* All Finished *-*\n"); - $finish; - end - - initial #1 ->e; -endmodule diff --git a/test_regress/t/t_timing_long.pl b/test_regress/t/t_timing_long.pl index eb279f82e..3efef3dff 100755 --- a/test_regress/t/t_timing_long.pl +++ b/test_regress/t/t_timing_long.pl @@ -54,9 +54,22 @@ top_filename("$Self->{obj_dir}/t_timing_long.v"); gen($Self->{top_filename}); +if ($Self->have_coroutines) { + compile( + verilator_flags2 => ["--exe --build --main --timing"], + verilator_make_cmake => 0, + verilator_make_gmake => 0, + make_main => 0, + make_top => 1, + ); + + execute( + check_finished => 1, + ); +} + compile( - # verilator_flags2 => ["--exe --build --main --timing"], # Unsupported - verilator_flags2 => ["--exe --build --main -Wno-STMTDLY"], + verilator_flags2 => ["--exe --build --main --no-timing -Wno-STMTDLY"], verilator_make_cmake => 0, verilator_make_gmake => 0, make_main => 0, diff --git a/test_regress/t/t_timing_nba.pl b/test_regress/t/t_timing_nba.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_nba.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_nba.v b/test_regress/t/t_timing_nba.v new file mode 100644 index 000000000..9554a5ad3 --- /dev/null +++ b/test_regress/t/t_timing_nba.v @@ -0,0 +1,41 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + integer cyc = 0; + + reg [7:0] a; + reg [127:0] b; + + always #1 begin + cyc <= cyc + 1; + if (cyc == 0) begin + a <= 8'hFF; + a[7] <= 1'b0; + end + else if (cyc == 1) begin +`ifdef TEST_VERBOSE + $write("a = %x\n", a); +`endif + if (a != 8'h7F) $stop; + end + else if (cyc == 2) begin + b <= 128'hFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; + b[127] <= 1'b0; + end + else if (cyc == 3) begin +`ifdef TEST_VERBOSE + $write("b = %x\n", b); +`endif + if (b != 128'h7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) $stop; + end + else if (cyc > 3) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_timing_net_delay.out b/test_regress/t/t_timing_net_delay.out deleted file mode 100644 index 116ccc0a0..000000000 --- a/test_regress/t/t_timing_net_delay.out +++ /dev/null @@ -1,11 +0,0 @@ -%Warning-ASSIGNDLY: t/t_timing_net_delay.v:13:15: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 13 | wire[3:0] #4 val1 = cyc; - | ^ - ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest - ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-ASSIGNDLY: t/t_timing_net_delay.v:17:12: Unsupported: Ignoring timing control on this assignment. - : ... In instance t - 17 | assign #4 val2 = cyc; - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_timing_off.pl b/test_regress/t/t_timing_off.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_off.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_off.v b/test_regress/t/t_timing_off.v new file mode 100644 index 000000000..1f25a7670 --- /dev/null +++ b/test_regress/t/t_timing_off.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event e1; + event e2; + + initial begin + int x; + // verilator timing_off + #1 + fork @e1; @e2; join; + @e1 + wait(x == 4) + x = #1 8; + // verilator timing_on + if (x != 8) $stop; + if ($time != 0) $stop; + // verilator timing_off + @e2; + // verilator timing_on + @e1; + if ((e1.triggered && e2.triggered) + || (!e1.triggered && !e2.triggered)) $stop; + if ($time != 2) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + + initial #2 ->e1; + // verilator timing_off + initial #2 ->e2; + // verilator timing_on + initial #3 $stop; // timeout + initial #1 @(e1, e2) #1 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_pong.pl b/test_regress/t/t_timing_pong.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_pong.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_pong.v b/test_regress/t/t_timing_pong.v new file mode 100644 index 000000000..a9972a4a3 --- /dev/null +++ b/test_regress/t/t_timing_pong.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event ping; + event pong; + + int cnt = 0; + + initial forever @ping begin +`ifdef TEST_VERBOSE + $write("ping\n"); +`endif + cnt++; + ->pong; + end + + initial forever @pong begin +`ifdef TEST_VERBOSE + $write("pong\n"); +`endif + if (cnt < 10) ->ping; + end + + initial #1 ->ping; + initial #2 + if (cnt == 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end else $stop; + initial #3 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_reentry.pl b/test_regress/t/t_timing_reentry.pl index dec83b460..f86c4b944 100755 --- a/test_regress/t/t_timing_reentry.pl +++ b/test_regress/t/t_timing_reentry.pl @@ -2,7 +2,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2019 by Wilson Snyder. This program is free software; you +# Copyright 2022 by Antmicro Ltd. This program is free software; you # can redistribute it and/or modify it under the terms of either the GNU # Lesser General Public License Version 3 or the Perl Artistic License # Version 2.0. @@ -10,18 +10,19 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -compile( - #verilator_flags2 => ['--exe --build --main --timing'], # Unsupported - verilator_flags2 => ['--exe --build --main --bbox-unsup -Wno-STMTDLY -Wno-INITIALDLY'], - verilator_make_cmake => 0, - verilator_make_gmake => 0, - make_main => 0, - make_top => 1, - ); +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); -execute( - check_finished => 1, - ) if !$Self->{vlt_all}; + execute( + check_finished => 1, + ); +} ok(1); 1; diff --git a/test_regress/t/t_timing_sched.pl b/test_regress/t/t_timing_sched.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_sched.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_sched.v b/test_regress/t/t_timing_sched.v new file mode 100644 index 000000000..57fa10fcd --- /dev/null +++ b/test_regress/t/t_timing_sched.v @@ -0,0 +1,61 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk1 = 0; + + assign #3 clk1 = ~clk1; + + logic clk2 = 0; + assign #11 clk2 = ~clk2; + + int a1 = 0; + int b1 = 0; + always @(posedge clk1) #4 a1 = a1 + 1; + always @(posedge clk1) @(posedge clk2) b1 = b1 + 1; + + int a2 = 0; + always_comb begin + a2 = a1 << 1; +`ifdef TEST_VERBOSE + $display("[%0t] a2 = %0d", $time, a2); +`endif + end + + int b2 = 0; + always_comb begin + b2 = b1 << 2; +`ifdef TEST_VERBOSE + $display("[%0t] b2 = %0d", $time, b2); +`endif + end + + // verilator lint_off UNOPTFLAT + int c1 = 0; + int c2 = 0; + always @(b2, c1) begin + c2 = c1 >> 3; + c1 = b2 << 3; + end + // verilator lint_on UNOPTFLAT + + always @(posedge clk1) if (a2 != a1 << 1) $stop; + always @(posedge clk2) #1 if (b2 != b1 << 2) $stop; + + initial #78 begin +`ifdef TEST_VERBOSE + $display("a1=%0d, b1=%0d, a2=%0d, b2=%0d, c1=%0d, c2=%0d", a1, b1, a2, b2, c1, c2); +`endif + if (a1 != 12) $stop; + if (b1 != 4) $stop; + if (a2 != a1 << 1) $stop; + if (b2 != b1 << 2) $stop; + if (c1 != b2 << 3) $stop; + if (c2 != c1 >> 3) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_sched_if.pl b/test_regress/t/t_timing_sched_if.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_sched_if.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_sched_if.v b/test_regress/t/t_timing_sched_if.v new file mode 100644 index 000000000..faa84e266 --- /dev/null +++ b/test_regress/t/t_timing_sched_if.v @@ -0,0 +1,69 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk1 = 0; + + assign #3 clk1 = ~clk1; + + logic clk2 = 0; + assign #11 clk2 = ~clk2; + + logic flag = 0; + int a1 = 0; + int b1 = 0; + int c1 = 0; + always @(posedge clk1) begin + if (flag) #4 a1 = a1 + 1; + else @(posedge clk2) b1 = b1 + 1; + c1 = c1 + 1; + flag = ~flag; + end + + int a2 = 0; + always_comb begin + a2 = a1 << 1; +`ifdef TEST_VERBOSE + $display("[%0t] a2 = %0d", $time, a2); +`endif + end + + int b2 = 0; + always_comb begin + b2 = b1 << 2; +`ifdef TEST_VERBOSE + $display("[%0t] b2 = %0d", $time, b2); +`endif + end + + int c2 = 0; + always_comb begin + c2 = c1 << 3; +`ifdef TEST_VERBOSE + $display("[%0t] c2 = %0d", $time, c2); +`endif + end + + always @(posedge clk1) begin + #1 if (c2 != c1 << 3) $stop; + #5 if (a2 != a1 << 1) $stop; + end + always @(posedge clk2) #1 if (b2 != b1 << 2) $stop; + + initial #78 begin +`ifdef TEST_VERBOSE + $display("a1=%0d, b1=%0d, c1=%0d, a2=%0d, b2=%0d, c2=%0d", a1, b1, c1, a2, b2, c2); +`endif + if (a1 != 3) $stop; + if (b1 != 4) $stop; + if (c1 != a1 + b1) $stop; + if (a2 != a1 << 1) $stop; + if (b2 != b1 << 2) $stop; + if (c2 != c1 << 3) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_sched_nba.pl b/test_regress/t/t_timing_sched_nba.pl new file mode 100755 index 000000000..f86c4b944 --- /dev/null +++ b/test_regress/t/t_timing_sched_nba.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_sched_nba.v b/test_regress/t/t_timing_sched_nba.v new file mode 100644 index 000000000..4106d755c --- /dev/null +++ b/test_regress/t/t_timing_sched_nba.v @@ -0,0 +1,50 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk1 = 0; + + assign #3 clk1 = ~clk1; + + logic clk2 = 0; + assign #11 clk2 = ~clk2; + + int a1 = 0; + int b1 = 0; + always @(posedge clk1) #4 a1 <= a1 + 1; + always @(posedge clk1) @(posedge clk2) b1 <= b1 + 1; + + int a2 = 0; + always_comb begin + a2 = a1 << 1; +`ifdef TEST_VERBOSE + $display("[%0t] a2 = %0d", $time, a2); +`endif + end + + int b2 = 0; + always_comb begin + b2 = b1 << 2; +`ifdef TEST_VERBOSE + $display("[%0t] b2 = %0d", $time, b2); +`endif + end + + always @(posedge clk1) #5 if (a2 != a1 << 1) $stop; + always @(posedge clk2) #1 if (b2 != b1 << 2) $stop; + + initial #78 begin +`ifdef TEST_VERBOSE + $display("a1=%0d, b1=%0d, a2=%0d, b2=%0d", a1, b1, a2, b2); +`endif + if (a1 != 12) $stop; + if (b1 != 4) $stop; + if (a2 != a1 << 1) $stop; + if (b2 != b1 << 2) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_timing_unset1.out b/test_regress/t/t_timing_unset1.out new file mode 100644 index 000000000..4f3b14171 --- /dev/null +++ b/test_regress/t/t_timing_unset1.out @@ -0,0 +1,34 @@ +%Error-NEEDTIMINGOPT: t/t_notiming.v:12:9: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 12 | #1 + | ^ + ... For error description see https://verilator.org/warn/NEEDTIMINGOPT?v=latest +%Error-NEEDTIMINGOPT: t/t_notiming.v:13:8: Use --timing or --no-timing to specify how forks should be handled + : ... In instance t + 13 | fork @e; @e; join; + | ^~~~ +%Error-NEEDTIMINGOPT: t/t_notiming.v:14:8: Use --timing or --no-timing to specify how event controls should be handled + : ... In instance t + 14 | @e + | ^ +%Error-NEEDTIMINGOPT: t/t_notiming.v:15:8: Use --timing or --no-timing to specify how wait statements should be handled + : ... In instance t + 15 | wait(x == 4) + | ^~~~ +%Error-NEEDTIMINGOPT: t/t_notiming.v:16:13: Use --timing or --no-timing to specify how timing controls should be handled + : ... In instance t + 16 | x = #1 8; + | ^ +%Error-NEEDTIMINGOPT: t/t_notiming.v:19:8: Use --timing or --no-timing to specify how event controls should be handled + : ... In instance t + 19 | @e + | ^ +%Error-NEEDTIMINGOPT: t/t_notiming.v:26:13: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 26 | initial #1 ->e; + | ^ +%Error-NEEDTIMINGOPT: t/t_notiming.v:27:13: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 27 | initial #2 $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_intra_assign_delay.pl b/test_regress/t/t_timing_unset1.pl similarity index 87% rename from test_regress/t/t_timing_intra_assign_delay.pl rename to test_regress/t/t_timing_unset1.pl index d61820774..167a6cf70 100755 --- a/test_regress/t/t_timing_intra_assign_delay.pl +++ b/test_regress/t/t_timing_unset1.pl @@ -10,8 +10,10 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); -lint( - verilator_flags2 => ['-Wall -Wno-DECLFILENAME'], +top_filename("t/t_notiming.v"); + +compile( + # --timing/--no-timing not specified fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_timing_unset2.out b/test_regress/t/t_timing_unset2.out new file mode 100644 index 000000000..7d8dd8304 --- /dev/null +++ b/test_regress/t/t_timing_unset2.out @@ -0,0 +1,26 @@ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:25:8: Use --timing or --no-timing to specify how event controls should be handled + : ... In instance t + 25 | @e1; + | ^ + ... For error description see https://verilator.org/warn/NEEDTIMINGOPT?v=latest +%Error-NEEDTIMINGOPT: t/t_timing_off.v:33:13: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 33 | initial #2 ->e1; + | ^ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:37:13: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 37 | initial #3 $stop; + | ^ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:38:13: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:38:15: Use --timing or --no-timing to specify how event controls should be handled + : ... In instance t + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:38:26: Use --timing or --no-timing to specify how delays should be handled + : ... In instance t + 38 | initial #1 @(e1, e2) #1 $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_timing_unset2.pl b/test_regress/t/t_timing_unset2.pl new file mode 100755 index 000000000..be9ccffa3 --- /dev/null +++ b/test_regress/t/t_timing_unset2.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_timing_off.v"); + +compile( + # --timing/--no-timing not specified + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_wait.pl b/test_regress/t/t_timing_wait.pl new file mode 100755 index 000000000..ecf974b24 --- /dev/null +++ b/test_regress/t/t_timing_wait.pl @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing -Wno-WAITCONST"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_wait.v b/test_regress/t/t_timing_wait.v new file mode 100644 index 000000000..abfda1c09 --- /dev/null +++ b/test_regress/t/t_timing_wait.v @@ -0,0 +1,55 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(msg) $write(msg) +`else + `define WRITE_VERBOSE(msg) +`endif + +module t; + int a = 0; + int b = 0; + int c = 0; + + initial begin + `WRITE_VERBOSE("start with a==0, b==0, c==0\n"); + #2 a = 1; `WRITE_VERBOSE("assign 1 to a\n"); + #1 a = 2; `WRITE_VERBOSE("assign 2 to a\n"); // a==2 + #1 a = 0; `WRITE_VERBOSE("assign 0 to a\n"); + #1 a = 2; `WRITE_VERBOSE("assign 2 to a\n"); // 1a + #1 c = 3; `WRITE_VERBOSE("assign 3 to c\n"); + #1 c = 4; `WRITE_VERBOSE("assign 4 to c\n"); // a+bc + b = 5; + end + + initial begin + #1 `WRITE_VERBOSE("waiting for a==2\n"); + wait(a == 2) if (a != 2) $stop; + `WRITE_VERBOSE("waiting for a<2\n"); + wait(a < 2) if (a >= 2) $stop; + `WRITE_VERBOSE("waiting for a==0\n"); + wait(a == 0) if (a != 0) $stop; + `WRITE_VERBOSE("waiting for 1 1 && a < 3) if (a <= 1 || a >= 3) $stop; + `WRITE_VERBOSE("waiting for b>a\n"); + wait(b > a) if (b <= a) $stop; + `WRITE_VERBOSE("waiting for a+b= c) $stop; + `WRITE_VERBOSE("waiting for ac\n"); + wait(a < b && b > c) if (a >= b || b <= c) $stop; + wait(0 < 1) $write("*-* All Finished *-*\n"); + $finish; + end + + initial wait(0) $stop; + initial wait(1 == 0) $stop; + + initial #11 $stop; // timeout +endmodule diff --git a/test_regress/t/t_timing_zerodly_unsup.out b/test_regress/t/t_timing_zerodly_unsup.out new file mode 100644 index 000000000..fc3a2f5b0 --- /dev/null +++ b/test_regress/t/t_timing_zerodly_unsup.out @@ -0,0 +1,5 @@ +%Error-ZERODLY: t/t_timing_zerodly_unsup.v:12:14: Unsupported: #0 delays do not schedule process resumption in the Inactive region + 12 | #0 if (v) $finish; + | ^ + ... For error description see https://verilator.org/warn/ZERODLY?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_timing_zerodly_unsup.pl b/test_regress/t/t_timing_zerodly_unsup.pl new file mode 100755 index 000000000..8ab3c0995 --- /dev/null +++ b/test_regress/t/t_timing_zerodly_unsup.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_timing_zerodly_unsup.v b/test_regress/t/t_timing_zerodly_unsup.v new file mode 100644 index 000000000..7fba70252 --- /dev/null +++ b/test_regress/t/t_timing_zerodly_unsup.v @@ -0,0 +1,18 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + event e; + logic v = 0; + initial #1 begin + fork + #0 if (v) $finish; + else $stop; + join_none + ->e; + end + initial @e v = 1; +endmodule diff --git a/test_regress/t/t_verilated_all.pl b/test_regress/t/t_verilated_all.pl index 402f834d4..4cdd44194 100755 --- a/test_regress/t/t_verilated_all.pl +++ b/test_regress/t/t_verilated_all.pl @@ -18,6 +18,7 @@ compile( "--coverage-toggle --coverage-line --coverage-user", "--trace --vpi ", "--trace-threads 1", + $Self->have_coroutines ? "--timing" : "--no-timing -Wno-STMTDLY", "--prof-exec", "--prof-pgo", "$root/include/verilated_save.cpp"], threads => 2 @@ -56,6 +57,7 @@ foreach my $file (sort keys %hit) { && $file !~ /_sc/ && $file !~ /_fst/ && $file !~ /_heavy/ + && ($file !~ /_timing/ || $Self->have_coroutines) && ($file !~ /_thread/)) { error("Include file not covered by t_verilated_all test: ", $file); } diff --git a/test_regress/t/t_verilated_all.v b/test_regress/t/t_verilated_all.v index 8edabe38a..2030b8ba8 100644 --- a/test_regress/t/t_verilated_all.v +++ b/test_regress/t/t_verilated_all.v @@ -17,7 +17,7 @@ module t (/*AUTOARG*/ cyc <= cyc + 1; if (cyc!=0) begin if (cyc==10) begin - $write("*-* All Finished *-*\n"); + #5 $write("*-* All Finished *-*\n"); $finish; end end diff --git a/test_regress/t/t_verilated_all_newest.pl b/test_regress/t/t_verilated_all_newest.pl index 221f84ad7..7ea15b92d 100755 --- a/test_regress/t/t_verilated_all_newest.pl +++ b/test_regress/t/t_verilated_all_newest.pl @@ -16,7 +16,8 @@ my $root = ".."; compile( # Can't use --coverage and --savable together, so cheat and compile inline - verilator_flags2 => ["--cc --coverage-toggle --coverage-line --coverage-user --trace --prof-exec --prof-pgo --vpi $root/include/verilated_save.cpp"], + verilator_flags2 => ["--cc --coverage-toggle --coverage-line --coverage-user --trace --prof-exec --prof-pgo --vpi $root/include/verilated_save.cpp", + $Self->have_coroutines ? "--timing" : "--no-timing -Wno-STMTDLY"], make_flags => 'DRIVER_STD=newest', ); diff --git a/test_regress/t/t_verilated_threaded.pl b/test_regress/t/t_verilated_threaded.pl index a11df1cfe..a012b4529 100755 --- a/test_regress/t/t_verilated_threaded.pl +++ b/test_regress/t/t_verilated_threaded.pl @@ -16,7 +16,8 @@ my $root = ".."; compile( # Can't use --coverage and --savable together, so cheat and compile inline - verilator_flags2 => ["--cc --coverage-toggle --coverage-line --coverage-user --trace --vpi $root/include/verilated_save.cpp"], + verilator_flags2 => ["--cc --coverage-toggle --coverage-line --coverage-user --trace --vpi $root/include/verilated_save.cpp", + $Self->have_coroutines ? "--timing" : "--no-timing -Wno-STMTDLY"], threads => 1 ); diff --git a/test_regress/t/t_vlt_timing.pl b/test_regress/t/t_vlt_timing.pl new file mode 100755 index 000000000..0b172f726 --- /dev/null +++ b/test_regress/t/t_vlt_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +top_filename("t/t_timing_off.v"); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing t/t_vlt_timing.vlt"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_vlt_timing.vlt b/test_regress/t/t_vlt_timing.vlt new file mode 100644 index 000000000..f5ed20a92 --- /dev/null +++ b/test_regress/t/t_vlt_timing.vlt @@ -0,0 +1,11 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`verilator_config + +timing_on --file "t/t_timing_off.v" --lines 23 +timing_off -file "t/t_timing_off.v" -lines 26-34 +timing_on -file "t/t_timing_off.v" -lines 35-38 diff --git a/test_regress/t/t_wait.out b/test_regress/t/t_wait.out index 409170e8e..f60b978eb 100644 --- a/test_regress/t/t_wait.out +++ b/test_regress/t/t_wait.out @@ -1,14 +1,39 @@ -%Error-UNSUPPORTED: t/t_wait.v:12:7: Unsupported: wait statements +%Error-NOTIMING: t/t_wait.v:12:7: Wait statements require --timing + : ... In instance t 12 | wait (value == 1); | ^~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_wait.v:14:7: Unsupported: wait statements + ... For error description see https://verilator.org/warn/NOTIMING?v=latest +%Error-NOTIMING: t/t_wait.v:14:7: Wait statements require --timing + : ... In instance t 14 | wait (0); | ^~~~ -%Error-UNSUPPORTED: t/t_wait.v:17:7: Unsupported: wait statements +%Error-NOTIMING: t/t_wait.v:17:7: Wait statements require --timing + : ... In instance t 17 | wait (value == 2); | ^~~~ -%Error-UNSUPPORTED: t/t_wait.v:20:7: Unsupported: wait statements +%Error-NOTIMING: t/t_wait.v:20:7: Wait statements require --timing + : ... In instance t 20 | wait (value == 3) if (value != 3) $stop; | ^~~~ +%Warning-STMTDLY: t/t_wait.v:25:8: Ignoring delay on this statement due to --no-timing + : ... In instance t + 25 | #10; + | ^~ + ... Use "/* verilator lint_off STMTDLY */" and lint_on around source to disable this message. +%Warning-STMTDLY: t/t_wait.v:27:8: Ignoring delay on this statement due to --no-timing + : ... In instance t + 27 | #10; + | ^~ +%Warning-STMTDLY: t/t_wait.v:29:8: Ignoring delay on this statement due to --no-timing + : ... In instance t + 29 | #10; + | ^~ +%Warning-STMTDLY: t/t_wait.v:31:8: Ignoring delay on this statement due to --no-timing + : ... In instance t + 31 | #10; + | ^~ +%Warning-STMTDLY: t/t_wait.v:33:8: Ignoring delay on this statement due to --no-timing + : ... In instance t + 33 | #10; + | ^~ %Error: Exiting due to diff --git a/test_regress/t/t_wait.pl b/test_regress/t/t_wait.pl index 89ffd046b..c586a8d47 100755 --- a/test_regress/t/t_wait.pl +++ b/test_regress/t/t_wait.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(linter => 1); lint( - verilator_flags2 => ['--lint-only'], + verilator_flags2 => ['--lint-only --no-timing'], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_wait_timing.pl b/test_regress/t/t_wait_timing.pl new file mode 100755 index 000000000..8c852569f --- /dev/null +++ b/test_regress/t/t_wait_timing.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_wait.v"); + + compile( + timing_loop => 1, + verilator_flags2 => ["--timing -Wno-WAITCONST"], + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/verilator-config.cmake.in b/verilator-config.cmake.in index 2125341b8..2210b3297 100644 --- a/verilator-config.cmake.in +++ b/verilator-config.cmake.in @@ -89,6 +89,12 @@ define_property(TARGET FULL_DOCS "Verilator multithread tracing enabled" ) +define_property(TARGET + PROPERTY VERILATOR_TIMING + BRIEF_DOCS "Verilator timing enabled" + FULL_DOCS "Verilator timing enabled" +) + define_property(TARGET PROPERTY VERILATOR_COVERAGE BRIEF_DOCS "Verilator coverage enabled" @@ -337,6 +343,11 @@ function(verilate TARGET) endif() target_compile_features(${TARGET} PRIVATE cxx_std_11) + + if (${VERILATE_PREFIX}_TIMING) + check_cxx_compiler_flag(-fcoroutines-ts COROUTINES_TS_FLAG) + target_compile_options(${TARGET} PRIVATE $,-fcoroutines-ts,-fcoroutines>) + endif() endfunction() function(_verilator_find_systemc) From 2af53048845b1a9b66dc7670365a1d3a5145f5f2 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Fri, 26 Aug 2022 12:11:44 +0200 Subject: [PATCH 028/177] Fix tracing of slow coroutines (#3576 part) (#3579) --- src/V3Trace.cpp | 8 ++-- test_regress/t/t_timing_trace.out | 67 +++++++++++++++++++++++++++++++ test_regress/t/t_timing_trace.pl | 32 +++++++++++++++ 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 test_regress/t/t_timing_trace.out create mode 100755 test_regress/t/t_timing_trace.pl diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index b6fbb95ce..ebb1f1e7c 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -839,9 +839,11 @@ private: V3GraphVertex* const funcVtxp = getCFuncVertexp(nodep); if (!m_finding) { // If public, we need a unique activity code to allow for sets // directly in this func - if (nodep->funcPublic() || nodep->dpiExportImpl() - || nodep == v3Global.rootp()->evalp()) { - V3GraphVertex* const activityVtxp = getActivityVertexp(nodep, nodep->slow()); + if (nodep->funcPublic() || nodep->dpiExportImpl() || nodep == v3Global.rootp()->evalp() + || nodep->isCoroutine()) { + // Cannot treat a coroutine as slow, it may be resumed later + const bool slow = nodep->slow() && !nodep->isCoroutine(); + V3GraphVertex* const activityVtxp = getActivityVertexp(nodep, slow); new V3GraphEdge(&m_graph, activityVtxp, funcVtxp, 1); } } diff --git a/test_regress/t/t_timing_trace.out b/test_regress/t/t_timing_trace.out new file mode 100644 index 000000000..c21672923 --- /dev/null +++ b/test_regress/t/t_timing_trace.out @@ -0,0 +1,67 @@ +$version Generated by VerilatedVcd $end +$date Thu Aug 25 09:56:30 2022 $end +$timescale 1ps $end + + $scope module top $end + $scope module t $end + $var wire 1 # clk $end + $var wire 32 $ cyc [31:0] $end + $scope module clkgen $end + $var wire 1 # clk $end + $upscope $end + $upscope $end + $upscope $end +$enddefinitions $end + + +#0 +0# +b00000000000000000000000000000000 $ +#5 +1# +b00000000000000000000000000000001 $ +#10 +0# +#15 +1# +b00000000000000000000000000000010 $ +#20 +0# +#25 +1# +b00000000000000000000000000000011 $ +#30 +0# +#35 +1# +b00000000000000000000000000000100 $ +#40 +0# +#45 +1# +b00000000000000000000000000000101 $ +#50 +0# +#55 +1# +b00000000000000000000000000000110 $ +#60 +0# +#65 +1# +b00000000000000000000000000000111 $ +#70 +0# +#75 +1# +b00000000000000000000000000001000 $ +#80 +0# +#85 +1# +b00000000000000000000000000001001 $ +#90 +0# +#95 +1# +b00000000000000000000000000001010 $ diff --git a/test_regress/t/t_timing_trace.pl b/test_regress/t/t_timing_trace.pl new file mode 100755 index 000000000..5dc845188 --- /dev/null +++ b/test_regress/t/t_timing_trace.pl @@ -0,0 +1,32 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_timing_clkgen1.v"); + + compile( + verilator_flags2 => ["--timing --trace -Wno-MINTYPMAXDLY"], + timing_loop => 1 + ); + + execute( + check_finished => 1, + ); + + vcd_identical($Self->trace_filename, $Self->{golden_filename}); +} + +ok(1); +1; From 24ec84851a337beed074f29d32808706c44b62e8 Mon Sep 17 00:00:00 2001 From: Aleksander Kiryk Date: Mon, 29 Aug 2022 14:39:41 +0200 Subject: [PATCH 029/177] Support $sampled (#3569) --- docs/CONTRIBUTORS | 1 + src/V3Assert.cpp | 40 ++++++++++++++- src/V3Clock.cpp | 57 ++++++++++++++++++++- src/verilog.y | 6 ++- test_regress/t/t_assert_past.pl | 22 ++++++++ test_regress/t/t_assert_past.v | 28 ++++++++++ test_regress/t/t_assert_sampled.pl | 22 ++++++++ test_regress/t/t_assert_sampled.v | 56 ++++++++++++++++++++ test_regress/t/t_past_strobe.out | 11 ++++ test_regress/t/t_past_strobe.pl | 22 ++++++++ test_regress/t/t_past_strobe.v | 44 ++++++++++++++++ test_regress/t/t_sampled_expr.pl | 22 ++++++++ test_regress/t/t_sampled_expr.v | 68 +++++++++++++++++++++++++ test_regress/t/t_sampled_expr_unsup.out | 6 +++ test_regress/t/t_sampled_expr_unsup.pl | 20 ++++++++ test_regress/t/t_sampled_expr_unsup.v | 35 +++++++++++++ 16 files changed, 456 insertions(+), 4 deletions(-) create mode 100755 test_regress/t/t_assert_past.pl create mode 100644 test_regress/t/t_assert_past.v create mode 100755 test_regress/t/t_assert_sampled.pl create mode 100644 test_regress/t/t_assert_sampled.v create mode 100644 test_regress/t/t_past_strobe.out create mode 100755 test_regress/t/t_past_strobe.pl create mode 100644 test_regress/t/t_past_strobe.v create mode 100755 test_regress/t/t_sampled_expr.pl create mode 100644 test_regress/t/t_sampled_expr.v create mode 100644 test_regress/t/t_sampled_expr_unsup.out create mode 100755 test_regress/t/t_sampled_expr_unsup.pl create mode 100644 test_regress/t/t_sampled_expr_unsup.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 5f2441d2d..b8f55e112 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -5,6 +5,7 @@ Please see the Verilator manual for 200+ additional contributors. Thanks to all. Adrien Le Masle Ahmed El-Mahmoudy +Aleksander Kiryk Alex Chadwick Àlex Torregrosa Aliaksei Chapyzhenka diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 86e8710a8..5a46f1ad9 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -45,6 +45,7 @@ private: VDouble0 m_statAsNotImm; // Statistic tracking VDouble0 m_statAsImm; // Statistic tracking VDouble0 m_statAsFull; // Statistic tracking + bool m_inSampled = false; // True inside a sampled expression // METHODS string assertDisplayMessage(AstNode* nodep, const string& prefix, const string& message) { @@ -66,6 +67,11 @@ private: nodep->fmtp()->scopeNamep(new AstScopeName{nodep->fileline(), true}); } } + AstSampled* newSampledExpr(AstNode* nodep) { + const auto sampledp = new AstSampled{nodep->fileline(), nodep}; + sampledp->dtypeFrom(nodep); + return sampledp; + } AstVarRef* newMonitorNumVarRefp(AstNode* nodep, VAccess access) { if (!m_monitorNumVarp) { m_monitorNumVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorNum", @@ -332,7 +338,8 @@ private: ticks = VN_AS(nodep->ticksp(), Const)->toUInt(); } UASSERT_OBJ(ticks >= 1, nodep, "0 tick should have been checked in V3Width"); - AstNode* inp = nodep->exprp()->unlinkFrBack(); + AstNode* const exprp = nodep->exprp()->unlinkFrBack(); + AstNode* inp = newSampledExpr(exprp); AstVar* invarp = nullptr; AstSenTree* const sentreep = nodep->sentreep(); sentreep->unlinkFrBack(); @@ -353,10 +360,41 @@ private: } nodep->replaceWith(inp); } + + //========== Move $sampled down to read-only variables virtual void visit(AstSampled* nodep) override { + if (nodep->user1()) return; + VL_RESTORER(m_inSampled); + { + m_inSampled = true; + iterateChildren(nodep); + } nodep->replaceWith(nodep->exprp()->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(nodep), nodep); } + virtual void visit(AstVarRef* nodep) override { + iterateChildren(nodep); + if (m_inSampled) { + if (!nodep->access().isReadOnly()) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: Write to variable in sampled expression"); + } else { + VNRelinker relinkHandle; + nodep->unlinkFrBack(&relinkHandle); + AstSampled* const newp = newSampledExpr(nodep); + relinkHandle.relink(newp); + newp->user1(1); + } + } + } + // Don't sample sensitivities + virtual void visit(AstSenItem* nodep) override { + VL_RESTORER(m_inSampled); + { + m_inSampled = false; + iterateChildren(nodep); + } + } //========== Statements virtual void visit(AstDisplay* nodep) override { diff --git a/src/V3Clock.cpp b/src/V3Clock.cpp index 0b5a62730..be1dec524 100644 --- a/src/V3Clock.cpp +++ b/src/V3Clock.cpp @@ -72,8 +72,11 @@ public: class ClockVisitor final : public VNVisitor { private: // STATE + AstCFunc* m_evalp = nullptr; // The '_eval' function + AstScope* m_scopep = nullptr; // Current scope AstSenTree* m_lastSenp = nullptr; // Last sensitivity match, so we can detect duplicates. AstIf* m_lastIfp = nullptr; // Last sensitivity if active to add more under + bool m_inSampled = false; // True inside a sampled expression // METHODS VL_DEBUG_FUNC; // Declare debug() @@ -87,6 +90,25 @@ private: } return senEqnp; } + AstVarScope* createSampledVar(AstVarScope* vscp) { + if (vscp->user1p()) return VN_AS(vscp->user1p(), VarScope); + const AstVar* const varp = vscp->varp(); + const string newvarname + = string("__Vsampled__") + vscp->scopep()->nameDotless() + "__" + varp->name(); + FileLine* const flp = vscp->fileline(); + AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()}; + newvarp->noReset(true); // Reset by below assign + m_scopep->modp()->addStmtp(newvarp); + AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; + vscp->user1p(newvscp); + m_scopep->addVarp(newvscp); + // At the top of _eval, assign them + AstAssign* const finalp = new AstAssign{flp, new AstVarRef{flp, newvscp, VAccess::WRITE}, + new AstVarRef{flp, vscp, VAccess::READ}}; + m_evalp->addInitsp(finalp); + UINFO(4, "New Sampled: " << newvscp << endl); + return newvscp; + } AstIf* makeActiveIf(AstSenTree* sensesp) { AstNode* const senEqnp = createSenseEquation(sensesp->sensesp()); UASSERT_OBJ(senEqnp, sensesp, "No sense equation, shouldn't be in sequent activation."); @@ -151,12 +173,45 @@ private: clearLastSen(); } + //========== Create sampled values + virtual void visit(AstScope* nodep) override { + VL_RESTORER(m_scopep); + { + m_scopep = nodep; + iterateChildren(nodep); + } + } + virtual void visit(AstSampled* nodep) override { + VL_RESTORER(m_inSampled); + { + m_inSampled = true; + iterateChildren(nodep); + nodep->replaceWith(nodep->exprp()->unlinkFrBack()); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + } + virtual void visit(AstVarRef* nodep) override { + iterateChildren(nodep); + if (m_inSampled && !nodep->user1SetOnce()) { + UASSERT_OBJ(nodep->access().isReadOnly(), nodep, "Should have failed in V3Access"); + AstVarScope* const varscp = nodep->varScopep(); + AstVarScope* const lastscp = createSampledVar(varscp); + AstNode* const newp = new AstVarRef{nodep->fileline(), lastscp, VAccess::READ}; + newp->user1SetOnce(); // Don't sample this one + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + } + //-------------------- virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS - explicit ClockVisitor(AstNetlist* netlistp) { iterate(netlistp); } + explicit ClockVisitor(AstNetlist* netlistp) { + m_evalp = netlistp->evalp(); + iterate(netlistp); + } virtual ~ClockVisitor() override = default; }; diff --git a/src/verilog.y b/src/verilog.y index 938a2e82f..0362db208 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -5313,10 +5313,12 @@ concurrent_assertion_item: // IEEE: concurrent_assertion_item concurrent_assertion_statement: // ==IEEE: concurrent_assertion_statement // // IEEE: assert_property_statement //UNSUP remove below: - yASSERT yPROPERTY '(' property_spec ')' elseStmtBlock { $$ = new AstAssert($1, $4, nullptr, $6, false); } + yASSERT yPROPERTY '(' property_spec ')' elseStmtBlock + { $$ = new AstAssert{$1, new AstSampled{$1, $4}, nullptr, $6, false}; } //UNSUP yASSERT yPROPERTY '(' property_spec ')' action_block { } // // IEEE: assume_property_statement - | yASSUME yPROPERTY '(' property_spec ')' elseStmtBlock { $$ = new AstAssert($1, $4, nullptr, $6, false); } + | yASSUME yPROPERTY '(' property_spec ')' elseStmtBlock + { $$ = new AstAssert{$1, new AstSampled{$1, $4}, nullptr, $6, false}; } //UNSUP yASSUME yPROPERTY '(' property_spec ')' action_block { } // // IEEE: cover_property_statement | yCOVER yPROPERTY '(' property_spec ')' stmtBlock { $$ = new AstCover($1, $4, $6, false); } diff --git a/test_regress/t/t_assert_past.pl b/test_regress/t/t_assert_past.pl new file mode 100755 index 000000000..c505d6263 --- /dev/null +++ b/test_regress/t/t_assert_past.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ['--assert'], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_assert_past.v b/test_regress/t/t_assert_past.v new file mode 100644 index 000000000..df6c6a920 --- /dev/null +++ b/test_regress/t/t_assert_past.v @@ -0,0 +1,28 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + clk + ); + input clk; + int cyc = 0; + logic val = 0; + // Example: + always @(posedge clk) begin + cyc <= cyc + 1; + val = ~val; + $display("t=%0t cyc=%0d val=%b", $time, cyc, val); + if (cyc == 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + assert property(@(posedge clk) cyc % 2 == 0 |=> $past(val) == 0) + else $display("$past assert 1 failed"); + assert property(@(posedge clk) cyc % 2 == 1 |=> $past(val) == 1) + else $display("$past assert 2 failed"); + // Example end +endmodule diff --git a/test_regress/t/t_assert_sampled.pl b/test_regress/t/t_assert_sampled.pl new file mode 100755 index 000000000..c505d6263 --- /dev/null +++ b/test_regress/t/t_assert_sampled.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ['--assert'], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_assert_sampled.v b/test_regress/t/t_assert_sampled.v new file mode 100644 index 000000000..49c7cc71e --- /dev/null +++ b/test_regress/t/t_assert_sampled.v @@ -0,0 +1,56 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + reg [3:0] a, b; + + Test1 t1(clk, a, b); + Test2 t2(clk, a, b); + + initial begin + a = 0; + b = 0; + end + + always @(posedge clk) begin + a <= a + 1; + b = b + 1; + + $display("a = %0d, b = %0d, %0d == %0d", a, b, $sampled(a), $sampled(b)); + + if (b >= 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module Test1( + clk, a, b + ); + + input clk; + input [3:0] a, b; + + assert property (@(posedge clk) $sampled(a) == $sampled(b)); + +endmodule + +module Test2( + clk, a, b + ); + + input clk; + input [3:0] a, b; + + assert property (@(posedge clk) a == b); + +endmodule diff --git a/test_regress/t/t_past_strobe.out b/test_regress/t/t_past_strobe.out new file mode 100644 index 000000000..9df6ce6ca --- /dev/null +++ b/test_regress/t/t_past_strobe.out @@ -0,0 +1,11 @@ +1 == 1, 0 == 0 +2 == 2, 1 == 1 +3 == 3, 2 == 2 +4 == 4, 3 == 3 +5 == 5, 4 == 4 +6 == 6, 5 == 5 +7 == 7, 6 == 6 +8 == 8, 7 == 7 +9 == 9, 8 == 8 +*-* All Finished *-* +10 == 10, 9 == 9 diff --git a/test_regress/t/t_past_strobe.pl b/test_regress/t/t_past_strobe.pl new file mode 100755 index 000000000..4bbe254e3 --- /dev/null +++ b/test_regress/t/t_past_strobe.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_past_strobe.v b/test_regress/t/t_past_strobe.v new file mode 100644 index 000000000..b7e58466a --- /dev/null +++ b/test_regress/t/t_past_strobe.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + reg [3:0] a, b; + + Test1 t1(clk, a, b); + + initial begin + a = 0; + b = 0; + end + + always @(posedge clk) begin + a <= a + 1; + b = b + 1; + + if (b >= 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module Test1( + clk, a, b + ); + + input clk; + input [3:0] a, b; + + always @(posedge clk) begin + $strobe("%0d == %0d, %0d == %0d", a, b, $past(a), $past(b)); + end + +endmodule diff --git a/test_regress/t/t_sampled_expr.pl b/test_regress/t/t_sampled_expr.pl new file mode 100755 index 000000000..c505d6263 --- /dev/null +++ b/test_regress/t/t_sampled_expr.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ['--assert'], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_sampled_expr.v b/test_regress/t/t_sampled_expr.v new file mode 100644 index 000000000..9447b48f8 --- /dev/null +++ b/test_regress/t/t_sampled_expr.v @@ -0,0 +1,68 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + reg [3:0] a, b; + + Test1 t1(clk, a, b); + Test2 t2(clk, a, b); + Test3 t3(clk); + + initial begin + a = 0; + b = 0; + end + + always @(posedge clk) begin + a <= a + 1; + b = b + 1; + + $display("a = %0d, b = %0d, %0d == %0d", a, b, $sampled(a), $sampled(b)); + + if (b >= 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module Test1( + clk, a, b + ); + + input clk; + input [3:0] a, b; + + assert property (@(posedge clk) $sampled(a == b) == ($sampled(a) == $sampled(b))); +endmodule + +module Test2( + clk, a, b + ); + + input clk; + input [3:0] a, b; + + assert property (@(posedge clk) eq(a, b)); + + function [0:0] eq([3:0] x, y); + return x == y; + endfunction +endmodule + +module Test3( + clk + ); + + input clk; + + assert property (@(posedge clk) $sampled($time) == $time); +endmodule diff --git a/test_regress/t/t_sampled_expr_unsup.out b/test_regress/t/t_sampled_expr_unsup.out new file mode 100644 index 000000000..170ef48f8 --- /dev/null +++ b/test_regress/t/t_sampled_expr_unsup.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_sampled_expr_unsup.v:34:36: Unsupported: Write to variable in sampled expression + : ... In instance t.t1 + 34 | assert property (@(posedge clk) a++ >= 0); + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_sampled_expr_unsup.pl b/test_regress/t/t_sampled_expr_unsup.pl new file mode 100755 index 000000000..24a95330a --- /dev/null +++ b/test_regress/t/t_sampled_expr_unsup.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile( + expect_filename=>$Self->{golden_filename}, + verilator_flags2=> ['--assert -Wno-UNSIGNED'], + fails => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_sampled_expr_unsup.v b/test_regress/t/t_sampled_expr_unsup.v new file mode 100644 index 000000000..1e8eb9de2 --- /dev/null +++ b/test_regress/t/t_sampled_expr_unsup.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + integer cyc; + + Test1 t1(clk); + + always @(posedge clk) begin + cyc <= cyc + 1; + + if (cyc >= 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module Test1( + clk + ); + + input clk; + reg [3:0] a = 0; + + assert property (@(posedge clk) a++ >= 0); +endmodule From 8de21e9bb7aeea86dfb668098c456e1c9c802ffd Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 24 Aug 2022 17:17:57 +0100 Subject: [PATCH 030/177] Document and ensure OrderGraph is bipartite Minor refactoring and documentation. No functional change. --- src/V3Order.cpp | 60 +++------ src/V3OrderGraph.h | 287 +++++++++++++++-------------------------- src/V3OrderMoveGraph.h | 141 ++++++++++++++++++++ src/V3Partition.h | 1 + src/V3PartitionGraph.h | 2 +- 5 files changed, 265 insertions(+), 226 deletions(-) create mode 100644 src/V3OrderMoveGraph.h diff --git a/src/V3Order.cpp b/src/V3Order.cpp index bb133e8b8..46eb0de7c 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -86,6 +86,7 @@ #include "V3GraphStream.h" #include "V3List.h" #include "V3OrderGraph.h" +#include "V3OrderMoveGraph.h" #include "V3Partition.h" #include "V3PartitionGraph.h" #include "V3Sched.h" @@ -102,21 +103,6 @@ #include #include -//###################################################################### -// Functions for above graph classes - -void OrderGraph::loopsVertexCb(V3GraphVertex* vertexp) { - if (debug()) cout << "-Info-Loop: " << vertexp << "\n"; - if (OrderLogicVertex* const vvertexp = dynamic_cast(vertexp)) { - std::cerr << vvertexp->nodep()->fileline()->warnOther() - << " Example path: " << vvertexp->nodep()->typeName() << endl; - } - if (OrderVarVertex* const vvertexp = dynamic_cast(vertexp)) { - std::cerr << vvertexp->vscp()->fileline()->warnOther() - << " Example path: " << vvertexp->vscp()->prettyName() << endl; - } -} - //###################################################################### // Order information stored under each AstNode::user1p()... @@ -138,7 +124,7 @@ private: public: // METHODS - OrderVarVertex* getVarVertex(V3Graph* graphp, AstVarScope* varscp, VarVertexType type) { + OrderVarVertex* getVarVertex(OrderGraph* graphp, AstVarScope* varscp, VarVertexType type) { const unsigned idx = static_cast(type); OrderVarVertex* vertexp = m_vertexps[idx]; if (!vertexp) { @@ -312,21 +298,7 @@ class OrderBuildVisitor final : public VNVisitor { } } - // Roles of vertices: - // VarVertexType::STD: Data dependencies for combinational logic and delayed - // assignment updates (AssignPost). - // VarVertexType::POST: Ensures all sequential blocks reading a signal do so before - // any combinational or delayed assignments update that signal. - // VarVertexType::PORD: Ensures a _d = _q AssignPre is the first write of a _d, - // before any sequential blocks write to that _d. - // VarVertexType::PRE: This is an optimization. Try to ensure that a _d = _q - // AssignPre is the last read of a _q, after all reads of that - // _q by sequential logic. Note: The model is still correct if we - // cannot satisfy this due to other constraints. If this ordering - // is possible, then combined with the PORD constraint we get - // that all writes to _d are after all reads of a _q, which then - // allows us to eliminate the _d completely and assign to the _q - // directly (this is what V3LifePost does). + // Note: See V3OrderGraph.h about the roles of the various vertex types // Variable is produced if (gen) { @@ -338,9 +310,9 @@ class OrderBuildVisitor final : public VNVisitor { OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); // Add edge from producing LogicVertex -> produced VarStdVertex if (m_inPost) { - new OrderPostCutEdge(m_graphp, m_logicVxp, varVxp); + m_graphp->addSoftEdge(m_logicVxp, varVxp, WEIGHT_COMBO); } else { - new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); + m_graphp->addHardEdge(m_logicVxp, varVxp, WEIGHT_NORMAL); } // Add edge from produced VarPostVertex -> to producing LogicVertex @@ -352,22 +324,22 @@ class OrderBuildVisitor final : public VNVisitor { // ALWAYS do it: // There maybe a wire a=b; between the two blocks OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST); - new OrderEdge(m_graphp, postVxp, m_logicVxp, WEIGHT_POST); + m_graphp->addHardEdge(postVxp, m_logicVxp, WEIGHT_POST); } else if (m_inPre) { // AstAssignPre // Add edge from producing LogicVertex -> produced VarPordVertex OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD); - new OrderEdge(m_graphp, m_logicVxp, ordVxp, WEIGHT_NORMAL); + m_graphp->addHardEdge(m_logicVxp, ordVxp, WEIGHT_NORMAL); // Add edge from producing LogicVertex -> produced VarStdVertex OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); - new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); + m_graphp->addHardEdge(m_logicVxp, varVxp, WEIGHT_NORMAL); } else { // Sequential (clocked) logic // Add edge from produced VarPordVertex -> to producing LogicVertex OrderVarVertex* const ordVxp = getVarVertex(varscp, VarVertexType::PORD); - new OrderEdge(m_graphp, ordVxp, m_logicVxp, WEIGHT_NORMAL); + m_graphp->addHardEdge(ordVxp, m_logicVxp, WEIGHT_NORMAL); // Add edge from producing LogicVertex-> to produced VarStdVertex OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); - new OrderEdge(m_graphp, m_logicVxp, varVxp, WEIGHT_NORMAL); + m_graphp->addHardEdge(m_logicVxp, varVxp, WEIGHT_NORMAL); } } @@ -382,7 +354,7 @@ class OrderBuildVisitor final : public VNVisitor { // Ignore explicit sensitivities OrderVarVertex* const varVxp = getVarVertex(varscp, VarVertexType::STD); // Add edge from consumed VarStdVertex -> to consuming LogicVertex - new OrderEdge(m_graphp, varVxp, m_logicVxp, WEIGHT_MEDIUM); + m_graphp->addHardEdge(varVxp, m_logicVxp, WEIGHT_MEDIUM); } } else if (m_inPre) { // AstAssignPre logic @@ -390,17 +362,17 @@ class OrderBuildVisitor final : public VNVisitor { // This one is cutable (vs the producer) as there's only one such consumer, // but may be many producers OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE); - new OrderPreCutEdge(m_graphp, preVxp, m_logicVxp); + m_graphp->addSoftEdge(preVxp, m_logicVxp, WEIGHT_PRE); } else { // Sequential (clocked) logic // Add edge from consuming LogicVertex -> to consumed VarPreVertex // Generation of 'pre' because we want to indicate it should be before // AstAssignPre OrderVarVertex* const preVxp = getVarVertex(varscp, VarVertexType::PRE); - new OrderEdge(m_graphp, m_logicVxp, preVxp, WEIGHT_NORMAL); + m_graphp->addHardEdge(m_logicVxp, preVxp, WEIGHT_NORMAL); // Add edge from consuming LogicVertex -> to consumed VarPostVertex OrderVarVertex* const postVxp = getVarVertex(varscp, VarVertexType::POST); - new OrderEdge(m_graphp, m_logicVxp, postVxp, WEIGHT_POST); + m_graphp->addHardEdge(m_logicVxp, postVxp, WEIGHT_POST); } } } @@ -742,7 +714,7 @@ private: // Path from vertexp to a logic vertex; new edge. // Note we use the last edge's weight, not some function of // multiple edges - new OrderEdge(m_outGraphp, moveVxp, m_logic2move[toLVertexp], weight); + new V3GraphEdge(m_outGraphp, moveVxp, m_logic2move[toLVertexp], weight); madeDeps = true; } else { // This is an OrderVarVertex. @@ -767,7 +739,7 @@ private: // Create incoming edge, from previous logic that writes // this var, to the Vertex representing the (var,domain) - new OrderEdge(m_outGraphp, moveVxp, m_var2move[key], weight); + new V3GraphEdge(m_outGraphp, moveVxp, m_var2move[key], weight); madeDeps = true; } } diff --git a/src/V3OrderGraph.h b/src/V3OrderGraph.h index b08c0ff48..625cbbe3a 100644 --- a/src/V3OrderGraph.h +++ b/src/V3OrderGraph.h @@ -1,6 +1,6 @@ // -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* -// DESCRIPTION: Verilator: Block code ordering +// DESCRIPTION: Verilator: Ordering constraint graph // // Code available from: https://verilator.org // @@ -13,23 +13,52 @@ // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* -// OrderGraph Class Hierarchy: // -// V3GraphVertex -// OrderMoveVertex -// MTaskMoveVertex -// OrderEitherVertex -// OrderLogicVertex -// OrderVarVertex -// OrderVarStdVertex -// OrderVarPreVertex -// OrderVarPostVertex -// OrderVarPordVertex +// OrderGraph is a bipartite graph, with the two parts being formed of only OrderLogicVertex and +// OrderVarVertex vertices respectively (i.e.: edges are always between OrderLogicVertex and +// OrderVarVertex, and never between two OrderLogicVertex or OrderVarVertex). The graph represents +// both fine-grained dependencies, and additional ordering constraints between logic blocks and +// variables. The fact that OrderGraph is bipartite is important and we take advantage of this fact +// in various algorithms, so this property must be maintained. +// +// Both OrderLogicVertex and OrderVarVertex derives from OrderEitherVertex, so OrderGraph is +// composed only of OrderEitherVertex vertices. +// +// OrderLogicVertex holds a 'logic block', which is just some computational construct that is +// ordered as a single unit. Ordering of these logic blocks is determined by the variables they +// read and write, which is represented by the edges between OrderLogicVertex and OrderVarVertex +// instances (and hence the graph is bipartite). +// +// OrderVarVertex is abstract, and has various concrete subtypes that represent various ordering +// constraints imposed by variables accessed by logic blocks. The concrete subtypes and their +// roles are: +// +// OrderVarStdVertex: Data dependencies for combinational logic and delayed assignment +// updates (AssignPost). +// OrderVarPostVertex: Ensures all sequential logic blocks reading a signal do so before any +// combinational or delayed assignments update that signal. +// OrderVarPordVertex: Ensures a _d = _q AssignPre used to implement delayed (non-blocking) +// assignments is the first write of a _d, before any sequential blocks +// write to that _d. +// OrderVarPreVertex: This is an optimization. Try to ensure that a _d = _q AssignPre is the +// last read of a _q, after all reads of that _q by sequential logic. The +// model is still correct if we cannot satisfy this due to other interfering +// constraints. If respecting this constraint is possible, then combined +// with the OrderVarPordVertex constraint we get that all writes to _d are +// after all reads of a _q, which then allows us to eliminate the _d +// completely and assign to the _q directly. This means these delayed +// assignments can be implemented without temporary storage (the redundant +// storage is eliminated in V3LifePost). +// +// Ordering constraints are represented by directed edges, where the source of an edge needs to be +// ordered before the sink of an edge. A constraint can be either hard (must be satisfied), +// represented by a non cutable edge, or a constraint can be soft (ideally should be satisfied, but +// is ok not to if other hard constraints interfere), represented by a cutable edge. Edges +// otherwise carry no additional information. TODO: what about weight? +// +// Note: It is required for hard (non-cutable) constraints to form a DAG, but together with the +// soft constraints the graph can be arbitrary so long as it remains bipartite. // -// V3GraphEdge -// OrderEdge -// OrderPostCutEdge -// OrderPreCutEdge //************************************************************************* #ifndef VERILATOR_V3ORDERGRAPH_H_ @@ -41,13 +70,10 @@ #include "V3Ast.h" #include "V3Graph.h" -#include +class OrderLogicVertex; +class OrderVarVertex; -class OrderMoveVertex; -class OrderMoveVertexMaker; -class OrderMoveDomScope; - -//###################################################################### +//====================================================================== enum OrderWeights : uint8_t { WEIGHT_COMBO = 1, // Breakable combo logic @@ -57,26 +83,37 @@ enum OrderWeights : uint8_t { WEIGHT_NORMAL = 32 // High weight just so dot graph looks nice }; -//###################################################################### -// Graph types +//====================================================================== +// Graph type class OrderGraph final : public V3Graph { public: - OrderGraph() = default; - virtual ~OrderGraph() override = default; // METHODS - virtual void loopsVertexCb(V3GraphVertex* vertexp) override; + + // Methods to add edges representing constraints, utilizing the type system to help us ensure + // the graph remains bipartite. + inline void addHardEdge(OrderLogicVertex* fromp, OrderVarVertex* top, int weight); + inline void addHardEdge(OrderVarVertex* fromp, OrderLogicVertex* top, int weight); + inline void addSoftEdge(OrderLogicVertex* fromp, OrderVarVertex* top, int weight); + inline void addSoftEdge(OrderVarVertex* fromp, OrderLogicVertex* top, int weight); }; -//###################################################################### +//====================================================================== // Vertex types class OrderEitherVertex VL_NOT_FINAL : public V3GraphVertex { - AstSenTree* m_domainp; // Clock domain (nullptr = to be computed as we iterate) + // Event domain of vertex. For OrderLogicVertex this represents the conditions when the logic + // block must be executed. For OrderVarVertex, this is the union of the domains of all the + // OrderLogicVertex vertices that drive the variable. If initially set to nullptr (e.g.: all + // OrderVarVertex and those OrderLogicVertices that represent combinational logic), then the + // ordering algorithm will compute the domain automatically based on the edges representing + // data-flow (those between OrderLogicVertex and OrderVarStdVertex), otherwise the domain is + // as given (e.g.: for those OrderLogicVertices that represent clocked logic). + AstSenTree* m_domainp; protected: // CONSTRUCTOR - OrderEitherVertex(V3Graph* graphp, AstSenTree* domainp) + OrderEitherVertex(OrderGraph* graphp, AstSenTree* domainp) : V3GraphVertex{graphp} , m_domainp{domainp} {} virtual ~OrderEitherVertex() override = default; @@ -87,18 +124,23 @@ public: // ACCESSORS AstSenTree* domainp() const { return m_domainp; } - void domainp(AstSenTree* domainp) { m_domainp = domainp; } + void domainp(AstSenTree* domainp) { +#if VL_DEBUG + UASSERT(!m_domainp, "Domain should only be set once"); +#endif + m_domainp = domainp; + } }; class OrderLogicVertex final : public OrderEitherVertex { AstNode* const m_nodep; // The logic this vertex represents AstScope* const m_scopep; // Scope the logic is under - AstSenTree* const m_hybridp; + AstSenTree* const m_hybridp; // Additional sensitivities for hybrid combinational logic public: // CONSTRUCTOR - OrderLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* domainp, AstSenTree* hybridp, - AstNode* nodep) + OrderLogicVertex(OrderGraph* graphp, AstScope* scopep, AstSenTree* domainp, + AstSenTree* hybridp, AstNode* nodep) : OrderEitherVertex{graphp, domainp} , m_nodep{nodep} , m_scopep{scopep} @@ -131,7 +173,7 @@ class OrderVarVertex VL_NOT_FINAL : public OrderEitherVertex { public: // CONSTRUCTOR - OrderVarVertex(V3Graph* graphp, AstVarScope* vscp) + OrderVarVertex(OrderGraph* graphp, AstVarScope* vscp) : OrderEitherVertex{graphp, nullptr} , m_vscp{vscp} {} virtual ~OrderVarVertex() override = default; @@ -151,8 +193,8 @@ public: class OrderVarStdVertex final : public OrderVarVertex { public: // CONSTRUCTOR - OrderVarStdVertex(V3Graph* graphp, AstVarScope* varScp) - : OrderVarVertex{graphp, varScp} {} + OrderVarStdVertex(OrderGraph* graphp, AstVarScope* vscp) + : OrderVarVertex{graphp, vscp} {} virtual ~OrderVarStdVertex() override = default; // METHODS @@ -167,8 +209,8 @@ public: class OrderVarPreVertex final : public OrderVarVertex { public: // CONSTRUCTOR - OrderVarPreVertex(V3Graph* graphp, AstVarScope* varScp) - : OrderVarVertex{graphp, varScp} {} + OrderVarPreVertex(OrderGraph* graphp, AstVarScope* vscp) + : OrderVarVertex{graphp, vscp} {} virtual ~OrderVarPreVertex() override = default; // METHODS @@ -183,8 +225,8 @@ public: class OrderVarPostVertex final : public OrderVarVertex { public: // CONSTRUCTOR - OrderVarPostVertex(V3Graph* graphp, AstVarScope* varScp) - : OrderVarVertex{graphp, varScp} {} + OrderVarPostVertex(OrderGraph* graphp, AstVarScope* vscp) + : OrderVarVertex{graphp, vscp} {} virtual ~OrderVarPostVertex() override = default; // METHODS @@ -199,8 +241,8 @@ public: class OrderVarPordVertex final : public OrderVarVertex { public: // CONSTRUCTOR - OrderVarPordVertex(V3Graph* graphp, AstVarScope* varScp) - : OrderVarVertex{graphp, varScp} {} + OrderVarPordVertex(OrderGraph* graphp, AstVarScope* vscp) + : OrderVarVertex{graphp, vscp} {} virtual ~OrderVarPordVertex() override = default; // METHODS @@ -212,153 +254,36 @@ public: // LCOV_EXCL_STOP }; -//###################################################################### -// Edge types +//====================================================================== +// Edge type -class OrderEdge VL_NOT_FINAL : public V3GraphEdge { -public: +class OrderEdge final : public V3GraphEdge { + friend class OrderGraph; // Only the OrderGraph can create these // CONSTRUCTOR - OrderEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, int weight, - bool cutable = false) + OrderEdge(OrderGraph* graphp, OrderEitherVertex* fromp, OrderEitherVertex* top, int weight, + bool cutable) : V3GraphEdge{graphp, fromp, top, weight, cutable} {} virtual ~OrderEdge() override = default; -}; - -class OrderPostCutEdge final : public OrderEdge { - // Edge created from output of post assignment -public: - // CONSTRUCTOR - OrderPostCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) - : OrderEdge{graphp, fromp, top, WEIGHT_COMBO, CUTABLE} {} - virtual ~OrderPostCutEdge() override = default; // LCOV_EXCL_START // Debug code - virtual string dotColor() const override { return "palegreen"; } + virtual string dotColor() const override { return cutable() ? "green" : "red"; } // LCOV_EXCL_STOP }; -class OrderPreCutEdge final : public OrderEdge { - // Edge created from var_PREVAR->consuming logic vertex - // Always breakable, just results in performance loss - // in which case we can't optimize away the pre/post delayed assignments -public: - // CONSTRUCTOR - OrderPreCutEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top) - : OrderEdge{graphp, fromp, top, WEIGHT_PRE, CUTABLE} {} - virtual ~OrderPreCutEdge() override = default; +//====================================================================== +// Inline methods - // LCOV_EXCL_START // Debug code - virtual string dotColor() const override { return "khaki"; } - // LCOV_EXCL_STOP -}; - -//###################################################################### -//--- Following only under the move graph, not the main graph - -class OrderMoveVertex final : public V3GraphVertex { - enum OrderMState : uint8_t { POM_WAIT, POM_READY, POM_MOVED }; - - OrderLogicVertex* const m_logicp; - OrderMState m_state; // Movement state - OrderMoveDomScope* m_domScopep; // Domain/scope list information - -protected: - friend class OrderProcess; - friend class OrderMoveVertexMaker; - // These only contain the "next" item, - // for the head of the list, see the same var name under OrderProcess - V3ListEnt m_pomWaitingE; // List of nodes needing inputs to become ready - V3ListEnt m_readyVerticesE; // List of ready under domain/scope -public: - // CONSTRUCTORS - OrderMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp) - : V3GraphVertex{graphp} - , m_logicp{logicp} - , m_state{POM_WAIT} - , m_domScopep{nullptr} {} - virtual ~OrderMoveVertex() override = default; - - // METHODS - virtual string dotColor() const override { - if (logicp()) { - return logicp()->dotColor(); - } else { - return ""; - } - } - - virtual string name() const override { - string nm; - if (VL_UNCOVERABLE(!logicp())) { // Avoid crash when debugging - nm = "nul"; // LCOV_EXCL_LINE - } else { - nm = logicp()->name(); - nm += (string("\\nMV:") + " d=" + cvtToHex(logicp()->domainp()) - + " s=" + cvtToHex(logicp()->scopep())); - } - return nm; - } - OrderLogicVertex* logicp() const { return m_logicp; } - bool isWait() const { return m_state == POM_WAIT; } - void setReady() { - UASSERT(m_state == POM_WAIT, "Wait->Ready on node not in proper state"); - m_state = POM_READY; - } - void setMoved() { - UASSERT(m_state == POM_READY, "Ready->Moved on node not in proper state"); - m_state = POM_MOVED; - } - OrderMoveDomScope* domScopep() const { return m_domScopep; } - OrderMoveVertex* pomWaitingNextp() const { return m_pomWaitingE.nextp(); } - void domScopep(OrderMoveDomScope* ds) { m_domScopep = ds; } -}; - -// Similar to OrderMoveVertex, but modified for threaded code generation. -class MTaskMoveVertex final : public V3GraphVertex { - // This could be more compact, since we know m_varp and m_logicp - // cannot both be set. Each MTaskMoveVertex represents a logic node - // or a var node, it can't be both. - OrderLogicVertex* const m_logicp; // Logic represented by this vertex - const OrderEitherVertex* const m_varp; // Var represented by this vertex - const AstSenTree* const m_domainp; - -public: - MTaskMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp, const OrderEitherVertex* varp, - const AstSenTree* domainp) - : V3GraphVertex{graphp} - , m_logicp{logicp} - , m_varp{varp} - , m_domainp{domainp} { - UASSERT(!(logicp && varp), "MTaskMoveVertex: logicp and varp may not both be set!\n"); - } - virtual ~MTaskMoveVertex() override = default; - - // ACCESSORS - OrderLogicVertex* logicp() const { return m_logicp; } - const OrderEitherVertex* varp() const { return m_varp; } - const AstScope* scopep() const { return m_logicp ? m_logicp->scopep() : nullptr; } - const AstSenTree* domainp() const { return m_domainp; } - - virtual string dotColor() const override { - if (logicp()) { - return logicp()->dotColor(); - } else { - return "yellow"; - } - } - virtual string name() const override { - string nm; - if (logicp()) { - nm = logicp()->name(); - nm += (string("\\nMV:") + " d=" + cvtToHex(logicp()->domainp()) + " s=" - + cvtToHex(logicp()->scopep()) - // "color()" represents the mtask ID. - + "\\nt=" + cvtToStr(color())); - } else { - nm = "nolog\\nt=" + cvtToStr(color()); - } - return nm; - } -}; +void OrderGraph::addHardEdge(OrderLogicVertex* fromp, OrderVarVertex* top, int weight) { + new OrderEdge{this, fromp, top, weight, /* cutable: */ false}; +} +void OrderGraph::addHardEdge(OrderVarVertex* fromp, OrderLogicVertex* top, int weight) { + new OrderEdge{this, fromp, top, weight, /* cutable: */ false}; +} +void OrderGraph::addSoftEdge(OrderLogicVertex* fromp, OrderVarVertex* top, int weight) { + new OrderEdge{this, fromp, top, weight, /* cutable: */ true}; +} +void OrderGraph::addSoftEdge(OrderVarVertex* fromp, OrderLogicVertex* top, int weight) { + new OrderEdge{this, fromp, top, weight, /* cutable: */ true}; +} #endif // Guard diff --git a/src/V3OrderMoveGraph.h b/src/V3OrderMoveGraph.h new file mode 100644 index 000000000..fea02866c --- /dev/null +++ b/src/V3OrderMoveGraph.h @@ -0,0 +1,141 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Ordering graph +// +// 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 +// +//************************************************************************* +// +// TODO: Fix comment +// +//************************************************************************* + +#ifndef VERILATOR_V3ORDERMOVEGRAPH_H_ +#define VERILATOR_V3ORDERMOVEGRAPH_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3Graph.h" +#include "V3OrderGraph.h" + +#include + +class OrderMoveDomScope; + +class OrderMoveVertex final : public V3GraphVertex { + enum OrderMState : uint8_t { POM_WAIT, POM_READY, POM_MOVED }; + + OrderLogicVertex* const m_logicp; + OrderMState m_state; // Movement state + OrderMoveDomScope* m_domScopep; // Domain/scope list information + +protected: + friend class OrderProcess; + friend class OrderMoveVertexMaker; + // These only contain the "next" item, + // for the head of the list, see the same var name under OrderProcess + V3ListEnt m_pomWaitingE; // List of nodes needing inputs to become ready + V3ListEnt m_readyVerticesE; // List of ready under domain/scope +public: + // CONSTRUCTORS + OrderMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp) + : V3GraphVertex{graphp} + , m_logicp{logicp} + , m_state{POM_WAIT} + , m_domScopep{nullptr} {} + virtual ~OrderMoveVertex() override = default; + + // METHODS + virtual string dotColor() const override { + if (logicp()) { + return logicp()->dotColor(); + } else { + return ""; + } + } + + virtual string name() const override { + string nm; + if (VL_UNCOVERABLE(!logicp())) { // Avoid crash when debugging + nm = "nul"; // LCOV_EXCL_LINE + } else { + nm = logicp()->name(); + nm += (string("\\nMV:") + " d=" + cvtToHex(logicp()->domainp()) + + " s=" + cvtToHex(logicp()->scopep())); + } + return nm; + } + OrderLogicVertex* logicp() const { return m_logicp; } + bool isWait() const { return m_state == POM_WAIT; } + void setReady() { + UASSERT(m_state == POM_WAIT, "Wait->Ready on node not in proper state"); + m_state = POM_READY; + } + void setMoved() { + UASSERT(m_state == POM_READY, "Ready->Moved on node not in proper state"); + m_state = POM_MOVED; + } + OrderMoveDomScope* domScopep() const { return m_domScopep; } + OrderMoveVertex* pomWaitingNextp() const { return m_pomWaitingE.nextp(); } + void domScopep(OrderMoveDomScope* ds) { m_domScopep = ds; } +}; + +// Similar to OrderMoveVertex, but modified for threaded code generation. +class MTaskMoveVertex final : public V3GraphVertex { + // This could be more compact, since we know m_varp and m_logicp + // cannot both be set. Each MTaskMoveVertex represents a logic node + // or a var node, it can't be both. + OrderLogicVertex* const m_logicp; // Logic represented by this vertex + const OrderEitherVertex* const m_varp; // Var represented by this vertex + const AstSenTree* const m_domainp; + +public: + MTaskMoveVertex(V3Graph* graphp, OrderLogicVertex* logicp, const OrderEitherVertex* varp, + const AstSenTree* domainp) + : V3GraphVertex{graphp} + , m_logicp{logicp} + , m_varp{varp} + , m_domainp{domainp} { + UASSERT(!(logicp && varp), "MTaskMoveVertex: logicp and varp may not both be set!\n"); + } + virtual ~MTaskMoveVertex() override = default; + + // ACCESSORS + OrderLogicVertex* logicp() const { return m_logicp; } + const OrderEitherVertex* varp() const { return m_varp; } + const AstScope* scopep() const { return m_logicp ? m_logicp->scopep() : nullptr; } + const AstSenTree* domainp() const { return m_domainp; } + + virtual string dotColor() const override { + if (logicp()) { + return logicp()->dotColor(); + } else { + return "yellow"; + } + } + virtual string name() const override { + string nm; + if (logicp()) { + nm = logicp()->name(); + nm += (string("\\nMV:") + " d=" + cvtToHex(logicp()->domainp()) + " s=" + + cvtToHex(logicp()->scopep()) + // "color()" represents the mtask ID. + + "\\nt=" + cvtToStr(color())); + } else { + nm = "nolog\\nt=" + cvtToStr(color()); + } + return nm; + } +}; + +#endif // Guard diff --git a/src/V3Partition.h b/src/V3Partition.h index c358599f4..d8c8f64fa 100644 --- a/src/V3Partition.h +++ b/src/V3Partition.h @@ -22,6 +22,7 @@ #include "V3Graph.h" #include "V3OrderGraph.h" +#include "V3OrderMoveGraph.h" #include #include diff --git a/src/V3PartitionGraph.h b/src/V3PartitionGraph.h index b0eac05d3..896ee6826 100644 --- a/src/V3PartitionGraph.h +++ b/src/V3PartitionGraph.h @@ -21,7 +21,7 @@ #include "verilatedos.h" #include "V3Graph.h" -#include "V3OrderGraph.h" +#include "V3OrderMoveGraph.h" #include From 546aeab9f2c35783aa3bbab1ab2710aea2ee0ca3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 25 Aug 2022 15:17:38 +0100 Subject: [PATCH 031/177] V3Order: Minor refactoring for clarity Refactor ProcessMoveBuildGraph utilizing the fact that OrderGraph is a bipartite graph, also remove unnecessary unordered_map and distribute variable domain map. No functional change. --- src/V3Order.cpp | 151 ++++++++++++++++++++++-------------------------- 1 file changed, 69 insertions(+), 82 deletions(-) diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 46eb0de7c..421eae567 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -542,10 +542,9 @@ inline std::ostream& operator<<(std::ostream& lhs, const OrderMoveDomScope& rhs) template class ProcessMoveBuildGraph final { - // ProcessMoveBuildGraph takes as input the fine-grained graph of - // OrderLogicVertex, OrderVarVertex, etc; this is 'm_graph' in - // OrderVisitor. It produces a slightly coarsened graph to drive the - // code scheduling. + // ProcessMoveBuildGraph takes as input the fine-grained bipartite OrderGraph of + // OrderLogicVertex and OrderVarVertex vertices. It produces a slightly coarsened graph to + // drive the code scheduling. // // * For the serial code scheduler, the new graph contains // nodes of type OrderMoveVertex. @@ -561,8 +560,7 @@ class ProcessMoveBuildGraph final { // AstSenTree::user1p() -> AstSenTree: Original AstSenTree for trigger // TYPES - using VxDomPair = std::pair; - using Logic2Move = std::unordered_map; + using DomainMap = std::map; public: class MoveVertexMaker VL_NOT_FINAL { @@ -574,31 +572,28 @@ public: const OrderEitherVertex* varVertexp, const AstSenTree* domainp) = 0; - virtual void freeVertexp(T_MoveVertex* freeMep) = 0; }; private: // MEMBERS - const V3Graph* m_graphp; // Input graph of OrderLogicVertex's etc + const OrderGraph* const m_graphp; // Input OrderGraph + V3Graph* const m_outGraphp; // Output graph of T_MoveVertex vertices // Map from Trigger reference AstSenItem to the original AstSenTree const std::unordered_map& m_trigToSen; - V3Graph* m_outGraphp; // Output graph of T_MoveVertex's MoveVertexMaker* const m_vxMakerp; // Factory class for T_MoveVertex's - Logic2Move m_logic2move; // Map Logic to Vertex - // Maps an (original graph vertex, domain) pair to a T_MoveVertex - // Not std::unordered_map, because std::pair doesn't provide std::hash - std::map m_var2move; + // Storage for domain -> T_MoveVertex, maps held in OrderVarVertex::userp() + std::deque m_domainMaps; public: // CONSTRUCTORS ProcessMoveBuildGraph( - const V3Graph* logicGraphp, // Input graph of OrderLogicVertex etc. - const std::unordered_map& trigToSen, + const OrderGraph* logicGraphp, // Input graph of OrderLogicVertex etc. V3Graph* outGraphp, // Output graph of T_MoveVertex's + const std::unordered_map& trigToSen, MoveVertexMaker* vxMakerp) : m_graphp{logicGraphp} - , m_trigToSen{trigToSen} , m_outGraphp{outGraphp} + , m_trigToSen{trigToSen} , m_vxMakerp{vxMakerp} {} virtual ~ProcessMoveBuildGraph() = default; @@ -616,22 +611,20 @@ public: // already created that pair, in which case, we've already // done the forward search, so stop. - // For each logic node, make a T_MoveVertex + // For each logic vertex, make a T_MoveVertex, for each variable vertex, allocate storage for (V3GraphVertex* itp = m_graphp->verticesBeginp(); itp; itp = itp->verticesNextp()) { - if (OrderLogicVertex* const lvertexp = dynamic_cast(itp)) { - T_MoveVertex* const moveVxp - = m_vxMakerp->makeVertexp(lvertexp, nullptr, lvertexp->domainp()); - if (moveVxp) { - // Cross link so we can find it later - m_logic2move[lvertexp] = moveVxp; - } + if (OrderLogicVertex* const lvtxp = dynamic_cast(itp)) { + lvtxp->userp(m_vxMakerp->makeVertexp(lvtxp, nullptr, lvtxp->domainp())); + } else { + // This is an OrderVarVertex + m_domainMaps.emplace_back(); + itp->userp(&m_domainMaps.back()); } } // Build edges between logic vertices for (V3GraphVertex* itp = m_graphp->verticesBeginp(); itp; itp = itp->verticesNextp()) { - if (OrderLogicVertex* const lvertexp = dynamic_cast(itp)) { - T_MoveVertex* const moveVxp = m_logic2move[lvertexp]; - if (moveVxp) iterate(moveVxp, lvertexp, lvertexp->domainp()); + if (OrderLogicVertex* const lvtxp = dynamic_cast(itp)) { + iterateLogicVertex(lvtxp); } } } @@ -695,61 +688,62 @@ private: return fromSenItemp->edgeType().exclusiveEdge(toSenItemp->edgeType()); } - // Return true if moveVxp has downstream dependencies - bool iterate(T_MoveVertex* moveVxp, const V3GraphVertex* origVxp, AstSenTree* domainp) { - bool madeDeps = false; - // Search forward from given original vertex, making new edges from - // moveVxp forward - for (V3GraphEdge* edgep = origVxp->outBeginp(); edgep; edgep = edgep->outNextp()) { - if (edgep->weight() == 0) { // Was cut - continue; - } - const int weight = edgep->weight(); - if (const OrderLogicVertex* const toLVertexp - = dynamic_cast(edgep->top())) { + void iterateLogicVertex(const OrderLogicVertex* lvtxp) { + AstSenTree* const domainp = lvtxp->domainp(); + T_MoveVertex* const lMoveVtxp = static_cast(lvtxp->userp()); + // Search forward from lvtxp, making new edges from lMoveVtxp forward + for (V3GraphEdge* edgep = lvtxp->outBeginp(); edgep; edgep = edgep->outNextp()) { + if (edgep->weight() == 0) continue; // Was cut - // Do not construct dependencies across exclusive domains. - if (domainsExclusive(domainp, toLVertexp->domainp())) continue; + // OrderGraph is a bipartite graph, so we know it's an OrderVarVertex + const OrderVarVertex* const vvtxp = static_cast(edgep->top()); - // Path from vertexp to a logic vertex; new edge. - // Note we use the last edge's weight, not some function of - // multiple edges - new V3GraphEdge(m_outGraphp, moveVxp, m_logic2move[toLVertexp], weight); - madeDeps = true; - } else { - // This is an OrderVarVertex. - const V3GraphVertex* nonLogicVxp = edgep->top(); - const VxDomPair key(nonLogicVxp, domainp); - if (!m_var2move[key]) { - const OrderVarVertex* const eithp - = static_cast(nonLogicVxp); - T_MoveVertex* const newMoveVxp - = m_vxMakerp->makeVertexp(nullptr, eithp, domainp); - m_var2move[key] = newMoveVxp; + // Look up T_MoveVertex for this domain on this variable + DomainMap& mapp = *static_cast(vvtxp->userp()); + const auto pair = mapp.emplace(domainp, nullptr); + // Reference to the mapped T_MoveVertex + T_MoveVertex*& vMoveVtxp = pair.first->second; - // Find downstream logics that depend on (var, domain) - if (!iterate(newMoveVxp, edgep->top(), domainp)) { - // No downstream dependencies, so remove this - // intermediate vertex. - m_var2move[key] = nullptr; - m_vxMakerp->freeVertexp(newMoveVxp); - continue; - } - } + // On first encounter, visit downstream logic dependent on this (var, domain) + if (pair.second) vMoveVtxp = iterateVarVertex(vvtxp, domainp); - // Create incoming edge, from previous logic that writes - // this var, to the Vertex representing the (var,domain) - new V3GraphEdge(m_outGraphp, moveVxp, m_var2move[key], weight); - madeDeps = true; - } + // If no downstream dependents from this variable, then there is no need to add this + // variable as a dependent. + if (!vMoveVtxp) continue; + + // Add this (variable, domain) as dependent of the logic that writes it. + new V3GraphEdge{m_outGraphp, lMoveVtxp, vMoveVtxp, 1}; } - return madeDeps; } + + // Return the T_MoveVertex for this (var, domain) pair, iff it has downstream dependencies, + // otherwise return nullptr. + T_MoveVertex* iterateVarVertex(const OrderVarVertex* vvtxp, AstSenTree* domainp) { + T_MoveVertex* vMoveVtxp = nullptr; + // Search forward from vvtxp, making new edges from vMoveVtxp forward + for (V3GraphEdge* edgep = vvtxp->outBeginp(); edgep; edgep = edgep->outNextp()) { + if (edgep->weight() == 0) continue; // Was cut + + // OrderGraph is a bipartite graph, so we know it's an OrderLogicVertex + const OrderLogicVertex* const lvtxp + = static_cast(edgep->top()); + + // Do not construct dependencies across exclusive domains. + if (domainsExclusive(domainp, lvtxp->domainp())) continue; + + // there is a path from this vvtx to a logic vertex. Add the new edge. + if (!vMoveVtxp) vMoveVtxp = m_vxMakerp->makeVertexp(nullptr, vvtxp, domainp); + T_MoveVertex* const lMoveVxp = static_cast(lvtxp->userp()); + new V3GraphEdge{m_outGraphp, vMoveVtxp, lMoveVxp, 1}; + } + return vMoveVtxp; + } + VL_UNCOPYABLE(ProcessMoveBuildGraph); }; -//###################################################################### -// OrderMoveVertexMaker and related +// ###################################################################### +// OrderMoveVertexMaker and related class OrderMoveVertexMaker final : public ProcessMoveBuildGraph::MoveVertexMaker { // MEMBERS @@ -770,10 +764,6 @@ public: resultp->m_pomWaitingE.pushBack(*m_pomWaitingp, resultp); return resultp; } - virtual void freeVertexp(OrderMoveVertex* freeMep) override { - freeMep->m_pomWaitingE.unlink(*m_pomWaitingp, freeMep); - freeMep->unlinkDelete(m_pomGraphp); - } private: VL_UNCOPYABLE(OrderMoveVertexMaker); @@ -791,9 +781,6 @@ public: const AstSenTree* domainp) override { return new MTaskMoveVertex(m_pomGraphp, lvertexp, varVertexp, domainp); } - virtual void freeVertexp(MTaskMoveVertex* freeMep) override { - freeMep->unlinkDelete(m_pomGraphp); - } private: VL_UNCOPYABLE(OrderMTaskMoveVertexMaker); @@ -1109,7 +1096,7 @@ void OrderProcess::processMoveBuildGraph() { m_pomGraph.userClearVertices(); OrderMoveVertexMaker createOrderMoveVertex(&m_pomGraph, &m_pomWaiting); - ProcessMoveBuildGraph serialPMBG(&m_graph, m_trigToSen, &m_pomGraph, + ProcessMoveBuildGraph serialPMBG(&m_graph, &m_pomGraph, m_trigToSen, &createOrderMoveVertex); serialPMBG.build(); } @@ -1326,7 +1313,7 @@ void OrderProcess::processMTasks() { // This is quite similar to the 'm_pomGraph' of the serial code gen: V3Graph logicGraph; OrderMTaskMoveVertexMaker create_mtask_vertex(&logicGraph); - ProcessMoveBuildGraph mtask_pmbg(&m_graph, m_trigToSen, &logicGraph, + ProcessMoveBuildGraph mtask_pmbg(&m_graph, &logicGraph, m_trigToSen, &create_mtask_vertex); mtask_pmbg.build(); From 881c3f6e40aa50050a40cbf097f446faed56289d Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 26 Aug 2022 16:52:28 +0100 Subject: [PATCH 032/177] Minor optimization of PartContraction Remove rarely used debug code from initialization loop. --- src/V3Partition.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index b6ab138b0..b31a3967a 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -1270,6 +1270,18 @@ public: // METHODS void go() { + if (m_slowAsserts) { + // Check there are no redundant edges + for (V3GraphVertex* itp = m_mtasksp->verticesBeginp(); itp; + itp = itp->verticesNextp()) { + std::unordered_set neighbors; + for (V3GraphEdge* edgep = itp->outBeginp(); edgep; edgep = edgep->outNextp()) { + const bool first = neighbors.insert(edgep->top()).second; + UASSERT_OBJ(first, itp, "Redundant edge found in input to PartContraction()"); + } + } + } + unsigned maxMTasks = v3Global.opt.threadsMaxMTasks(); if (maxMTasks == 0) { // Unspecified so estimate if (v3Global.opt.threads() > 1) { @@ -1290,15 +1302,9 @@ public: // - Incrementally recompute critical paths near the merged mtask. for (V3GraphVertex* itp = m_mtasksp->verticesBeginp(); itp; itp = itp->verticesNextp()) { - itp->userp(nullptr); // Reset user value. Used by PartPropagateCp. - std::unordered_set neighbors; + itp->userp(nullptr); // Reset user value while we are here. Used by PartPropagateCp. for (V3GraphEdge* edgep = itp->outBeginp(); edgep; edgep = edgep->outNextp()) { m_sb.add(static_cast(edgep)); - if (m_slowAsserts) { - UASSERT_OBJ(neighbors.find(edgep->top()) == neighbors.end(), itp, - "Redundant edge found in input to PartContraction()"); - } - neighbors.insert(edgep->top()); } siblingPairFromRelatives(itp); siblingPairFromRelatives(itp); From ebbe24966c769891e819da9e5ff9af3c77a7c90b Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 26 Aug 2022 18:14:53 +0100 Subject: [PATCH 033/177] Remove unnecessary virtual methods --- src/V3Order.cpp | 10 +++------- src/V3Partition.cpp | 9 +++------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 421eae567..d2bc97118 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -790,7 +790,7 @@ class OrderVerticesByDomainThenScope final { PartPtrIdMap m_ids; public: - virtual bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const { + bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const { const MTaskMoveVertex* const l_vxp = dynamic_cast(lhsp); const MTaskMoveVertex* const r_vxp = dynamic_cast(rhsp); uint64_t l_id = m_ids.findId(l_vxp->domainp()); @@ -803,14 +803,10 @@ public: } }; -class MTaskVxIdLessThan final { -public: - MTaskVxIdLessThan() = default; - virtual ~MTaskVxIdLessThan() = default; - +struct MTaskVxIdLessThan final { // Sort vertex's, which must be AbstractMTask's, into a deterministic // order by comparing their serial IDs. - virtual bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const { + bool operator()(const V3GraphVertex* lhsp, const V3GraphVertex* rhsp) const { const AbstractMTask* const lmtaskp = dynamic_cast(lhsp); const AbstractMTask* const rmtaskp = dynamic_cast(rhsp); return lmtaskp->id() < rmtaskp->id(); diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index b31a3967a..1d8f00438 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -394,11 +394,8 @@ private: // Sort AbstractMTask objects into deterministic order by calling id() // which is a unique and stable serial number. -class MTaskIdLessThan final { -public: - MTaskIdLessThan() = default; - virtual ~MTaskIdLessThan() = default; - virtual bool operator()(const AbstractMTask* lhsp, const AbstractMTask* rhsp) const { +struct MTaskIdLessThan final { + bool operator()(const AbstractMTask* lhsp, const AbstractMTask* rhsp) const { return lhsp->id() < rhsp->id(); } }; @@ -727,7 +724,7 @@ class OrderByPtrId final { PartPtrIdMap m_ids; public: - virtual bool operator()(const OrderVarStdVertex* lhsp, const OrderVarStdVertex* rhsp) const { + bool operator()(const OrderVarStdVertex* lhsp, const OrderVarStdVertex* rhsp) const { const uint64_t l_id = m_ids.findId(lhsp); const uint64_t r_id = m_ids.findId(rhsp); return l_id < r_id; From 505bba14ebb5e53f9685b4df9674e646321d9e9f Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 26 Aug 2022 18:22:30 +0100 Subject: [PATCH 034/177] Improve PartFixDataHazards for clarity and speed. - Use modern C++ - Implement OrderLogicVertex->LogicMTask map with OrderLogicVertex::userp(), insteas of std::unordered_map - Simplify data structures - Simplify code and assert properties No functional change. --- src/V3Partition.cpp | 190 +++++++++++++++++++------------------------- 1 file changed, 83 insertions(+), 107 deletions(-) diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index 1d8f00438..666e4f4b2 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -1937,101 +1937,77 @@ private: class PartFixDataHazards final { private: // TYPES - using LogicMTaskSet = std::set; - using TasksByRank = std::map; + using TasksByRank = std::map>; using OvvSet = std::set; - using Olv2MTaskMap = std::unordered_map; // MEMBERS V3Graph* const m_mtasksp; // Mtask graph - Olv2MTaskMap m_olv2mtask; // Map OrderLogicVertex to LogicMTask who wraps it - unsigned m_mergesDone = 0; // Number of MTasks merged. For stats only. public: // CONSTRUCTORs explicit PartFixDataHazards(V3Graph* mtasksp) : m_mtasksp{mtasksp} {} // METHODS private: - void findAdjacentTasks(OvvSet::iterator ovvIt, TasksByRank* tasksByRankp) { + void findAdjacentTasks(const OrderVarStdVertex* varVtxp, TasksByRank& tasksByRank) { // Find all writer tasks for this variable, group by rank. - for (V3GraphEdge* edgep = (*ovvIt)->inBeginp(); edgep; edgep = edgep->inNextp()) { - const OrderLogicVertex* const logicp = dynamic_cast(edgep->fromp()); - if (!logicp) continue; - LogicMTask* const writerMtaskp = m_olv2mtask.at(logicp); - (*tasksByRankp)[writerMtaskp->rank()].insert(writerMtaskp); + for (V3GraphEdge* edgep = varVtxp->inBeginp(); edgep; edgep = edgep->inNextp()) { + if (const auto* const logicVtxp = dynamic_cast(edgep->fromp())) { + LogicMTask* const writerMtaskp = static_cast(logicVtxp->userp()); + tasksByRank[writerMtaskp->rank()].insert(writerMtaskp); + } } // Find all reader tasks for this variable, group by rank. - for (V3GraphEdge* edgep = (*ovvIt)->outBeginp(); edgep; edgep = edgep->outNextp()) { - const OrderLogicVertex* const logicp = dynamic_cast(edgep->fromp()); - if (!logicp) continue; - LogicMTask* const readerMtaskp = m_olv2mtask.at(logicp); - (*tasksByRankp)[readerMtaskp->rank()].insert(readerMtaskp); + for (V3GraphEdge* edgep = varVtxp->outBeginp(); edgep; edgep = edgep->outNextp()) { + if (const auto* const logicVtxp = dynamic_cast(edgep->fromp())) { + LogicMTask* const readerMtaskp = static_cast(logicVtxp->userp()); + tasksByRank[readerMtaskp->rank()].insert(readerMtaskp); + } } } - void mergeSameRankTasks(TasksByRank* tasksByRankp) { - LogicMTask* lastMergedp = nullptr; - for (TasksByRank::iterator rankIt = tasksByRankp->begin(); rankIt != tasksByRankp->end(); - ++rankIt) { + void mergeSameRankTasks(const TasksByRank& tasksByRank) { + LogicMTask* lastRecipientp = nullptr; + for (const auto& pair : tasksByRank) { // Find the largest node at this rank, merge into it. (If we // happen to find a huge node, this saves time in // partRedirectEdgesFrom() versus merging into an arbitrary node.) - LogicMTask* mergedp = nullptr; - for (LogicMTaskSet::iterator it = rankIt->second.begin(); it != rankIt->second.end(); - ++it) { - LogicMTask* const mtaskp = *it; - if (mergedp) { - if (mergedp->cost() < mtaskp->cost()) mergedp = mtaskp; - } else { - mergedp = mtaskp; - } + LogicMTask* recipientp = nullptr; + for (LogicMTask* const mtaskp : pair.second) { + if (!recipientp || (recipientp->cost() < mtaskp->cost())) recipientp = mtaskp; } - rankIt->second.erase(mergedp); + UASSERT_OBJ(!lastRecipientp || (lastRecipientp->rank() < recipientp->rank()), + recipientp, "Merging must be on lower rank"); - while (!rankIt->second.empty()) { - const auto begin = rankIt->second.cbegin(); - LogicMTask* const donorp = *begin; - UASSERT_OBJ(donorp != mergedp, donorp, "Donor can't be merged edge"); - rankIt->second.erase(begin); - // Merge donorp into mergedp. - // Fix up the map, so donor's OLVs map to mergedp - for (LogicMTask::VxList::const_iterator tmvit = donorp->vertexListp()->begin(); - tmvit != donorp->vertexListp()->end(); ++tmvit) { - const MTaskMoveVertex* const tmvp = *tmvit; - const OrderLogicVertex* const logicp = tmvp->logicp(); - if (logicp) m_olv2mtask[logicp] = mergedp; + for (LogicMTask* const donorp : pair.second) { + // Merge donor into recipient. + if (donorp == recipientp) continue; + // Fix up the map, so donor's OLVs map to recipientp + for (const MTaskMoveVertex* const tmvp : *(donorp->vertexListp())) { + tmvp->logicp()->userp(recipientp); } - // Move all vertices from donorp to mergedp - mergedp->moveAllVerticesFrom(donorp); + // Move all vertices from donorp to recipientp + recipientp->moveAllVerticesFrom(donorp); // Redirect edges from donorp to recipientp, delete donorp - partRedirectEdgesFrom(m_mtasksp, mergedp, donorp, nullptr); - ++m_mergesDone; + partRedirectEdgesFrom(m_mtasksp, recipientp, donorp, nullptr); } - if (lastMergedp) { - UASSERT_OBJ(lastMergedp->rank() < mergedp->rank(), mergedp, - "Merging must be on lower rank"); - if (!lastMergedp->hasRelativeMTask(mergedp)) { - new MTaskEdge(m_mtasksp, lastMergedp, mergedp, 1); - } + if (lastRecipientp && !lastRecipientp->hasRelativeMTask(recipientp)) { + new MTaskEdge{m_mtasksp, lastRecipientp, recipientp, 1}; } - lastMergedp = mergedp; + lastRecipientp = recipientp; } } bool hasDpiHazard(LogicMTask* mtaskp) { - for (LogicMTask::VxList::const_iterator it = mtaskp->vertexListp()->begin(); - it != mtaskp->vertexListp()->end(); ++it) { - if (!(*it)->logicp()) continue; - AstNode* const nodep = (*it)->logicp()->nodep(); - // NOTE: We don't handle DPI exports. If testbench code calls a - // DPI-exported function at any time during eval() we may have - // a data hazard. (Likewise in non-threaded mode if an export - // messes with an ordered variable we're broken.) + for (const MTaskMoveVertex* const moveVtxp : *(mtaskp->vertexListp())) { + if (OrderLogicVertex* const lvtxp = moveVtxp->logicp()) { + // NOTE: We don't handle DPI exports. If testbench code calls a + // DPI-exported function at any time during eval() we may have + // a data hazard. (Likewise in non-threaded mode if an export + // messes with an ordered variable we're broken.) - // Find all calls to DPI-imported functions, we can put those - // into a serial order at least. That should solve the most - // likely DPI-related data hazards. - if (DpiImportCallVisitor(nodep).hasDpiHazard()) { // - return true; + // Find all calls to DPI-imported functions, we can put those + // into a serial order at least. That should solve the most + // likely DPI-related data hazards. + if (DpiImportCallVisitor{lvtxp->nodep()}.hasDpiHazard()) return true; } } return false; @@ -2039,36 +2015,40 @@ private: public: void go() { - uint64_t startUsecs = 0; - if (debug() >= 3) startUsecs = V3Os::timeUsecs(); - // Build an OLV->mtask map and a set of OVVs OrderByPtrId ovvOrder; OvvSet ovvSet(ovvOrder); // OVV's which wrap systemC vars will be handled slightly specially OvvSet ovvSetSystemC(ovvOrder); + // TODO: This loop is entirely redundant as we iterate every vertex of the graph + // during ranking below anyway, so we could do all this work in the body of that + // loop. However... the order in which OrderVarStdVertex are added to ovvSet can + // have a significant impact on model performance (+/-15% was observed), and doing + // it this way happens to be best on some benchmarks. Need to investigate and find + // a better way that yields consistent performance. for (V3GraphVertex* vxp = m_mtasksp->verticesBeginp(); vxp; vxp = vxp->verticesNextp()) { LogicMTask* const mtaskp = static_cast(vxp); - // Should be only one MTaskMoveVertex in each mtask at this - // stage, but whatever, write it as a loop: - for (LogicMTask::VxList::const_iterator it = mtaskp->vertexListp()->begin(); - it != mtaskp->vertexListp()->end(); ++it) { - const MTaskMoveVertex* const tmvp = *it; - if (const OrderLogicVertex* const logicp = tmvp->logicp()) { - m_olv2mtask[logicp] = mtaskp; - // Look at downstream vars. - for (V3GraphEdge* edgep = logicp->outBeginp(); edgep; - edgep = edgep->outNextp()) { - // Only consider OrderVarStdVertex which reflects - // an actual lvalue assignment; the others do not. - const OrderVarStdVertex* const ovvp - = dynamic_cast(edgep->top()); - if (!ovvp) continue; - if (ovvp->vscp()->varp()->isSc()) { - ovvSetSystemC.insert(ovvp); + + // Set up the OrderLogicVertex -> LogicMTask map + // Entry and exit MTasks have no MTaskMoveVertices under them, so move on + if (mtaskp->vertexListp()->empty()) continue; + // Otherwise there should be only one MTaskMoveVertex in each MTask at this stage + UASSERT_OBJ(mtaskp->vertexListp()->size() == 1, mtaskp, "Multiple MTaskMoveVertex"); + const MTaskMoveVertex* const moveVtxp = mtaskp->vertexListp()->front(); + if (OrderLogicVertex* const lvtxp = moveVtxp->logicp()) { + // Set up mapping back to the MTask from the OrderLogicVertex + lvtxp->userp(mtaskp); + // Look at downstream variables + for (V3GraphEdge *edgep = lvtxp->outBeginp(), *nextp; edgep; edgep = nextp) { + nextp = edgep->outNextp(); + // Only consider OrderVarStdVertex which reflects + // an actual lvalue assignment; the others do not. + if (const auto* const vvtxp = dynamic_cast(edgep->top())) { + if (vvtxp->vscp()->varp()->isSc()) { + ovvSetSystemC.insert(vvtxp); } else { - ovvSet.insert(ovvp); + ovvSet.insert(vvtxp); } } } @@ -2082,13 +2062,14 @@ public: // one large design.) { GraphStreamUnordered serialize(m_mtasksp); - const V3GraphVertex* vertexp; - while ((vertexp = serialize.nextp())) { + while (LogicMTask* const mtaskp + = const_cast(static_cast(serialize.nextp()))) { + // Compute and assign rank uint32_t rank = 0; - for (V3GraphEdge* edgep = vertexp->inBeginp(); edgep; edgep = edgep->inNextp()) { + for (V3GraphEdge* edgep = mtaskp->inBeginp(); edgep; edgep = edgep->inNextp()) { rank = std::max(edgep->fromp()->rank() + 1, rank); } - const_cast(vertexp)->rank(rank); + mtaskp->rank(rank); } } @@ -2105,14 +2086,14 @@ public: // NOTE: we don't update the CP's stored in the LogicMTasks to // reflect the changes we make to the graph. That's OK, as we // haven't yet initialized CPs when we call this routine. - for (OvvSet::iterator ovvit = ovvSet.begin(); ovvit != ovvSet.end(); ++ovvit) { + for (const OrderVarStdVertex* const varVtxp : ovvSet) { // Build a set of mtasks, per rank, which access this var. // Within a rank, sort by MTaskID to avoid nondeterminism. TasksByRank tasksByRank; // Find all reader and writer tasks for this variable, add to // tasksByRank. - findAdjacentTasks(ovvit, &tasksByRank); + findAdjacentTasks(varVtxp, tasksByRank); // Merge all writer and reader tasks from same rank together. // @@ -2129,7 +2110,7 @@ public: // and it seems to. It also creates fairly few edges. We don't // want to create tons of edges here, doing so is not nice to // the main edge contraction pass. - mergeSameRankTasks(&tasksByRank); + mergeSameRankTasks(tasksByRank); } // Handle SystemC vars just a little differently. Instead of @@ -2145,11 +2126,10 @@ public: // Hopefully we only have a few SC vars -- top level ports, probably. { TasksByRank tasksByRank; - for (OvvSet::iterator ovvit = ovvSetSystemC.begin(); ovvit != ovvSetSystemC.end(); - ++ovvit) { - findAdjacentTasks(ovvit, &tasksByRank); + for (const OrderVarStdVertex* const varVtxp : ovvSetSystemC) { + findAdjacentTasks(varVtxp, tasksByRank); } - mergeSameRankTasks(&tasksByRank); + mergeSameRankTasks(tasksByRank); } // Handle nodes containing DPI calls, we want to serialize those @@ -2157,17 +2137,13 @@ public: // Same basic strategy as above to serialize access to SC vars. if (!v3Global.opt.threadsDpiPure() || !v3Global.opt.threadsDpiUnpure()) { TasksByRank tasksByRank; - for (V3GraphVertex* vxp = m_mtasksp->verticesBeginp(); vxp; - vxp = vxp->verticesNextp()) { - LogicMTask* const mtaskp = static_cast(vxp); - if (hasDpiHazard(mtaskp)) tasksByRank[vxp->rank()].insert(mtaskp); + for (V3GraphVertex *vtxp = m_mtasksp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNextp(); + LogicMTask* const mtaskp = static_cast(vtxp); + if (hasDpiHazard(mtaskp)) tasksByRank[mtaskp->rank()].insert(mtaskp); } - mergeSameRankTasks(&tasksByRank); + mergeSameRankTasks(tasksByRank); } - - UINFO(4, "PartFixDataHazards() merged " << m_mergesDone << " pairs of nodes in " - << (V3Os::timeUsecs() - startUsecs) - << " usecs.\n"); } private: From c0f9b0d8f689e695100aa9f0d9bb544a9edc3a4e Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 31 Aug 2022 12:53:11 +0100 Subject: [PATCH 035/177] V3Partition: Refactor initialization of MTask dependencies No functional change --- src/V3Partition.cpp | 122 ++++++++++++++++++++++---------------------- src/V3Partition.h | 2 +- 2 files changed, 63 insertions(+), 61 deletions(-) diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index 666e4f4b2..479a91fd6 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -2611,33 +2611,72 @@ void V3Partition::hashGraphDebug(const V3Graph* graphp, const char* debugName) { UINFO(0, "Hash of shape (not contents) of " << debugName << " = " << cvtToStr(hash) << endl); } -void V3Partition::setupMTaskDeps(V3Graph* mtasksp, const Vx2MTaskMap* vx2mtaskp) { - // Look at each mtask - for (V3GraphVertex* itp = mtasksp->verticesBeginp(); itp; itp = itp->verticesNextp()) { - LogicMTask* const mtaskp = static_cast(itp); - const LogicMTask::VxList* vertexListp = mtaskp->vertexListp(); +uint32_t V3Partition::setupMTaskDeps(V3Graph* mtasksp) { + uint32_t totalGraphCost = 0; - // For each logic vertex in this mtask, create an mtask-to-mtask - // edge based on the logic-to-logic edge. - for (LogicMTask::VxList::const_iterator vit = vertexListp->begin(); - vit != vertexListp->end(); ++vit) { - for (V3GraphEdge* outp = (*vit)->outBeginp(); outp; outp = outp->outNextp()) { - UASSERT(outp->weight() > 0, "Mtask not assigned weight"); - const MTaskMoveVertex* const top = dynamic_cast(outp->top()); - UASSERT(top, "MoveVertex not associated to mtask"); - const auto it = vlstd::as_const(vx2mtaskp)->find(top); - UASSERT(it != vx2mtaskp->end(), "MTask map can't find id"); - LogicMTask* const otherMTaskp = it->second; - UASSERT(otherMTaskp, "nullptr other Mtask"); - UASSERT_OBJ(otherMTaskp != mtaskp, mtaskp, "Would create a cycle edge"); + // 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}; - // Don't create redundant edges. - if (mtaskp->hasRelativeMTask(otherMTaskp)) continue; + // The V3InstrCount within LogicMTask will set user5 on each AST + // node, to assert that we never count any node twice. + const VNUser5InUse user5inUse; - new MTaskEdge(mtasksp, mtaskp, otherMTaskp, 1); - } + // Create the LogicMTasks for each MTaskMoveVertex + for (V3GraphVertex *vtxp = m_fineDepsGraphp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNextp(); + MTaskMoveVertex* const mVtxp = static_cast(vtxp); + LogicMTask* const mtaskp = new LogicMTask{mtasksp, mVtxp}; + mVtxp->userp(mtaskp); + 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 dependency edges based on the dependencies between MTaskMoveVertex + // vertices. + for (V3GraphVertex *vtxp = mtasksp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNextp(); + LogicMTask* const mtaskp = static_cast(vtxp); + + // Entry and exit vertices handled separately + if (VL_UNLIKELY((mtaskp == entryMTask) || (mtaskp == exitMTask))) continue; + + // At this point, there should only be one MTaskMoveVertex per LogicMTask + UASSERT_OBJ(mtaskp->vertexListp()->size() == 1, mtaskp, "Multiple MTaskMoveVertex"); + + MTaskMoveVertex* const mvtxp = mtaskp->vertexListp()->front(); + for (V3GraphEdge* outp = mvtxp->outBeginp(); outp; outp = outp->outNextp()) { + UASSERT(outp->weight() > 0, "Dependency with 0 weight in Move graph"); + + // Grab the opposite end MTask. + LogicMTask* const otherp = static_cast(outp->top()->userp()); + UASSERT_OBJ(otherp != mtaskp, mtaskp, "Would create a cycle edge"); + + // Don't create redundant edges. + if (mtaskp->hasRelativeMTask(otherp)) continue; + + // Add the MTask->MTask dependency edge + new MTaskEdge{mtasksp, mtaskp, otherp, 1}; } } + + // Create Dependencies to/from the entry/exit vertices. + for (V3GraphVertex *vtxp = mtasksp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNextp(); + LogicMTask* const mtaskp = static_cast(vtxp); + + if (VL_UNLIKELY((mtaskp == entryMTask) || (mtaskp == exitMTask))) continue; + + // Add the entry/exit edges + if (mtaskp->inEmpty()) new MTaskEdge{mtasksp, entryMTask, mtaskp, 1}; + if (mtaskp->outEmpty()) new MTaskEdge{mtasksp, mtaskp, exitMTask, 1}; + } + + return totalGraphCost; } void V3Partition::go(V3Graph* mtasksp) { @@ -2648,44 +2687,7 @@ void V3Partition::go(V3Graph* mtasksp) { // MTaskMoveVertex. Over time, we'll merge MTasks together and // eventually each MTask will wrap a large number of MTaskMoveVertices // (and the logic nodes therein.) - uint32_t totalGraphCost = 0; - { - // Artificial single entry point vertex in the MTask graph to allow sibling merges. - // This is required as otherwise disjoint sub-graphs could not be merged, but the - // coarsening algorithm assumes that the graph is connected. - LogicMTask* const entryMTask = new LogicMTask{mtasksp, nullptr}; - - // The V3InstrCount within LogicMTask will set user5 on each AST - // node, to assert that we never count any node twice. - const VNUser5InUse inUser5; - Vx2MTaskMap vx2mtask; - for (V3GraphVertex* vxp = m_fineDepsGraphp->verticesBeginp(); vxp; - vxp = vxp->verticesNextp()) { - MTaskMoveVertex* const mtmvVxp = dynamic_cast(vxp); - UASSERT_OBJ(mtmvVxp, vxp, "Every vertex here should be an MTaskMoveVertex"); - - LogicMTask* const mtaskp = new LogicMTask(mtasksp, mtmvVxp); - vx2mtask[mtmvVxp] = mtaskp; - - totalGraphCost += mtaskp->cost(); - } - - // Artificial single exit point vertex in the MTask graph to allow sibling merges. - // this enables merging MTasks with no downstream dependents if that is the ideal merge. - LogicMTask* const exitMTask = new LogicMTask{mtasksp, nullptr}; - - // Create the mtask->mtask dep edges based on vertex deps - setupMTaskDeps(mtasksp, &vx2mtask); - - // Add the entry/exit edges - for (V3GraphVertex* vtxp = mtasksp->verticesBeginp(); vtxp; vtxp = vtxp->verticesNextp()) { - if (vtxp == entryMTask) continue; - if (vtxp == exitMTask) continue; - LogicMTask* const lmtp = static_cast(vtxp); - if (vtxp->inEmpty()) new MTaskEdge{mtasksp, entryMTask, lmtp, 1}; - if (vtxp->outEmpty()) new MTaskEdge{mtasksp, lmtp, exitMTask, 1}; - } - } + const uint32_t totalGraphCost = setupMTaskDeps(mtasksp); V3Partition::debugMTaskGraphStats(mtasksp, "initial"); diff --git a/src/V3Partition.h b/src/V3Partition.h index d8c8f64fa..8368f2c5a 100644 --- a/src/V3Partition.h +++ b/src/V3Partition.h @@ -66,7 +66,7 @@ public: static void finalize(AstNetlist* netlistp); private: - static void setupMTaskDeps(V3Graph* mtasksp, const Vx2MTaskMap* vx2mtaskp); + uint32_t setupMTaskDeps(V3Graph* mtasksp); VL_DEBUG_FUNC; // Declare debug() VL_UNCOPYABLE(V3Partition); From 875361d7cee7a102b1a79a91622c14d3ec45d80a Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 1 Sep 2022 17:29:40 +0200 Subject: [PATCH 036/177] V3Partition: Reduce working set size of PartContraction (#3587) This yields an additional 25% speedup of MT scheduling. --- src/V3Order.cpp | 55 ++++++++++++++++--------------- src/V3Partition.cpp | 80 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 95 insertions(+), 40 deletions(-) diff --git a/src/V3Order.cpp b/src/V3Order.cpp index d2bc97118..678dafa78 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -1334,37 +1334,38 @@ void OrderProcess::processMTasks() { const V3GraphVertex* moveVxp; while ((moveVxp = emit_logic.nextp())) { const MTaskMoveVertex* const movep = dynamic_cast(moveVxp); + // Only care about logic vertices + if (!movep->logicp()) continue; + const unsigned mtaskId = movep->color(); UASSERT(mtaskId > 0, "Every MTaskMoveVertex should have an mtask assignment >0"); - if (movep->logicp()) { - // Add this logic to the per-mtask order - mtaskStates[mtaskId].m_logics.push_back(movep->logicp()); - // Since we happen to be iterating over every logic node, - // take this opportunity to annotate each AstVar with the id's - // of mtasks that consume it and produce it. We'll use this - // information in V3EmitC when we lay out var's in memory. - const OrderLogicVertex* const logicp = movep->logicp(); - for (const V3GraphEdge* edgep = logicp->inBeginp(); edgep; edgep = edgep->inNextp()) { - const OrderVarVertex* const pre_varp - = dynamic_cast(edgep->fromp()); - if (!pre_varp) continue; - AstVar* const varp = pre_varp->vscp()->varp(); - // varp depends on logicp, so logicp produces varp, - // and vice-versa below - varp->addProducingMTaskId(mtaskId); - } - for (const V3GraphEdge* edgep = logicp->outBeginp(); edgep; - edgep = edgep->outNextp()) { - const OrderVarVertex* const post_varp - = dynamic_cast(edgep->top()); - if (!post_varp) continue; - AstVar* const varp = post_varp->vscp()->varp(); - varp->addConsumingMTaskId(mtaskId); - } - // TODO? We ignore IO vars here, so those will have empty mtask - // signatures. But we could also give those mtask signatures. + // Add this logic to the per-mtask order + mtaskStates[mtaskId].m_logics.push_back(movep->logicp()); + + // Since we happen to be iterating over every logic node, + // take this opportunity to annotate each AstVar with the id's + // of mtasks that consume it and produce it. We'll use this + // information in V3EmitC when we lay out var's in memory. + const OrderLogicVertex* const logicp = movep->logicp(); + for (const V3GraphEdge* edgep = logicp->inBeginp(); edgep; edgep = edgep->inNextp()) { + const OrderVarVertex* const pre_varp + = dynamic_cast(edgep->fromp()); + if (!pre_varp) continue; + AstVar* const varp = pre_varp->vscp()->varp(); + // varp depends on logicp, so logicp produces varp, + // and vice-versa below + varp->addProducingMTaskId(mtaskId); } + for (const V3GraphEdge* edgep = logicp->outBeginp(); edgep; edgep = edgep->outNextp()) { + const OrderVarVertex* const post_varp + = dynamic_cast(edgep->top()); + if (!post_varp) continue; + AstVar* const varp = post_varp->vscp()->varp(); + varp->addConsumingMTaskId(mtaskId); + } + // TODO? We ignore IO vars here, so those will have empty mtask + // signatures. But we could also give those mtask signatures. } // Create the AstExecGraph node which represents the execution diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index 479a91fd6..e2d70360e 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -2611,6 +2611,42 @@ void V3Partition::hashGraphDebug(const V3Graph* graphp, const char* debugName) { UINFO(0, "Hash of shape (not contents) of " << debugName << " = " << cvtToStr(hash) << endl); } +// Predicate function to determine what MTaskMoveVertex to bypass when constructing the MTask +// graph. The fine-grained dependency graph of MTaskMoveVertex vertices is a bipartite graph of: +// - 1. MTaskMoveVertex instances containing logic via OrderLogicVertex +// (MTaskMoveVertex::logicp() != nullptr) +// - 2. MTaskMoveVertex instances containing an (OrderVarVertex, domain) pair +// Our goal is to order the logic vertices. The second type of variable/domain vertices only carry +// dependencies and are eventually discarded. In order to reduce the working set size of +// PartContraction, we 'bypass' and not create LogicMTask vertices for the variable vertices, and +// instead add the transitive dependencies directly, but only if adding the transitive edges +// directly does not require more dependency edges than keeping the intermediate vertex. That is, +// we bypass a variable vertex if fanIn * fanOut <= fanIn + fanOut. This can only be true if fanIn +// or fanOut are 1, or if they are both 2. This can cause significant reduction in working set +// size. +static bool bypassOk(MTaskMoveVertex* mvtxp) { + // Need to keep all logic vertices + if (mvtxp->logicp()) return false; + // Count fan-in, up to 3 + unsigned fanIn = 0; + for (V3GraphEdge* edgep = mvtxp->inBeginp(); edgep; edgep = edgep->inNextp()) { + if (++fanIn == 3) break; + } + UDEBUGONLY(UASSERT_OBJ(fanIn <= 3, mvtxp, "Should have stopped counting fanIn");); + // If fanInn no more than one, bypass + if (fanIn <= 1) return true; + // Count fan-out, up to 3 + unsigned fanOut = 0; + for (V3GraphEdge* edgep = mvtxp->outBeginp(); edgep; edgep = edgep->outNextp()) { + if (++fanOut == 3) break; + } + UDEBUGONLY(UASSERT_OBJ(fanOut <= 3, mvtxp, "Should have stopped counting fanOut");); + // If fan-out no more than one, bypass + if (fanOut <= 1) return true; + // They can only be (2, 2), (2, 3), (3, 2), (3, 3) at this point, bypass if (2, 2) + return fanIn + fanOut == 4; +} + uint32_t V3Partition::setupMTaskDeps(V3Graph* mtasksp) { uint32_t totalGraphCost = 0; @@ -2627,9 +2663,13 @@ uint32_t V3Partition::setupMTaskDeps(V3Graph* mtasksp) { for (V3GraphVertex *vtxp = m_fineDepsGraphp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) { nextp = vtxp->verticesNextp(); MTaskMoveVertex* const mVtxp = static_cast(vtxp); - LogicMTask* const mtaskp = new LogicMTask{mtasksp, mVtxp}; - mVtxp->userp(mtaskp); - totalGraphCost += mtaskp->cost(); + if (bypassOk(mVtxp)) { + mVtxp->userp(nullptr); // Set to nullptr to mark as bypassed + } else { + LogicMTask* const mtaskp = new LogicMTask{mtasksp, mVtxp}; + mVtxp->userp(mtaskp); + totalGraphCost += mtaskp->cost(); + } } // Artificial single exit point vertex in the MTask graph to allow sibling merges. @@ -2647,20 +2687,34 @@ uint32_t V3Partition::setupMTaskDeps(V3Graph* mtasksp) { // At this point, there should only be one MTaskMoveVertex per LogicMTask UASSERT_OBJ(mtaskp->vertexListp()->size() == 1, mtaskp, "Multiple MTaskMoveVertex"); - MTaskMoveVertex* const mvtxp = mtaskp->vertexListp()->front(); - for (V3GraphEdge* outp = mvtxp->outBeginp(); outp; outp = outp->outNextp()) { - UASSERT(outp->weight() > 0, "Dependency with 0 weight in Move graph"); + UASSERT_OBJ(mvtxp->userp(), mtaskp, "Bypassed MTaskMoveVertex should not have MTask"); - // Grab the opposite end MTask. - LogicMTask* const otherp = static_cast(outp->top()->userp()); + // Function to add a edge to a dependent from 'mtaskp' + const auto addEdge = [mtasksp, mtaskp](LogicMTask* otherp) { UASSERT_OBJ(otherp != mtaskp, mtaskp, "Would create a cycle edge"); - - // Don't create redundant edges. - if (mtaskp->hasRelativeMTask(otherp)) continue; - - // Add the MTask->MTask dependency edge + if (mtaskp->hasRelativeMTask(otherp)) return; // Don't create redundant edges. new MTaskEdge{mtasksp, mtaskp, otherp, 1}; + }; + + // Iterate downstream direct dependents + for (V3GraphEdge *dEdgep = mvtxp->outBeginp(), *dNextp; dEdgep; dEdgep = dNextp) { + dNextp = dEdgep->outNextp(); + V3GraphVertex* const top = dEdgep->top(); + if (LogicMTask* const otherp = static_cast(top->userp())) { + // The opposite end of the edge is not a bypassed vertex, add as direct dependent + addEdge(otherp); + } else { + // The opposite end of the edge is a bypassed vertex, add transitive dependents + for (V3GraphEdge *tEdgep = top->outBeginp(), *tNextp; tEdgep; tEdgep = tNextp) { + tNextp = tEdgep->outNextp(); + LogicMTask* const transp = static_cast(tEdgep->top()->userp()); + // The Move graph is bipartite (logic <-> var), and logic is never bypassed, + // hence 'transp' must be non nullptr. + UASSERT_OBJ(transp, mvtxp, "This cannot be a bypassed vertex"); + addEdge(transp); + } + } } } From 4640bea31a1e8f6882531ee66c69d66386a4fe1a Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 1 Sep 2022 14:24:02 +0100 Subject: [PATCH 037/177] V3Partition: More improvements for PartFixDataHazards - Remove redundant loop through the MTask graph - Gather variables directly from the OrderGraph, which is simpler and faster. --- src/V3Order.cpp | 2 +- src/V3Partition.cpp | 100 ++++++++++++++++---------------------------- src/V3Partition.h | 8 ++-- 3 files changed, 41 insertions(+), 69 deletions(-) diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 678dafa78..c71025076 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -1319,7 +1319,7 @@ void OrderProcess::processMTasks() { // Partition logicGraph into LogicMTask's. The partitioner will annotate // each vertex in logicGraph with a 'color' which is really an mtask ID // in this context. - V3Partition partitioner(&logicGraph); + V3Partition partitioner(&m_graph, &logicGraph); V3Graph mtasks; partitioner.go(&mtasks); diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index e2d70360e..78d268891 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -717,20 +717,6 @@ void MergeCandidate::rescore() { } } -// ###################################################################### -// Vertex utility classes - -class OrderByPtrId final { - PartPtrIdMap m_ids; - -public: - bool operator()(const OrderVarStdVertex* lhsp, const OrderVarStdVertex* rhsp) const { - const uint64_t l_id = m_ids.findId(lhsp); - const uint64_t r_id = m_ids.findId(rhsp); - return l_id < r_id; - } -}; - //###################################################################### // PartParallelismEst - Estimate parallelism of graph @@ -1938,14 +1924,15 @@ class PartFixDataHazards final { private: // TYPES using TasksByRank = std::map>; - using OvvSet = std::set; // MEMBERS + const OrderGraph* const m_orderGraphp; // The OrderGraph V3Graph* const m_mtasksp; // Mtask graph public: // CONSTRUCTORs - explicit PartFixDataHazards(V3Graph* mtasksp) - : m_mtasksp{mtasksp} {} + explicit PartFixDataHazards(const OrderGraph* orderGraphp, V3Graph* mtasksp) + : m_orderGraphp{orderGraphp} + , m_mtasksp{mtasksp} {} // METHODS private: void findAdjacentTasks(const OrderVarStdVertex* varVtxp, TasksByRank& tasksByRank) { @@ -2015,51 +2002,8 @@ private: public: void go() { - // Build an OLV->mtask map and a set of OVVs - OrderByPtrId ovvOrder; - OvvSet ovvSet(ovvOrder); - // OVV's which wrap systemC vars will be handled slightly specially - OvvSet ovvSetSystemC(ovvOrder); - - // TODO: This loop is entirely redundant as we iterate every vertex of the graph - // during ranking below anyway, so we could do all this work in the body of that - // loop. However... the order in which OrderVarStdVertex are added to ovvSet can - // have a significant impact on model performance (+/-15% was observed), and doing - // it this way happens to be best on some benchmarks. Need to investigate and find - // a better way that yields consistent performance. - for (V3GraphVertex* vxp = m_mtasksp->verticesBeginp(); vxp; vxp = vxp->verticesNextp()) { - LogicMTask* const mtaskp = static_cast(vxp); - - // Set up the OrderLogicVertex -> LogicMTask map - // Entry and exit MTasks have no MTaskMoveVertices under them, so move on - if (mtaskp->vertexListp()->empty()) continue; - // Otherwise there should be only one MTaskMoveVertex in each MTask at this stage - UASSERT_OBJ(mtaskp->vertexListp()->size() == 1, mtaskp, "Multiple MTaskMoveVertex"); - const MTaskMoveVertex* const moveVtxp = mtaskp->vertexListp()->front(); - if (OrderLogicVertex* const lvtxp = moveVtxp->logicp()) { - // Set up mapping back to the MTask from the OrderLogicVertex - lvtxp->userp(mtaskp); - // Look at downstream variables - for (V3GraphEdge *edgep = lvtxp->outBeginp(), *nextp; edgep; edgep = nextp) { - nextp = edgep->outNextp(); - // Only consider OrderVarStdVertex which reflects - // an actual lvalue assignment; the others do not. - if (const auto* const vvtxp = dynamic_cast(edgep->top())) { - if (vvtxp->vscp()->varp()->isSc()) { - ovvSetSystemC.insert(vvtxp); - } else { - ovvSet.insert(vvtxp); - } - } - } - } - } - - // Rank the graph. - // DGS is faster than V3GraphAlg's recursive rank, in the worst - // cases where the recursive rank must pass through the same node - // many times. (We saw 22s for DGS vs. 500s for recursive rank on - // one large design.) + // Rank the graph. DGS is faster than V3GraphAlg's recursive rank, and also allows us to + // set up the OrderLogicVertex -> LogicMTask map at the same time. { GraphStreamUnordered serialize(m_mtasksp); while (LogicMTask* const mtaskp @@ -2070,6 +2014,32 @@ public: rank = std::max(edgep->fromp()->rank() + 1, rank); } mtaskp->rank(rank); + + // Set up the OrderLogicVertex -> LogicMTask map + // Entry and exit MTasks have no MTaskMoveVertices under them, so move on + if (mtaskp->vertexListp()->empty()) continue; + // Otherwise there should be only one MTaskMoveVertex in each MTask at this stage + UASSERT_OBJ(mtaskp->vertexListp()->size() == 1, mtaskp, + "Multiple MTaskMoveVertex"); + const MTaskMoveVertex* const moveVtxp = mtaskp->vertexListp()->front(); + // Set up mapping back to the MTask from the OrderLogicVertex + if (OrderLogicVertex* const lvtxp = moveVtxp->logicp()) lvtxp->userp(mtaskp); + } + } + + // Gather all variables. SystemC vars will be handled slightly specially, so keep separate. + std::vector regularVars; + std::vector systemCVars; + for (V3GraphVertex *vtxp = m_orderGraphp->verticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNextp(); + // Only consider OrderVarStdVertex which reflects + // an actual lvalue assignment; the others do not. + if (const OrderVarStdVertex* const vvtxp = dynamic_cast(vtxp)) { + if (vvtxp->vscp()->varp()->isSc()) { + systemCVars.push_back(vvtxp); + } else { + regularVars.push_back(vvtxp); + } } } @@ -2086,7 +2056,7 @@ public: // NOTE: we don't update the CP's stored in the LogicMTasks to // reflect the changes we make to the graph. That's OK, as we // haven't yet initialized CPs when we call this routine. - for (const OrderVarStdVertex* const varVtxp : ovvSet) { + for (const OrderVarStdVertex* const varVtxp : regularVars) { // Build a set of mtasks, per rank, which access this var. // Within a rank, sort by MTaskID to avoid nondeterminism. TasksByRank tasksByRank; @@ -2126,7 +2096,7 @@ public: // Hopefully we only have a few SC vars -- top level ports, probably. { TasksByRank tasksByRank; - for (const OrderVarStdVertex* const varVtxp : ovvSetSystemC) { + for (const OrderVarStdVertex* const varVtxp : systemCVars) { findAdjacentTasks(varVtxp, tasksByRank); } mergeSameRankTasks(tasksByRank); @@ -2754,7 +2724,7 @@ void V3Partition::go(V3Graph* mtasksp) { // Merge nodes that could present data hazards; see comment within. { - PartFixDataHazards(mtasksp).go(); + PartFixDataHazards(m_orderGraphp, mtasksp).go(); V3Partition::debugMTaskGraphStats(mtasksp, "hazards"); hashGraphDebug(mtasksp, "mtasksp after fixDataHazards()"); } diff --git a/src/V3Partition.h b/src/V3Partition.h index 8368f2c5a..460965ae5 100644 --- a/src/V3Partition.h +++ b/src/V3Partition.h @@ -38,11 +38,13 @@ using Vx2MTaskMap = std::unordered_map; class V3Partition final { // MEMBERS - V3Graph* const m_fineDepsGraphp; // Fine-grained dependency graph + const OrderGraph* const m_orderGraphp; // The OrderGraph + const V3Graph* const m_fineDepsGraphp; // Fine-grained dependency graph public: // CONSTRUCTORS - explicit V3Partition(V3Graph* fineDepsGraphp) - : m_fineDepsGraphp{fineDepsGraphp} {} + explicit V3Partition(const OrderGraph* orderGraphp, const V3Graph* fineDepsGraphp) + : m_orderGraphp{orderGraphp} + , m_fineDepsGraphp{fineDepsGraphp} {} ~V3Partition() = default; // METHODS From da7ad35577e273be0f9f42cb7d0c9a114caecc9e Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Mon, 5 Sep 2022 12:27:24 +0200 Subject: [PATCH 038/177] Fix fork debug output (#3593) Signed-off-by: Krzysztof Bieganski --- include/verilated_timing.cpp | 3 ++- include/verilated_timing.h | 2 +- test_regress/t/t_timing_debug2.out | 36 ++++++++++++++++++++---------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/include/verilated_timing.cpp b/include/verilated_timing.cpp index 8c0da322e..3282b6b3f 100644 --- a/include/verilated_timing.cpp +++ b/include/verilated_timing.cpp @@ -148,7 +148,8 @@ void VlTriggerScheduler::dump(const char* eventDescription) { // VlForkSync:: Methods void VlForkSync::done(const char* filename, int linenum) { - VL_DEBUG_IF(VL_DBG_MSGF(" Process forked at %s:%d finished", filename, linenum);); + VL_DEBUG_IF( + VL_DBG_MSGF(" Process forked at %s:%d finished\n", filename, linenum);); if (m_join->m_counter > 0) m_join->m_counter--; if (m_join->m_counter == 0) m_join->m_susp.resume(); } diff --git a/include/verilated_timing.h b/include/verilated_timing.h index f7fd2ae3f..90d5f8665 100644 --- a/include/verilated_timing.h +++ b/include/verilated_timing.h @@ -283,7 +283,7 @@ public: auto join(const char* filename, int linenum) { assert(m_join); VL_DEBUG_IF( - VL_DBG_MSGF(" Awaiting join of fork at: %s:%d", filename, linenum);); + VL_DBG_MSGF(" Awaiting join of fork at: %s:%d\n", filename, linenum);); struct Awaitable { const std::shared_ptr join; // Join to await on bool await_ready() { return join->m_counter == 0; } // Suspend if join still exists diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out index a206663b2..1da1ef371 100644 --- a/test_regress/t/t_timing_debug2.out +++ b/test_regress/t/t_timing_debug2.out @@ -11,7 +11,8 @@ -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__2 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__3 --V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:13-V{t0,14}+ Vt_timing_debug2___024root___eval_initial__TOP__1 +-V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:13 +-V{t#,#}+ Vt_timing_debug2___024root___eval_initial__TOP__1 -V{t#,#}+ Vt_timing_debug2___024root___eval_settle -V{t#,#}+ Eval -V{t#,#}+ Vt_timing_debug2___024root___eval @@ -45,7 +46,8 @@ -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:79 [2] fork..join process 3 --V{t#,#} Process forked at t/t_timing_fork_join.v:17 finished-V{t0,48}+ Vt_timing_debug2___024root___eval_act +-V{t#,#} Process forked at t/t_timing_fork_join.v:17 finished +-V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#} No triggers active @@ -72,7 +74,8 @@ -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:19 [4] fork..join process 2 --V{t#,#} Process forked at t/t_timing_fork_join.v:16 finished-V{t0,75}+ Vt_timing_debug2___024root___eval_act +-V{t#,#} Process forked at t/t_timing_fork_join.v:16 finished +-V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#} No triggers active @@ -98,7 +101,8 @@ -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:17 [8] fork..join process 1 --V{t#,#} Process forked at t/t_timing_fork_join.v:15 finished-V{t0,101}+ Vt_timing_debug2___024root___eval_act +-V{t#,#} Process forked at t/t_timing_fork_join.v:15 finished +-V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#} No triggers active @@ -127,7 +131,8 @@ -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__2 --V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:21-V{t0,129}+ Vt_timing_debug2___024root___eval_act +-V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:21 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#} No triggers active @@ -154,7 +159,8 @@ -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:24 [20] fork..join process 7 --V{t#,#} Process forked at t/t_timing_fork_join.v:24 finished-V{t0,156}+ Vt_timing_debug2___024root___eval_act +-V{t#,#} Process forked at t/t_timing_fork_join.v:24 finished +-V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#} No triggers active @@ -180,7 +186,8 @@ -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:23 [24] fork..join process 6 --V{t#,#} Process forked at t/t_timing_fork_join.v:23 finished-V{t0,182}+ Vt_timing_debug2___024root___eval_act +-V{t#,#} Process forked at t/t_timing_fork_join.v:23 finished +-V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#} No triggers active @@ -205,9 +212,11 @@ -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:22 [32] fork..join process 5 --V{t#,#} Process forked at t/t_timing_fork_join.v:22 finished-V{t0,207} Resuming: Process waiting at (null):0 +-V{t#,#} Process forked at t/t_timing_fork_join.v:22 finished +-V{t#,#} Resuming: Process waiting at (null):0 [32] fork..join in fork ends --V{t#,#} Process forked at t/t_timing_fork_join.v:19 finished-V{t0,209} Resuming: Process waiting at (null):0 +-V{t#,#} Process forked at t/t_timing_fork_join.v:19 finished +-V{t#,#} Resuming: Process waiting at (null):0 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act @@ -309,7 +318,8 @@ fork..join_any process 1 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 -V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:44 --V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:41-V{t0,308}+ Vt_timing_debug2___024root___eval_act +-V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:41 +-V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#} No triggers active @@ -336,7 +346,8 @@ fork..join_any process 1 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:42 fork..join_any process 1 --V{t#,#} Process forked at t/t_timing_fork_join.v:42 finished-V{t0,335} Resuming: Process waiting at (null):0 +-V{t#,#} Process forked at t/t_timing_fork_join.v:42 finished +-V{t#,#} Resuming: Process waiting at (null):0 back in main process -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act @@ -376,7 +387,8 @@ back in main process -V{t#,#} Resuming processes waiting for @([event] t.e1) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:44 fork..join_any process 2 --V{t#,#} Process forked at t/t_timing_fork_join.v:43 finished-V{t0,374} Committing processes waiting for @([event] t.e1): +-V{t#,#} Process forked at t/t_timing_fork_join.v:43 finished +-V{t#,#} Committing processes waiting for @([event] t.e1): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:51 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act From 8b19d02e3b09c4b15008ff1e497f325bf037388c Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Mon, 5 Sep 2022 12:46:34 +0200 Subject: [PATCH 039/177] Fix `co_await VlNow{}` being added too many times (#3596) (or not at all) Signed-off-by: Krzysztof Bieganski --- src/V3SchedTiming.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index 5d22976ad..edffdaabc 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -264,7 +264,7 @@ void transformForks(AstNetlist* const netlistp) { // We can just pass it to the new function } else if (m_forkp->joinType().join()) { // If it's fork..join, we can refer to variables from the parent process - if (m_funcp->user1SetOnce()) { // Only do this once per function + if (!m_funcp->user1SetOnce()) { // Only do this once per function // Move all locals to the heap before the fork auto* const awaitp = new AstCAwait{ m_forkp->fileline(), new AstCStmt{m_forkp->fileline(), "VlNow{}"}}; From 54f89bce422716bcb7b5581b70439e6dbf611d0f Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Mon, 5 Sep 2022 16:17:51 +0200 Subject: [PATCH 040/177] Move `SenExprBuilder` to a header. (#3598) No functional change intended. Signed-off-by: Krzysztof Bieganski --- src/V3Sched.cpp | 219 +------------------------------------ src/V3SenExprBuilder.h | 243 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+), 218 deletions(-) create mode 100644 src/V3SenExprBuilder.h diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index af027ccb4..1f1ca4c20 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -44,8 +44,8 @@ #include "V3EmitCBase.h" #include "V3EmitV.h" #include "V3Order.h" +#include "V3SenExprBuilder.h" #include "V3Stats.h" -#include "V3UniqueNames.h" #include @@ -273,223 +273,6 @@ void createFinal(AstNetlist* netlistp, const LogicClasses& logicClasses) { splitCheck(funcp); } -//============================================================================ -// SenExprBuilder constructs the expressions used to compute if an -// AstSenTree have triggered - -class SenExprBuilder final { - // STATE - AstCFunc* const m_initp; // The initialization function - AstScope* const m_scopeTopp; // Top level scope - - std::vector m_locals; // Trigger eval local variables - std::vector m_preUpdates; // Pre update assignments - std::vector m_postUpdates; // Post update assignments - - std::unordered_map, AstVarScope*> m_prev; // The 'previous value' signals - std::unordered_map, AstVarScope*> m_curr; // The 'current value' signals - std::unordered_set> m_hasPreUpdate; // Whether the given sen expression already - // has an update statement in m_preUpdates - std::unordered_set> m_hasPostUpdate; // Likewis for m_postUpdates - - V3UniqueNames m_currNames{"__Vtrigcurr__expression"}; // For generating unique current value - // signal names - V3UniqueNames m_prevNames{"__Vtrigprev__expression"}; // Likewise for previous values - - static bool isSupportedDType(AstNodeDType* dtypep) { - dtypep = dtypep->skipRefp(); - if (VN_IS(dtypep, BasicDType)) return true; - if (VN_IS(dtypep, PackArrayDType)) return true; - if (VN_IS(dtypep, UnpackArrayDType)) return isSupportedDType(dtypep->subDTypep()); - if (VN_IS(dtypep, NodeUOrStructDType)) return true; // All are packed at the moment - return false; - } - - static bool isSimpleExpr(const AstNode* const exprp) { - return exprp->forall([](const AstNode* const nodep) { - return VN_IS(nodep, Const) || VN_IS(nodep, NodeVarRef) || VN_IS(nodep, Sel) - || VN_IS(nodep, NodeSel) || VN_IS(nodep, MemberSel) - || VN_IS(nodep, CMethodHard); - }); - } - - // METHODS - AstNode* getCurr(AstNode* exprp) { - // For simple expressions like varrefs or selects, just use them directly - if (isSimpleExpr(exprp)) return exprp->cloneTree(false); - - // Create the 'current value' variable - FileLine* const flp = exprp->fileline(); - auto result = m_curr.emplace(*exprp, nullptr); - if (result.second) { - AstVar* const varp - = new AstVar{flp, VVarType::BLOCKTEMP, m_currNames.get(exprp), exprp->dtypep()}; - varp->funcLocal(true); - m_locals.push_back(varp); - AstVarScope* vscp = new AstVarScope{flp, m_scopeTopp, varp}; - m_scopeTopp->addVarp(vscp); - result.first->second = vscp; - } - AstVarScope* const currp = result.first->second; - - // Add pre update if it does not exist yet in this round - if (m_hasPreUpdate.emplace(*currp).second) { - m_preUpdates.push_back(new AstAssign{flp, new AstVarRef{flp, currp, VAccess::WRITE}, - exprp->cloneTree(false)}); - } - return new AstVarRef{flp, currp, VAccess::READ}; - } - AstVarScope* getPrev(AstNode* exprp) { - FileLine* const flp = exprp->fileline(); - const auto rdCurr = [=]() { return getCurr(exprp); }; - - // Create the 'previous value' variable - auto it = m_prev.find(*exprp); - if (it == m_prev.end()) { - // For readability, use the scoped signal name if the trigger is a simple AstVarRef - string name; - if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) { - AstVarScope* vscp = refp->varScopep(); - name = "__Vtrigrprev__" + vscp->scopep()->nameDotless() + "__" - + vscp->varp()->name(); - } else { - name = m_prevNames.get(exprp); - } - - AstVarScope* const prevp = m_scopeTopp->createTemp(name, exprp->dtypep()); - it = m_prev.emplace(*exprp, prevp).first; - - // Add the initializer init - AstNode* const initp = exprp->cloneTree(false); - m_initp->addStmtsp( - new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE}, initp}); - } - - AstVarScope* const prevp = it->second; - - const auto wrPrev = [=]() { return new AstVarRef{flp, prevp, VAccess::WRITE}; }; - - // Add post update if it does not exist yet - if (m_hasPostUpdate.emplace(*exprp).second) { - if (!isSupportedDType(exprp->dtypep())) { - exprp->v3warn(E_UNSUPPORTED, - "Unsupported: Cannot detect changes on expression of complex type" - " (see combinational cycles reported by UNOPTFLAT)"); - return prevp; - } - - if (AstUnpackArrayDType* const dtypep = VN_CAST(exprp->dtypep(), UnpackArrayDType)) { - AstCMethodHard* const cmhp = new AstCMethodHard{flp, wrPrev(), "assign", rdCurr()}; - cmhp->dtypeSetVoid(); - cmhp->statement(true); - m_postUpdates.push_back(cmhp); - } else { - m_postUpdates.push_back(new AstAssign{flp, wrPrev(), rdCurr()}); - } - } - - return prevp; - } - - std::pair createTerm(AstSenItem* senItemp) { - FileLine* const flp = senItemp->fileline(); - AstNode* const senp = senItemp->sensp(); - - const auto currp = [=]() { return getCurr(senp); }; - const auto prevp = [=]() { return new AstVarRef{flp, getPrev(senp), VAccess::READ}; }; - const auto lsb = [=](AstNodeMath* opp) { return new AstSel{flp, opp, 0, 1}; }; - - // All event signals should be 1-bit at this point - switch (senItemp->edgeType()) { - case VEdgeType::ET_ILLEGAL: - return {nullptr, false}; // We already warn for this in V3LinkResolve - case VEdgeType::ET_CHANGED: - case VEdgeType::ET_HYBRID: // - if (VN_IS(senp->dtypep(), UnpackArrayDType)) { - AstCMethodHard* const resultp = new AstCMethodHard{flp, currp(), "neq", prevp()}; - resultp->dtypeSetBit(); - return {resultp, true}; - } - return {new AstNeq(flp, currp(), prevp()), true}; - case VEdgeType::ET_BOTHEDGE: // - return {lsb(new AstXor{flp, currp(), prevp()}), false}; - case VEdgeType::ET_POSEDGE: // - return {lsb(new AstAnd{flp, currp(), new AstNot{flp, prevp()}}), false}; - case VEdgeType::ET_NEGEDGE: // - return {lsb(new AstAnd{flp, new AstNot{flp, currp()}, prevp()}), false}; - case VEdgeType::ET_EVENT: { - UASSERT_OBJ(v3Global.hasEvents(), senItemp, "Inconsistent"); - { - // If the event is fired, set up the clearing process - AstCMethodHard* const callp = new AstCMethodHard{flp, currp(), "isFired"}; - callp->dtypeSetBit(); - AstIf* const ifp = new AstIf{flp, callp}; - m_postUpdates.push_back(ifp); - - // Clear 'fired' state when done - AstCMethodHard* const clearp = new AstCMethodHard{flp, currp(), "clearFired"}; - ifp->addIfsp(clearp); - clearp->dtypeSetVoid(); - clearp->statement(true); - - // Enqueue for clearing 'triggered' state on next eval - AstTextBlock* const blockp = new AstTextBlock{flp}; - ifp->addIfsp(blockp); - const auto add = [&](const string& text) { blockp->addText(flp, text, true); }; - add("vlSymsp->enqueueTriggeredEventForClearing("); - blockp->addNodep(currp()); - add(");\n"); - } - - // Get 'fired' state - AstCMethodHard* const callp = new AstCMethodHard{flp, currp(), "isFired"}; - callp->dtypeSetBit(); - return {callp, false}; - } - case VEdgeType::ET_TRUE: // - return {currp(), false}; - default: // LCOV_EXCL_START - senItemp->v3fatalSrc("Unknown edge type"); - return {nullptr, false}; - } // LCOV_EXCL_STOP - } - -public: - // Returns the expression computing the trigger, and a bool indicating that - // this trigger should be fired on the first evaluation (at initialization) - std::pair build(const AstSenTree* senTreep) { - FileLine* const flp = senTreep->fileline(); - AstNode* resultp = nullptr; - bool firedAtInitialization = false; - for (AstSenItem* senItemp = senTreep->sensesp(); senItemp; - senItemp = VN_AS(senItemp->nextp(), SenItem)) { - const auto& pair = createTerm(senItemp); - if (AstNode* const termp = pair.first) { - resultp = resultp ? new AstOr{flp, resultp, termp} : termp; - firedAtInitialization |= pair.second; - } - } - return {resultp, firedAtInitialization}; - } - - std::vector getAndClearLocals() { return std::move(m_locals); } - - std::vector getAndClearPreUpdates() { - m_hasPreUpdate.clear(); - return std::move(m_preUpdates); - } - - std::vector getAndClearPostUpdates() { - m_hasPostUpdate.clear(); - return std::move(m_postUpdates); - } - - // CONSTRUCTOR - SenExprBuilder(AstNetlist* netlistp, AstCFunc* initp) - : m_initp{initp} - , m_scopeTopp{netlistp->topScopep()->scopep()} {} -}; - //============================================================================ // A TriggerKit holds all the components related to a TRIGGERVEC variable diff --git a/src/V3SenExprBuilder.h b/src/V3SenExprBuilder.h new file mode 100644 index 000000000..bf18d9053 --- /dev/null +++ b/src/V3SenExprBuilder.h @@ -0,0 +1,243 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Builder for sensitivity checking expressions. +// +// 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_V3SENEXPRBUILDER_H_ +#define VERILATOR_V3SENEXPRBUILDER_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3UniqueNames.h" + +//###################################################################### +// SenExprBuilder constructs the expressions used to compute if an +// AstSenTree have triggered + +class SenExprBuilder final { + // STATE + AstCFunc* const m_initp; // The initialization function + AstScope* const m_scopeTopp; // Top level scope + + std::vector m_locals; // Trigger eval local variables + std::vector m_preUpdates; // Pre update assignments + std::vector m_postUpdates; // Post update assignments + + std::unordered_map, AstVarScope*> m_prev; // The 'previous value' signals + std::unordered_map, AstVarScope*> m_curr; // The 'current value' signals + std::unordered_set> m_hasPreUpdate; // Whether the given sen expression already + // has an update statement in m_preUpdates + std::unordered_set> m_hasPostUpdate; // Likewis for m_postUpdates + + V3UniqueNames m_currNames{"__Vtrigcurr__expression"}; // For generating unique current value + // signal names + V3UniqueNames m_prevNames{"__Vtrigprev__expression"}; // Likewise for previous values + + static bool isSupportedDType(AstNodeDType* dtypep) { + dtypep = dtypep->skipRefp(); + if (VN_IS(dtypep, BasicDType)) return true; + if (VN_IS(dtypep, PackArrayDType)) return true; + if (VN_IS(dtypep, UnpackArrayDType)) return isSupportedDType(dtypep->subDTypep()); + if (VN_IS(dtypep, NodeUOrStructDType)) return true; // All are packed at the moment + return false; + } + + static bool isSimpleExpr(const AstNode* const exprp) { + return exprp->forall([](const AstNode* const nodep) { + return VN_IS(nodep, Const) || VN_IS(nodep, NodeVarRef) || VN_IS(nodep, Sel) + || VN_IS(nodep, NodeSel) || VN_IS(nodep, MemberSel) + || VN_IS(nodep, CMethodHard); + }); + } + + // METHODS + AstNode* getCurr(AstNode* exprp) { + // For simple expressions like varrefs or selects, just use them directly + if (isSimpleExpr(exprp)) return exprp->cloneTree(false); + + // Create the 'current value' variable + FileLine* const flp = exprp->fileline(); + auto result = m_curr.emplace(*exprp, nullptr); + if (result.second) { + AstVar* const varp + = new AstVar{flp, VVarType::BLOCKTEMP, m_currNames.get(exprp), exprp->dtypep()}; + varp->funcLocal(true); + m_locals.push_back(varp); + AstVarScope* vscp = new AstVarScope{flp, m_scopeTopp, varp}; + m_scopeTopp->addVarp(vscp); + result.first->second = vscp; + } + AstVarScope* const currp = result.first->second; + + // Add pre update if it does not exist yet in this round + if (m_hasPreUpdate.emplace(*currp).second) { + m_preUpdates.push_back(new AstAssign{flp, new AstVarRef{flp, currp, VAccess::WRITE}, + exprp->cloneTree(false)}); + } + return new AstVarRef{flp, currp, VAccess::READ}; + } + AstVarScope* getPrev(AstNode* exprp) { + FileLine* const flp = exprp->fileline(); + const auto rdCurr = [=]() { return getCurr(exprp); }; + + // Create the 'previous value' variable + auto it = m_prev.find(*exprp); + if (it == m_prev.end()) { + // For readability, use the scoped signal name if the trigger is a simple AstVarRef + string name; + if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) { + AstVarScope* vscp = refp->varScopep(); + name = "__Vtrigrprev__" + vscp->scopep()->nameDotless() + "__" + + vscp->varp()->name(); + } else { + name = m_prevNames.get(exprp); + } + + AstVarScope* const prevp = m_scopeTopp->createTemp(name, exprp->dtypep()); + it = m_prev.emplace(*exprp, prevp).first; + + // Add the initializer init + AstNode* const initp = exprp->cloneTree(false); + m_initp->addStmtsp( + new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE}, initp}); + } + + AstVarScope* const prevp = it->second; + + const auto wrPrev = [=]() { return new AstVarRef{flp, prevp, VAccess::WRITE}; }; + + // Add post update if it does not exist yet + if (m_hasPostUpdate.emplace(*exprp).second) { + if (!isSupportedDType(exprp->dtypep())) { + exprp->v3warn(E_UNSUPPORTED, + "Unsupported: Cannot detect changes on expression of complex type" + " (see combinational cycles reported by UNOPTFLAT)"); + return prevp; + } + + if (AstUnpackArrayDType* const dtypep = VN_CAST(exprp->dtypep(), UnpackArrayDType)) { + AstCMethodHard* const cmhp = new AstCMethodHard{flp, wrPrev(), "assign", rdCurr()}; + cmhp->dtypeSetVoid(); + cmhp->statement(true); + m_postUpdates.push_back(cmhp); + } else { + m_postUpdates.push_back(new AstAssign{flp, wrPrev(), rdCurr()}); + } + } + + return prevp; + } + + std::pair createTerm(AstSenItem* senItemp) { + FileLine* const flp = senItemp->fileline(); + AstNode* const senp = senItemp->sensp(); + + const auto currp = [=]() { return getCurr(senp); }; + const auto prevp = [=]() { return new AstVarRef{flp, getPrev(senp), VAccess::READ}; }; + const auto lsb = [=](AstNodeMath* opp) { return new AstSel{flp, opp, 0, 1}; }; + + // All event signals should be 1-bit at this point + switch (senItemp->edgeType()) { + case VEdgeType::ET_ILLEGAL: + return {nullptr, false}; // We already warn for this in V3LinkResolve + case VEdgeType::ET_CHANGED: + case VEdgeType::ET_HYBRID: // + if (VN_IS(senp->dtypep(), UnpackArrayDType)) { + AstCMethodHard* const resultp = new AstCMethodHard{flp, currp(), "neq", prevp()}; + resultp->dtypeSetBit(); + return {resultp, true}; + } + return {new AstNeq(flp, currp(), prevp()), true}; + case VEdgeType::ET_BOTHEDGE: // + return {lsb(new AstXor{flp, currp(), prevp()}), false}; + case VEdgeType::ET_POSEDGE: // + return {lsb(new AstAnd{flp, currp(), new AstNot{flp, prevp()}}), false}; + case VEdgeType::ET_NEGEDGE: // + return {lsb(new AstAnd{flp, new AstNot{flp, currp()}, prevp()}), false}; + case VEdgeType::ET_EVENT: { + UASSERT_OBJ(v3Global.hasEvents(), senItemp, "Inconsistent"); + { + // If the event is fired, set up the clearing process + AstCMethodHard* const callp = new AstCMethodHard{flp, currp(), "isFired"}; + callp->dtypeSetBit(); + AstIf* const ifp = new AstIf{flp, callp}; + m_postUpdates.push_back(ifp); + + // Clear 'fired' state when done + AstCMethodHard* const clearp = new AstCMethodHard{flp, currp(), "clearFired"}; + ifp->addIfsp(clearp); + clearp->dtypeSetVoid(); + clearp->statement(true); + + // Enqueue for clearing 'triggered' state on next eval + AstTextBlock* const blockp = new AstTextBlock{flp}; + ifp->addIfsp(blockp); + const auto add = [&](const string& text) { blockp->addText(flp, text, true); }; + add("vlSymsp->enqueueTriggeredEventForClearing("); + blockp->addNodep(currp()); + add(");\n"); + } + + // Get 'fired' state + AstCMethodHard* const callp = new AstCMethodHard{flp, currp(), "isFired"}; + callp->dtypeSetBit(); + return {callp, false}; + } + case VEdgeType::ET_TRUE: // + return {currp(), false}; + default: // LCOV_EXCL_START + senItemp->v3fatalSrc("Unknown edge type"); + return {nullptr, false}; + } // LCOV_EXCL_STOP + } + +public: + // Returns the expression computing the trigger, and a bool indicating that + // this trigger should be fired on the first evaluation (at initialization) + std::pair build(const AstSenTree* senTreep) { + FileLine* const flp = senTreep->fileline(); + AstNode* resultp = nullptr; + bool firedAtInitialization = false; + for (AstSenItem* senItemp = senTreep->sensesp(); senItemp; + senItemp = VN_AS(senItemp->nextp(), SenItem)) { + const auto& pair = createTerm(senItemp); + if (AstNode* const termp = pair.first) { + resultp = resultp ? new AstOr{flp, resultp, termp} : termp; + firedAtInitialization |= pair.second; + } + } + return {resultp, firedAtInitialization}; + } + + std::vector getAndClearLocals() { return std::move(m_locals); } + + std::vector getAndClearPreUpdates() { + m_hasPreUpdate.clear(); + return std::move(m_preUpdates); + } + + std::vector getAndClearPostUpdates() { + m_hasPostUpdate.clear(); + return std::move(m_postUpdates); + } + + // CONSTRUCTOR + SenExprBuilder(AstNetlist* netlistp, AstCFunc* initp) + : m_initp{initp} + , m_scopeTopp{netlistp->topScopep()->scopep()} {} +}; + +#endif // Guard From a2e1b32a1c58253f7eb761666d8ac63b4e599640 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Mon, 5 Sep 2022 16:19:19 +0200 Subject: [PATCH 041/177] Fix inlining of forks (#3594) Before this change, some forked processes were being inlined in `V3Timing` because they contained no `CAwait`s. This only works under the assumption that no `CAwait`s will be added there later, which is not true, as a function called by a forked process could be turned into a coroutine later. The call would be wrapped in a new `CAwait`, but the process itself would have already been inlined at this point. This commit moves the inlining to `transformForks` in `V3SchedTiming`, which is called at a point when all `CAwait`s are already in place. Signed-off-by: Krzysztof Bieganski --- src/V3SchedTiming.cpp | 58 ++++++++++++++++++----------- src/V3Timing.cpp | 23 +++--------- test_regress/t/t_timing_debug2.out | 14 +++++-- test_regress/t/t_timing_fork_comb.v | 4 +- 4 files changed, 54 insertions(+), 45 deletions(-) diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index edffdaabc..24612ccd4 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -242,6 +242,7 @@ void transformForks(AstNetlist* const netlistp) { // STATE bool m_inClass = false; // Are we in a class? + bool m_beginHasAwaits = false; // Does the current begin have awaits? AstFork* m_forkp = nullptr; // Current fork AstCFunc* m_funcp = nullptr; // Current function @@ -309,8 +310,11 @@ void transformForks(AstNetlist* const netlistp) { iterateChildren(nodep); m_funcp = nullptr; } - virtual void visit(AstVar* nodep) override { nodep->user1(true); } + virtual void visit(AstVar* nodep) override { + if (!m_forkp) nodep->user1(true); + } virtual void visit(AstFork* nodep) override { + if (m_forkp) return; // Handle forks in forks after moving them to new functions VL_RESTORER(m_forkp); m_forkp = nodep; iterateChildrenConst(nodep); // Const, so we don't iterate the calls twice @@ -321,28 +325,40 @@ void transformForks(AstNetlist* const netlistp) { } virtual void visit(AstBegin* nodep) override { UASSERT_OBJ(m_forkp, nodep, "Begin outside of a fork"); - UASSERT_OBJ(!nodep->name().empty(), nodep, "Begin needs a name"); - FileLine* const flp = nodep->fileline(); - // Create a function to put this begin's statements in - AstCFunc* const newfuncp - = new AstCFunc{flp, nodep->name(), m_funcp->scopep(), "VlCoroutine"}; - m_funcp->addNextHere(newfuncp); - newfuncp->isLoose(m_funcp->isLoose()); - newfuncp->slow(m_funcp->slow()); - newfuncp->isConst(m_funcp->isConst()); - newfuncp->declPrivate(true); - // Replace the begin with a call to the newly created function - auto* const callp = new AstCCall{flp, newfuncp}; - nodep->replaceWith(callp); - // If we're in a class, add a vlSymsp arg - if (m_inClass) { - newfuncp->argTypes(EmitCBaseVisitor::symClassVar()); - callp->argTypes("vlSymsp"); + // Start with children, so later we only find awaits that are actually in this begin + m_beginHasAwaits = false; + iterateChildrenConst(nodep); + if (m_beginHasAwaits) { + UASSERT_OBJ(!nodep->name().empty(), nodep, "Begin needs a name"); + // Create a function to put this begin's statements in + FileLine* const flp = nodep->fileline(); + AstCFunc* const newfuncp + = new AstCFunc{flp, nodep->name(), m_funcp->scopep(), "VlCoroutine"}; + m_funcp->addNextHere(newfuncp); + newfuncp->isLoose(m_funcp->isLoose()); + newfuncp->slow(m_funcp->slow()); + newfuncp->isConst(m_funcp->isConst()); + newfuncp->declPrivate(true); + // Replace the begin with a call to the newly created function + auto* const callp = new AstCCall{flp, newfuncp}; + nodep->replaceWith(callp); + // If we're in a class, add a vlSymsp arg + if (m_inClass) { + newfuncp->argTypes(EmitCBaseVisitor::symClassVar()); + callp->argTypes("vlSymsp"); + } + // Put the begin's statements in the function, delete the begin + newfuncp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext()); + remapLocals(newfuncp, callp); + } else { + // No awaits, just inline the forked process + nodep->replaceWith(nodep->stmtsp()->unlinkFrBackWithNext()); } - // Put the begin's statements in the function, delete the begin - newfuncp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext()); VL_DO_DANGLING(nodep->deleteTree(), nodep); - remapLocals(newfuncp, callp); + } + virtual void visit(AstCAwait* nodep) override { + m_beginHasAwaits = true; + iterateChildrenConst(nodep); } //-------------------- diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 671435ff8..7d456cc2b 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -614,25 +614,12 @@ private: VL_RESTORER(m_procp); m_procp = beginp; iterate(beginp); - if (!m_procp->user2()) { - // No awaits, we can inline this process - if (auto* const stmtsp = beginp->stmtsp()) { - nodep->addHereThisAsNext(stmtsp->unlinkFrBackWithNext()); - } - VL_DO_DANGLING(beginp->unlinkFrBack()->deleteTree(), beginp); - // We inlined at least one process, so we can consider it joined; convert join_any - // to join_none - if (nodep->joinType().joinAny()) nodep->joinType(VJoinType::JOIN_NONE); - } else { - // Name the begin (later the name will be used for a new function) - beginp->name(nodep->name() + "__" + cvtToStr(idx++)); - } - } - if (!nodep->stmtsp()) { - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } else if (!nodep->joinType().joinNone()) { - makeForkJoin(nodep); + // Even if we do not find any awaits, we cannot simply inline the process here, as new + // awaits could be added later. + // Name the begin (later the name will be used for a new function) + beginp->name(nodep->name() + "__" + cvtToStr(idx++)); } + if (!nodep->joinType().joinNone()) makeForkJoin(nodep); } //-------------------- diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out index 1da1ef371..0972ba27c 100644 --- a/test_regress/t/t_timing_debug2.out +++ b/test_regress/t/t_timing_debug2.out @@ -6,11 +6,13 @@ -V{t#,#}+ Vt_timing_debug2___024root___eval_static -V{t#,#}+ Vt_timing_debug2___024root___eval_initial -V{t#,#}+ Vt_timing_debug2___024root___eval_initial__TOP__0 -[0] fork..join process 4 --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 +-V{t#,#} Process forked at t/t_timing_fork_join.v:14 finished -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__2 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__3 +[0] fork..join process 4 +-V{t#,#} Process forked at t/t_timing_fork_join.v:18 finished +-V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__5 -V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:13 -V{t#,#}+ Vt_timing_debug2___024root___eval_initial__TOP__1 -V{t#,#}+ Vt_timing_debug2___024root___eval_settle @@ -127,10 +129,11 @@ -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:16 [16] fork in fork starts -[16] fork..join process 8 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__2 +[16] fork..join process 8 +-V{t#,#} Process forked at t/t_timing_fork_join.v:25 finished -V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:21 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act @@ -242,9 +245,11 @@ -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:30 [64] main process -fork..join_any process 2 -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 -V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:33 +fork..join_any process 2 +-V{t#,#} Process forked at t/t_timing_fork_join.v:37 finished +-V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:31 back in main process -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act @@ -283,6 +288,7 @@ back in main process -V{t#,#} Resuming processes waiting for @([event] t.e1) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:33 fork..join_any process 1 +-V{t#,#} Process forked at t/t_timing_fork_join.v:32 finished -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act diff --git a/test_regress/t/t_timing_fork_comb.v b/test_regress/t/t_timing_fork_comb.v index 9463698c5..8c7daec56 100644 --- a/test_regress/t/t_timing_fork_comb.v +++ b/test_regress/t/t_timing_fork_comb.v @@ -48,9 +48,9 @@ module t; if (a != 10) $stop; if (b != 20) $stop; if (c != 30) $stop; - if (d != 50) $stop; + if (d != 45) $stop; if (e != 75) $stop; - if (f != 125) $stop; + if (f != 107) $stop; if (v != 'b001010) $stop; $write("*-* All Finished *-*\n"); $finish; From 90ab746a42e846d08e7e082cdef92d7ac42879b3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 6 Sep 2022 14:59:40 +0100 Subject: [PATCH 042/177] Make it possible to parallelize ico and act scheduling sections Small fixup patch so the 'ico' and 'act' scheduling sections could be ordered as multi-threaded. However, we still only order these single threaded at the moment (but switching them to multi-threaded now works). --- src/V3EmitCSyms.cpp | 4 +++- src/V3Order.cpp | 2 +- src/V3Partition.cpp | 17 ++++++++++------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index 0a13b7ecd..8195d705d 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -449,7 +449,9 @@ void EmitCSyms::emitSymHdr() { if (v3Global.opt.mtasks()) { puts("\n// MULTI-THREADING\n"); puts("VlThreadPool* const __Vm_threadPoolp;\n"); - puts("bool __Vm_even_cycle = false;\n"); + puts("bool __Vm_even_cycle__ico = false;\n"); + puts("bool __Vm_even_cycle__act = false;\n"); + puts("bool __Vm_even_cycle__nba = false;\n"); } if (v3Global.opt.profExec()) { diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 88ee47cd9..209b38205 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -1371,7 +1371,7 @@ void OrderProcess::processMTasks() { // Create the AstExecGraph node which represents the execution // of the MTask graph. FileLine* const rootFlp = v3Global.rootp()->fileline(); - AstExecGraph* const execGraphp = new AstExecGraph{rootFlp, "eval"}; + AstExecGraph* const execGraphp = new AstExecGraph{rootFlp, m_tag}; m_result.push_back(execGraphp); // Create CFuncs and bodies for each MTask. diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index 441aeea22..ac96491f3 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -3150,15 +3150,15 @@ static const std::vector createThreadFunctions(const ThreadSchedule& } // Unblock the fake "final" mtask when this thread is finished - funcp->addStmtsp( - new AstCStmt(fl, "vlSelf->__Vm_mtaskstate_final.signalUpstreamDone(even_cycle);\n")); + funcp->addStmtsp(new AstCStmt(fl, "vlSelf->__Vm_mtaskstate_final__" + tag + + ".signalUpstreamDone(even_cycle);\n")); } // Create the fake "final" mtask state variable AstBasicDType* const mtaskStateDtypep = v3Global.rootp()->typeTablep()->findBasicDType(fl, VBasicDTypeKwd::MTASKSTATE); AstVar* const varp - = new AstVar(fl, VVarType::MODULETEMP, "__Vm_mtaskstate_final", mtaskStateDtypep); + = new AstVar(fl, VVarType::MODULETEMP, "__Vm_mtaskstate_final__" + tag, mtaskStateDtypep); varp->valuep(new AstConst(fl, funcps.size())); varp->protect(false); // Do not protect as we still have references in AstText modp->addStmtp(varp); @@ -3170,6 +3170,7 @@ static void addThreadStartToExecGraph(AstExecGraph* const execGraphp, const std::vector& funcps) { // FileLine used for constructing nodes below FileLine* const fl = v3Global.rootp()->fileline(); + const string& tag = execGraphp->name(); // Add thread function invocations to execGraph const auto addStrStmt = [=](const string& stmt) -> void { // @@ -3179,7 +3180,8 @@ static void addThreadStartToExecGraph(AstExecGraph* const execGraphp, execGraphp->addStmtsp(new AstText(fl, text, /* tracking: */ true)); }; - addStrStmt("vlSymsp->__Vm_even_cycle = !vlSymsp->__Vm_even_cycle;\n"); + addStrStmt("vlSymsp->__Vm_even_cycle__" + tag + " = !vlSymsp->__Vm_even_cycle__" + tag + + ";\n"); const uint32_t last = funcps.size() - 1; for (uint32_t i = 0; i <= last; ++i) { @@ -3188,17 +3190,18 @@ static void addThreadStartToExecGraph(AstExecGraph* const execGraphp, // The first N-1 will run on the thread pool. addTextStmt("vlSymsp->__Vm_threadPoolp->workerp(" + cvtToStr(i) + ")->addTask("); execGraphp->addStmtsp(new AstAddrOfCFunc(fl, funcp)); - addTextStmt(", vlSelf, vlSymsp->__Vm_even_cycle);\n"); + addTextStmt(", vlSelf, vlSymsp->__Vm_even_cycle__" + tag + ");\n"); } else { // The last will run on the main thread. AstCCall* const callp = new AstCCall(fl, funcp); - callp->argTypes("vlSelf, vlSymsp->__Vm_even_cycle"); + callp->argTypes("vlSelf, vlSymsp->__Vm_even_cycle__" + tag); execGraphp->addStmtsp(callp); addStrStmt("Verilated::mtaskId(0);\n"); } } - addStrStmt("vlSelf->__Vm_mtaskstate_final.waitUntilUpstreamDone(vlSymsp->__Vm_even_cycle);\n"); + addStrStmt("vlSelf->__Vm_mtaskstate_final__" + tag + + ".waitUntilUpstreamDone(vlSymsp->__Vm_even_cycle__" + tag + ");\n"); } static void implementExecGraph(AstExecGraph* const execGraphp) { From ab6e1c23998788a4f3048b189024c5fd535e268d Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Thu, 15 Sep 2022 20:26:08 -0400 Subject: [PATCH 043/177] Commentary on --main --- bin/verilator | 3 +-- docs/guide/exe_verilator.rst | 21 +++++++++------------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/bin/verilator b/bin/verilator index b68812d61..36332490c 100755 --- a/bin/verilator +++ b/bin/verilator @@ -341,10 +341,9 @@ detailed descriptions of these arguments. --lib-create Create a DPI library +libext++[ext]... Extensions for finding modules --lint-only Lint, but do not make output - --main Generate a main C++ file --make Generate scripts for specified build tool -MAKEFLAGS Arguments to pass to make during --build - --main Generate C++ main() + --main Generate C++ main() file --max-num-width Maximum number width (default: 64K) --Mdir Name of output object directory --MMD Create .d dependency files diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index ce2140c7a..123d56059 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -684,16 +684,6 @@ Summary: If the design is not to be completely Verilated see also the :vlopt:`--bbox-sys` and :vlopt:`--bbox-unsup` options. -.. option:: --main - - Generates a simple main C++ file. Without :vlopt:`--timing`, you need to - modify this file to provide some stimuli to the design. However, this option - is especially useful with :vlopt:`--timing` and delay-generated clocks, as - then the main file provides a timing-enabled eval loop and requires no - modification by the user. :vlopt:`--build` can then be used to build the - simulation, allowing you to use Verilator without directly invoking - the C++ toolchain. - .. option:: --make Generates a script for the specified build tool. @@ -720,8 +710,15 @@ Summary: Generates a top-level C++ main() file that supports parsing arguments, but does not drive any inputs. This is sufficient to use for top-level - SystemVerilog designs that has no inputs, and does not need the C++ to - do any time advancement. + SystemVerilog designs that has no inputs. + + This option can also be used once to generate a main .cpp file as a + starting point for editing. Copy it outside the obj directory, manually + edit, and then pass the filename on later Verilator command line + invocations. + + Typically used with :vlopt:`--timing` to support delay-generated clocks, + and :vlopt:`--build`. Implies :vlopt:`--cc` if no other output mode was provided. From a214fd1f7875c364715bc537c5fa40859a4ba5d3 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 17 Sep 2022 08:56:41 -0400 Subject: [PATCH 044/177] Internals: Fix constructor syntax in new develop-v5 code --- src/V3AstNodeOther.h | 6 +++--- src/V3Const.cpp | 4 ++-- src/V3Delayed.cpp | 12 ++++++------ src/V3Order.cpp | 6 +++--- src/V3Partition.cpp | 6 +++--- src/V3Sched.cpp | 7 ++++--- src/V3SenExprBuilder.h | 2 +- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 123ab7a4b..dfd4290f8 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -3928,7 +3928,7 @@ public: ASTGEN_MEMBERS_Assign; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; - return new AstAssign(this->fileline(), lhsp, rhsp, controlp); + return new AstAssign{fileline(), lhsp, rhsp, controlp}; } bool brokeLhsMustBeLvalue() const override { return true; } }; @@ -3949,7 +3949,7 @@ public: ASTGEN_MEMBERS_AssignDly; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; - return new AstAssignDly(this->fileline(), lhsp, rhsp, controlp); + return new AstAssignDly{fileline(), lhsp, rhsp, controlp}; } bool isGateOptimizable() const override { return false; } string verilogKwd() const override { return "<="; } @@ -4011,7 +4011,7 @@ public: void strengthSpecp(AstStrengthSpec* const strengthSpecp) { setOp4p((AstNode*)strengthSpecp); } AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; - return new AstAssignW(this->fileline(), lhsp, rhsp, controlp); + return new AstAssignW{fileline(), lhsp, rhsp, controlp}; } bool brokeLhsMustBeLvalue() const override { return true; } AstAlways* convertToAlways(); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index bed6ca32a..e5dcb93a4 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2693,9 +2693,9 @@ private: // 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())); + new AstSenItem{nodep->fileline(), AstSenItem::Initial{}}); } else { - nodep->replaceWith(new AstSenItem(nodep->fileline(), AstSenItem::Never())); + nodep->replaceWith(new AstSenItem{nodep->fileline(), AstSenItem::Never{}}); } VL_DO_DANGLING(nodep->deleteTree(), nodep); } else { diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index 878de72f6..4e75e0788 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -556,14 +556,14 @@ private: if (!dlyvscp) { // First use of this delayed variable const string newvarname = (string("__Vdly__") + nodep->varp()->shortName()); dlyvscp = createVarSc(oldvscp, newvarname, 0, nullptr); - AstNodeAssign* const prep = new AstAssignPre( + AstNodeAssign* const prep = new AstAssignPre{ nodep->fileline(), - new AstVarRef(nodep->fileline(), dlyvscp, VAccess::WRITE), - new AstVarRef(nodep->fileline(), oldvscp, VAccess::READ)); - AstNodeAssign* const postp = new AstAssignPost( + new AstVarRef{nodep->fileline(), dlyvscp, VAccess::WRITE}, + new AstVarRef{nodep->fileline(), oldvscp, VAccess::READ}}; + AstNodeAssign* const postp = new AstAssignPost{ nodep->fileline(), - new AstVarRef(nodep->fileline(), oldvscp, VAccess::WRITE), - new AstVarRef(nodep->fileline(), dlyvscp, VAccess::READ)); + new AstVarRef{nodep->fileline(), oldvscp, VAccess::WRITE}, + new AstVarRef{nodep->fileline(), dlyvscp, VAccess::READ}}; postp->lhsp()->user2(true); // Don't detect this assignment oldvscp->user1p(dlyvscp); // So we can find it later // Make new ACTIVE with identical sensitivity tree diff --git a/src/V3Order.cpp b/src/V3Order.cpp index f8aeb96d6..fd2a4a25e 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -778,7 +778,7 @@ public: : m_pomGraphp{pomGraphp} {} MTaskMoveVertex* makeVertexp(OrderLogicVertex* lvertexp, const OrderEitherVertex* varVertexp, const AstSenTree* domainp) override { - return new MTaskMoveVertex(m_pomGraphp, lvertexp, varVertexp, domainp); + return new MTaskMoveVertex{m_pomGraphp, lvertexp, varVertexp, domainp}; } private: @@ -1264,9 +1264,9 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, newStmtsr = 0; scopep->addActivep(newFuncpr); // Create top call to it - AstCCall* const callp = new AstCCall(nodep->fileline(), newFuncpr); + AstCCall* const callp = new AstCCall{nodep->fileline(), newFuncpr}; // Where will we be adding the call? - AstActive* const newActivep = new AstActive(nodep->fileline(), name, domainp); + AstActive* const newActivep = new AstActive{nodep->fileline(), name, domainp}; newActivep->addStmtsp(callp); if (!activep) { activep = newActivep; diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index e18af2528..baefa2ea3 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -3150,15 +3150,15 @@ static const std::vector createThreadFunctions(const ThreadSchedule& } // Unblock the fake "final" mtask when this thread is finished - funcp->addStmtsp(new AstCStmt(fl, "vlSelf->__Vm_mtaskstate_final__" + tag - + ".signalUpstreamDone(even_cycle);\n")); + funcp->addStmtsp(new AstCStmt{fl, "vlSelf->__Vm_mtaskstate_final__" + tag + + ".signalUpstreamDone(even_cycle);\n"}); } // Create the fake "final" mtask state variable AstBasicDType* const mtaskStateDtypep = v3Global.rootp()->typeTablep()->findBasicDType(fl, VBasicDTypeKwd::MTASKSTATE); AstVar* const varp - = new AstVar(fl, VVarType::MODULETEMP, "__Vm_mtaskstate_final__" + tag, mtaskStateDtypep); + = new AstVar{fl, VVarType::MODULETEMP, "__Vm_mtaskstate_final__" + tag, mtaskStateDtypep}; varp->valuep(new AstConst(fl, funcps.size())); varp->protect(false); // Do not protect as we still have references in AstText modp->addStmtp(varp); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 1f1ca4c20..48acaa9cc 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -364,8 +364,9 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui const uint32_t nTriggers = senTreeps.size() + extraTriggers.size(); // Create the TRIGGERVEC variable - AstBasicDType* const tDtypep = new AstBasicDType(flp, VBasicDTypeKwd::TRIGGERVEC, - VSigning::UNSIGNED, nTriggers, nTriggers); + AstBasicDType* const tDtypep + = new AstBasicDType{flp, VBasicDTypeKwd::TRIGGERVEC, VSigning::UNSIGNED, + static_cast(nTriggers), static_cast(nTriggers)}; netlistp->typeTablep()->addTypesp(tDtypep); AstVarScope* const vscp = scopeTopp->createTemp("__V" + name + "Triggered", tDtypep); @@ -489,7 +490,7 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui const auto add = [&](const string& text) { blockp->addText(flp, text, true); }; add("#ifdef VL_DEBUG\n"); add("if (VL_UNLIKELY(vlSymsp->_vm_contextp__->debug())) {\n"); - blockp->addNodep(new AstCCall(flp, dumpp)); + blockp->addNodep(new AstCCall{flp, dumpp}); add("}\n"); add("#endif\n"); } diff --git a/src/V3SenExprBuilder.h b/src/V3SenExprBuilder.h index bf18d9053..59592a29f 100644 --- a/src/V3SenExprBuilder.h +++ b/src/V3SenExprBuilder.h @@ -160,7 +160,7 @@ class SenExprBuilder final { resultp->dtypeSetBit(); return {resultp, true}; } - return {new AstNeq(flp, currp(), prevp()), true}; + return {new AstNeq{flp, currp(), prevp()}, true}; case VEdgeType::ET_BOTHEDGE: // return {lsb(new AstXor{flp, currp(), prevp()}), false}; case VEdgeType::ET_POSEDGE: // From 550a5111b744fb6d2cb6731e3285c480d4a988e5 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Tue, 20 Sep 2022 20:28:43 -0400 Subject: [PATCH 045/177] Commentary --- docs/guide/contributors.rst | 4 +- docs/guide/exe_verilator.rst | 6 +-- docs/guide/extensions.rst | 2 +- docs/guide/warnings.rst | 75 ++++++++++++++++++++++-------------- 4 files changed, 52 insertions(+), 35 deletions(-) diff --git a/docs/guide/contributors.rst b/docs/guide/contributors.rst index 1c7733c9e..f4d3f4956 100644 --- a/docs/guide/contributors.rst +++ b/docs/guide/contributors.rst @@ -151,8 +151,8 @@ In 2018, Verilator 4.000 was released with multithreaded support. In 2019, Verilator joined the `CHIPS Alliance `_. -In 2022, Verilator 5.000 was released with IEEE scheduling semantics -and other improvements. +In 2022, Verilator 5.000 was released with IEEE scheduling semantics, +fork/join, delay handling, and other improvements. Currently, various language features and performance enhancements are added as the need arises. Verilator is now about 3x faster than in 2002, and is diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 81d0dce04..31ba43c54 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -181,7 +181,7 @@ Summary: individual bits, Verilator will attempt to decompose the vector and connect the single-bit clock signals. - In versions prior to 5.002, the clocker attribute is useful in cases where + In versions prior to 5.000, the clocker attribute is useful in cases where Verilator does not properly distinguish clock signals from other data signals. Using clocker will cause the signal indicated to be considered a clock, and remove it from the combinatorial logic reevaluation checking @@ -795,7 +795,7 @@ Summary: Deprecated and has no effect (ignored). - In versions prior to 5.002: + In versions prior to 5.000: Rarely needed. Disables a bug fix for ordering of clock enables with delayed assignments. This option should only be used when suggested by @@ -1618,7 +1618,7 @@ The grammar of configuration commands is as follows: Deprecated and has no effect (ignored). - In versions prior to 5.002: + In versions prior to 5.000: Indicate the signal is used to gate a clock, and the user takes responsibility for insuring there are no races related to it. diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index f353f6f43..e0622beb3 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -162,7 +162,7 @@ or "`ifdef`"'s may break other tools. Deprecated and has no effect (ignored). - In versions prior to 5.002: + In versions prior to 5.000: 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 diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index a94898a10..00f929b04 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -316,6 +316,20 @@ List Of Warnings potential for reset glitches. +.. option:: CLKDATA + + Historical, never issued since version 5.000. + + Warned that clock signal was mixed used with/as data signal. The + checking for this warning was 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 could be disabled without affecting the simulation + result. But it was recommended to check the warning as it may have + degrated the performance of the Verilated model. + + .. option:: CMPCONST .. TODO better example @@ -577,6 +591,14 @@ List Of Warnings with a newline." +.. option:: GENCLK + + Historical, never issued since version 5.000. + + Indicated that the specified signal was generated inside the model, and + was also being used as a clock. + + .. option:: HIERBLOCK Warns that the top module is marked as a hierarchy block by the @@ -634,6 +656,16 @@ List Of Warnings simulate correctly. +.. option:: IMPERFECTSCH + + Historical, never issued since version 5.000. + + Warned that the scheduling of the model is not absolutely perfect, and + some manual code edits may result in faster performance. This warning + defaulted to off, was not part of -Wall, and had to be turned on + explicitly before the top module statement is processed. + + .. option:: IMPLICIT .. TODO better example @@ -1362,6 +1394,16 @@ List Of Warnings undriven (...) and will be removed". +.. option:: UNOPT + + Historical, never issued since version 5.000. + + Warned that due to some construct, optimization of the specified signal + or block was disabled. + + Ignoring this warning only slowed simulations, it simulated correctly. + + .. option:: UNOPTFLAT .. TODO better example @@ -1430,6 +1472,10 @@ List Of Warnings the :option:`/*verilator&32;isolate_assignments*/` metacomment described above. + Prior to version 5.000, the UNOPTFLAT warning may also have been due to + clock enables, identified from the reported path going through a clock + gating instance. To fix these, the clock_enable meta comment was used. + To assist in resolving UNOPTFLAT, the option :vlopt:`--report-unoptflat` can be used, which will provide suggestions for variables that can be split up, and a graph of all the nodes connected in the loop. See the @@ -1700,32 +1746,3 @@ List Of Warnings Inactive region. Such processes do get resumed in the same time slot somewhere in the Active region. Issued only if Verilator is run with the :vlopt:`--timing` option. - - -Historical Warnings -=================== - -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. From 95145038b498cf93c8174a64fc66a859bf0fc7b8 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 15 Sep 2022 19:43:56 +0100 Subject: [PATCH 046/177] Generate AstNode accessors via astgen Introduce the @astgen directives parsed by astgen, currently used for the generation child node (operand) accessors. Please see the updated internal documentation for details. --- docs/internals.rst | 100 +++- src/V3Active.cpp | 8 +- src/V3Assert.cpp | 20 +- src/V3AssertPre.cpp | 2 +- src/V3AstInlines.h | 21 +- src/V3AstNodeDType.h | 149 ++---- src/V3AstNodeMath.h | 470 +++++++---------- src/V3AstNodeOther.h | 1062 ++++++++++++++++----------------------- src/V3AstNodes.cpp | 64 +-- src/V3Begin.cpp | 10 +- src/V3Branch.cpp | 2 +- src/V3Broken.cpp | 4 +- src/V3CCtors.cpp | 6 +- src/V3CUse.cpp | 2 +- src/V3Case.cpp | 10 +- src/V3Changed.cpp | 8 +- src/V3Class.cpp | 16 +- src/V3Clean.cpp | 6 +- src/V3Clock.cpp | 8 +- src/V3Common.cpp | 6 +- src/V3Config.cpp | 4 +- src/V3Const.cpp | 105 ++-- src/V3Coverage.cpp | 46 +- src/V3Delayed.cpp | 16 +- src/V3DepthBlock.cpp | 2 +- src/V3Descope.cpp | 2 +- src/V3EmitCFunc.h | 20 +- src/V3EmitV.cpp | 26 +- src/V3Expand.cpp | 4 +- src/V3Force.cpp | 4 +- src/V3Gate.cpp | 4 +- src/V3GenClk.cpp | 4 +- src/V3Inline.cpp | 30 +- src/V3InstrCount.cpp | 14 +- src/V3Life.cpp | 4 +- src/V3LinkCells.cpp | 4 +- src/V3LinkDot.cpp | 8 +- src/V3LinkInc.cpp | 8 +- src/V3LinkJump.cpp | 12 +- src/V3LinkLevel.cpp | 10 +- src/V3LinkParse.cpp | 4 +- src/V3LinkResolve.cpp | 4 +- src/V3MergeCond.cpp | 16 +- src/V3Order.cpp | 8 +- src/V3Param.cpp | 12 +- src/V3ParseImp.cpp | 2 +- src/V3Partition.cpp | 6 +- src/V3Premit.cpp | 4 +- src/V3ProtectLib.cpp | 66 +-- src/V3Randomize.cpp | 2 +- src/V3Reloop.cpp | 2 +- src/V3Sched.cpp | 40 +- src/V3SchedTiming.cpp | 6 +- src/V3Scope.cpp | 30 +- src/V3SenExprBuilder.h | 8 +- src/V3SenTree.h | 2 +- src/V3Simulate.h | 18 +- src/V3Slice.cpp | 4 +- src/V3Split.cpp | 18 +- src/V3SplitVar.cpp | 10 +- src/V3Stats.cpp | 4 +- src/V3Table.cpp | 8 +- src/V3Task.cpp | 28 +- src/V3Timing.cpp | 32 +- src/V3Trace.cpp | 12 +- src/V3TraceDecl.cpp | 6 +- src/V3Tristate.cpp | 40 +- src/V3Unknown.cpp | 10 +- src/V3Unroll.cpp | 14 +- src/V3VariableOrder.cpp | 2 +- src/V3Width.cpp | 54 +- src/astgen | 146 +++++- src/bisonpre | 5 +- src/verilog.y | 82 +-- 74 files changed, 1427 insertions(+), 1579 deletions(-) diff --git a/docs/internals.rst b/docs/internals.rst index 44c898676..d575bccec 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -96,10 +96,11 @@ this. Each ``AstNode`` has pointers to up to four children, accessed by the ``op1p`` through ``op4p`` methods. These methods are then abstracted in a specific Ast\* node class to a more specific name. For example with the -``AstIf`` node (for ``if`` statements), ``ifsp`` calls ``op2p`` to give the +``AstIf`` node (for ``if`` statements), ``thensp`` calls ``op2p`` to give the pointer to the AST for the "then" block, while ``elsesp`` calls ``op3p`` to give the pointer to the AST for the "else" block, or NULL if there is not -one. +one. These accessors are automatically generated by ``astgen`` after +parsing the ``@astgen`` directives in the specific ``AstNode`` subclasses. ``AstNode`` has the concept of a next and previous AST - for example the next and previous statements in a block. Pointers to the AST for these @@ -1005,22 +1006,99 @@ code: The ``astgen`` Script --------------------- -Some of the code implementing passes is extremely repetitive, and must be -implemented for each sub-class of ``AstNode``. However, while repetitive, -there is more variability than can be handled in C++ macros. +The ``astgen`` script is used to generate some of the repetitive C++ code +related to the ``AstNode`` type hierarchy. An example is the abstract ``visit`` +methods in ``VNVisitor``. There are other uses, please see the ``*__gen*`` +files in the bulid directories and the ``astgen`` script itself for details. A +description of the more advanced features of ``astgen`` are provided here. -In Verilator this is implemented by using a script, ``astgen`` to -pre-process the C++ code. For example in ``V3Const.cpp`` this is used to -implement the ``visit()`` functions for each binary operation using the -``TREEOP`` macro. -The original C source code is transformed into C code in the ``obj_opt`` +Generating ``AstNode`` members +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some of the member s of ``AstNode`` sub-classes are generated by ``astgen``. +These are emitted as pre-processor macro definitions, which then need to be +added to the ``AstNode`` sub-classes they correspond to. Specifically ``class +AstFoo`` should contain an instance of ``ASTGEN_MEMBERS_Foo;`` at class scope. +The ``astgen`` script checks and errors if this is not present. The method +generated depends on whether the class is a concrete final class, or an +abstract ``AstNode*`` base-class, and on ``@astgen`` directives present in +comment sections in the body of the ``AstNode`` sub-class definitions. + + +List of ``@astgen`` directives +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``@astgen`` directives in comments contained in the body of ``AstNode`` +sub-class definitions are parsed and contribute to the code generated by +``astgen``. The general syntax is ``@astgen := ``, +where ```` determines what is being defined, and ```` is +a ```` dependent description of the definition. The list of +``@astgen`` directives is as follows: + + +``op`` operand directives +""""""""""""""""""""""""""""" + +The ``op1``, ``op2``, ``op3`` and ``op4`` directives are used to describe the +name and type of the up to 4 child operands of a node. The syntax of the +```` field is `` : ``, where ```` +will be used as the base name of the generated operand accessors, and +```` is one of: + +1. An ``AstNode`` sub-class, defining the operand to be of that type, always + no-null, and with an always null ``nextp()``. That is, the child node is + always present, and is a single ``AstNode`` (as opposed to a list). + +2. ``Optional[]``. This is just like in point 1 above, but + defines the child node to be optional, meaning it may be null. + +3. ``List[AstNode sub-class]`` describes a list operand, which means the child + node may have a non-null ``nextp()`` and in addition the child itself may be + null, representing an empty list. + + +An example of the full syntax of the directive is +``@astgen op1 := lhsp : AstNodeMath``. + +``astnode`` generates accessors for the child nodes based on these directives. +For non-list children, the names of the getter and setter both are that of the +given ````. For list type children, the getter is ````, +and instead of the setter, there an ``add`` method is generated +that appends new nodes (or lists of nodes) to the child list. + +``alias op`` operand alias directives +"""""""""""""""""""""""""""""""""""""""" + +If a super-class already defined a name and type for a child node using the +``op`` directive, but a more appropriate name exists in the context of a +sub-class, then the alias directive can be used to introduce an additional name +for the child node. The is ``alias op := `` where +```` is the new name. ``op`` must have been defined in some +super-class of the current node. + +Example: ``@astgen alias op1 := condp`` + + +Additional features of ``astgen`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In addition to generating ``AstNode`` members as described above, +``astgen`` is also use to handle some of the repetitive implementation code +that is still variable enough not to be handled in C++ macros. + +In particular, ``astgen`` is used to pre-process some of the C++ source +files. For example in ``V3Const.cpp``, it is used to implement the +``visit()`` functions for each binary operation using the ``TREEOP`` macro. + +The original C++ source code is transformed into C++ code in the ``obj_opt`` and ``obj_dbg`` sub-directories (the former for the optimized version of Verilator, the latter for the debug version). So for example ``V3Const.cpp`` into ``V3Const__gen.cpp``. -Visitor Functions ----------------- +Visitor Functions +----------------- Verilator uses the "Visitor" design pattern to implement its refinement and optimization passes. This allows separation of the pass algorithm from the diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 1e8d0cb90..7bf61c9cd 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -218,7 +218,7 @@ private: // METHODS void addActive(AstActive* nodep) { UASSERT_OBJ(m_scopep, nodep, "nullptr scope"); - m_scopep->addActivep(nodep); + m_scopep->addBlocksp(nodep); } // VISITORS @@ -324,7 +324,7 @@ private: LatchDetectGraphVertex* const parentp = m_graph.currentp(); LatchDetectGraphVertex* const branchp = m_graph.addPathVertex(parentp, "BRANCH", true); m_graph.addPathVertex(branchp, "IF"); - iterateAndNextNull(nodep->ifsp()); + iterateAndNextNull(nodep->thensp()); m_graph.addPathVertex(branchp, "ELSE"); iterateAndNextNull(nodep->elsesp()); m_graph.currentp(parentp); @@ -529,7 +529,7 @@ private: visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS_COMB); } void visit(AstAlways* nodep) override { - if (!nodep->bodysp()) { // Empty always. Remove it now. + if (!nodep->stmtsp()) { // Empty always. Remove it now. VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); return; } @@ -539,7 +539,7 @@ private: void visit(AstAlwaysPostponed* nodep) override { // Might be empty with later optimizations, so this assertion can be removed, // but for now it is guaranteed to be not empty. - UASSERT_OBJ(nodep->bodysp(), nodep, "Should not be empty"); + UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not be empty"); visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS); } void visit(AstAlwaysPublic* nodep) override { diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index f8d73be67..e94440da1 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -76,7 +76,7 @@ private: if (!m_monitorNumVarp) { m_monitorNumVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorNum", nodep->findUInt64DType()}; - v3Global.rootp()->dollarUnitPkgAddp()->addStmtp(m_monitorNumVarp); + v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorNumVarp); } const auto varrefp = new AstVarRef(nodep->fileline(), m_monitorNumVarp, access); varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp()); @@ -86,7 +86,7 @@ private: if (!m_monitorOffVarp) { m_monitorOffVarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, "__VmonitorOff", nodep->findBitDType()}; - v3Global.rootp()->dollarUnitPkgAddp()->addStmtp(m_monitorOffVarp); + v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorOffVarp); } const auto varrefp = new AstVarRef(nodep->fileline(), m_monitorOffVarp, access); varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp()); @@ -153,7 +153,7 @@ private: selfDestruct = true; } else { // V3Coverage assigned us a bucket to increment. - AstCoverInc* const covincp = VN_AS(snodep->coverincp(), CoverInc); + AstCoverInc* const covincp = VN_AS(snodep->coverincsp(), CoverInc); UASSERT_OBJ(covincp, snodep, "Missing AstCoverInc under assertion"); covincp->unlinkFrBackWithNext(); // next() might have AstAssign for trace if (message != "") covincp->declp()->comment(message); @@ -215,7 +215,7 @@ private: iterateAndNextNull(ifp->condp()); // Recurse into the true case. - iterateAndNextNull(ifp->ifsp()); + iterateAndNextNull(ifp->thensp()); // If the last else is not an else if, recurse into that too. if (ifp->elsesp() && !nextifp) { // @@ -345,15 +345,15 @@ private: sentreep->unlinkFrBack(); AstAlways* const alwaysp = new AstAlways(nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, nullptr); - m_modp->addStmtp(alwaysp); + m_modp->addStmtsp(alwaysp); for (uint32_t i = 0; i < ticks; ++i) { AstVar* const outvarp = new AstVar( nodep->fileline(), VVarType::MODULETEMP, "_Vpast_" + cvtToStr(m_modPastNum++) + "_" + cvtToStr(i), inp->dtypep()); - m_modp->addStmtp(outvarp); + m_modp->addStmtsp(outvarp); AstNode* const assp = new AstAssignDly( nodep->fileline(), new AstVarRef(nodep->fileline(), outvarp, VAccess::WRITE), inp); - alwaysp->addStmtp(assp); + alwaysp->addStmtsp(assp); // if (debug() >= 9) assp->dumpTree(cout, "-ass: "); invarp = outvarp; inp = new AstVarRef(nodep->fileline(), invarp, VAccess::READ); @@ -425,7 +425,7 @@ private: stmtsp}; ifp->branchPred(VBranchPred::BP_UNLIKELY); AstNode* const newp = new AstAlwaysPostponed{fl, ifp}; - m_modp->addStmtp(newp); + m_modp->addStmtsp(newp); } else if (nodep->displayType() == VDisplayType::DT_STROBE) { nodep->displayType(VDisplayType::DT_DISPLAY); // Need one-shot @@ -433,7 +433,7 @@ private: const auto varp = new AstVar{fl, VVarType::MODULETEMP, "__Vstrobe" + cvtToStr(m_modStrobeNum++), nodep->findBitDType()}; - m_modp->addStmtp(varp); + m_modp->addStmtsp(varp); // Where $strobe was we do "__Vstrobe = '1;" const auto newsetp = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, new AstConst{fl, AstConst::BitTrue{}}}; @@ -445,7 +445,7 @@ private: AstNode* const newp = new AstAlwaysPostponed{fl, ifp}; stmtsp->addNext(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, new AstConst{fl, AstConst::BitFalse{}}}); - m_modp->addStmtp(newp); + m_modp->addStmtsp(newp); } } void visit(AstMonitorOff* nodep) override { diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index e993bd432..cdbcec1a4 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -84,7 +84,7 @@ private: void visit(AstAlways* nodep) override { iterateAndNextNull(nodep->sensesp()); if (nodep->sensesp()) m_seniAlwaysp = nodep->sensesp()->sensesp(); - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); m_seniAlwaysp = nullptr; } diff --git a/src/V3AstInlines.h b/src/V3AstInlines.h index a54ee12e3..94ab3b96f 100644 --- a/src/V3AstInlines.h +++ b/src/V3AstInlines.h @@ -61,7 +61,6 @@ bool AstNode::sameGateTree(const AstNode* node2p) const { return sameTreeIter(this, node2p, true, true); } -void AstNodeArrayDType::rangep(AstRange* nodep) { setOp2p(nodep); } int AstNodeArrayDType::left() const { return rangep()->leftConst(); } int AstNodeArrayDType::right() const { return rangep()->rightConst(); } int AstNodeArrayDType::hi() const { return rangep()->hiConst(); } @@ -71,13 +70,13 @@ VNumRange AstNodeArrayDType::declRange() const { return VNumRange{left(), right( AstRange::AstRange(FileLine* fl, int left, int right) : ASTGEN_SUPER_Range(fl) { - setOp2p(new AstConst{fl, static_cast(left)}); - setOp3p(new AstConst{fl, static_cast(right)}); + leftp(new AstConst{fl, static_cast(left)}); + rightp(new AstConst{fl, static_cast(right)}); } AstRange::AstRange(FileLine* fl, const VNumRange& range) : ASTGEN_SUPER_Range(fl) { - setOp2p(new AstConst{fl, static_cast(range.left())}); - setOp3p(new AstConst{fl, static_cast(range.right())}); + leftp(new AstConst{fl, static_cast(range.left())}); + rightp(new AstConst{fl, static_cast(range.right())}); } int AstRange::leftConst() const { AstConst* const constp = VN_CAST(leftp(), Const); @@ -97,7 +96,7 @@ AstPin::AstPin(FileLine* fl, int pinNum, AstVarRef* varname, AstNode* exprp) : ASTGEN_SUPER_Pin(fl) , m_pinNum{pinNum} , m_name{varname->name()} { - setNOp1p(exprp); + this->exprp(exprp); } AstPackArrayDType::AstPackArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp, @@ -105,7 +104,7 @@ AstPackArrayDType::AstPackArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType : ASTGEN_SUPER_PackArrayDType(fl) { childDTypep(dtp); // Only for parser refDTypep(nullptr); - setOp2p(rangep); + this->rangep(rangep); dtypep(nullptr); // V3Width will resolve const int width = subDTypep()->width() * rangep->elementsConst(); widthForce(width, width); @@ -113,7 +112,7 @@ AstPackArrayDType::AstPackArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType AstPackArrayDType::AstPackArrayDType(FileLine* fl, AstNodeDType* dtp, AstRange* rangep) : ASTGEN_SUPER_PackArrayDType(fl) { refDTypep(dtp); - setOp2p(rangep); + this->rangep(rangep); dtypep(this); const int width = subDTypep()->width() * rangep->elementsConst(); widthForce(width, width); @@ -133,20 +132,20 @@ bool AstActive::hasCombo() const { return m_sensesp->hasCombo(); } AstElabDisplay::AstElabDisplay(FileLine* fl, VDisplayType dispType, AstNode* exprsp) : ASTGEN_SUPER_ElabDisplay(fl) { - setOp1p(new AstSFormatF{fl, AstSFormatF::NoFormat(), exprsp}); + addFmtp(new AstSFormatF{fl, AstSFormatF::NoFormat(), exprsp}); m_displayType = dispType; } AstCStmt::AstCStmt(FileLine* fl, const string& textStmt) : ASTGEN_SUPER_CStmt(fl) { - addNOp1p(new AstText{fl, textStmt, true}); + addExprsp(new AstText{fl, textStmt, true}); } AstCMath::AstCMath(FileLine* fl, const string& textStmt, int setwidth, bool cleanOut) : ASTGEN_SUPER_CMath(fl) , m_cleanOut{cleanOut} , m_pure{true} { - addNOp1p(new AstText{fl, textStmt, true}); + addExprsp(new AstText{fl, textStmt, true}); if (setwidth) dtypeSetLogicSized(setwidth, VSigning::UNSIGNED); } diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index c1db3ae5b..e0bcf00cf 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -132,11 +132,13 @@ private: }; class AstNodeArrayDType VL_NOT_FINAL : public AstNodeDType { // Array data type, ie "some_dtype var_name [2:0]" - // Children: DTYPE (moved to refDTypep() in V3Width) - // Children: RANGE (array bounds) + // @astgen op1 := childDTypep : Optional[AstNodeDType] // moved to refDTypep() in V3Width + // @astgen op2 := rangep : Optional[AstRange] // array bounds private: AstNodeDType* m_refDTypep = nullptr; // Elements of this type (after widthing) - AstNode* rangenp() const { return op2p(); } // op2 = Array(s) of variable + + AstNode* rangenp() const { return reinterpret_cast(rangep()); } + protected: AstNodeArrayDType(VNType t, FileLine* fl) : AstNodeDType{t, fl} {} @@ -165,14 +167,10 @@ public: && subDTypep()->skipRefp()->similarDType(asamep->subDTypep()->skipRefp())); } AstNodeDType* getChildDTypep() const override { return childDTypep(); } - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } - AstRange* rangep() const { return VN_AS(op2p(), Range); } // op2 = Array(s) of variable - inline void rangep(AstRange* nodep); // METHODS AstBasicDType* basicp() const override { return subDTypep()->basicp(); @@ -193,6 +191,7 @@ public: }; class AstNodeUOrStructDType VL_NOT_FINAL : public AstNodeDType { // A struct or union; common handling + // @astgen op1 := membersp : List[AstMemberDType] private: // TYPES using MemberNameMap = std::map; @@ -233,16 +232,11 @@ public: int widthAlignBytes() const override; // (Slow) recurses - Width in bytes rounding up 1,2,4,8,12,... int widthTotalBytes() const override; - // op1 = members bool similarDType(AstNodeDType* samep) const override { return this == samep; // We don't compare members, require exact equivalence } string name() const override { return m_name; } void name(const string& flag) override { m_name = flag; } - AstMemberDType* membersp() const { - return VN_AS(op1p(), MemberDType); - } // op1 = AstMember list - void addMembersp(AstNode* nodep) { addNOp1p(nodep); } bool packed() const { return m_packed; } // packed() but as don't support unpacked, presently all structs static bool packedUnsup() { return true; } @@ -263,33 +257,31 @@ public: // === AstNode === class AstEnumItem final : public AstNode { + // @astgen op1 := rangep : Optional[AstRange] // Range for name appending + // @astgen op2 := valuep : Optional[AstNode] private: string m_name; public: // Parents: ENUM - AstEnumItem(FileLine* fl, const string& name, AstNode* rangep, AstNode* initp) + AstEnumItem(FileLine* fl, const string& name, AstRange* rangep, AstNode* valuep) : ASTGEN_SUPER_EnumItem(fl) , m_name{name} { - addNOp1p(rangep); - addNOp2p(initp); + this->rangep(rangep); + this->valuep(valuep); } ASTGEN_MEMBERS_EnumItem; string name() const override { return m_name; } bool maybePointedTo() const override { return true; } bool hasDType() const override { return true; } void name(const string& flag) override { m_name = flag; } - AstRange* rangep() const { return VN_AS(op1p(), Range); } // op1 = Range for name appending - void rangep(AstRange* nodep) { addOp1p((AstNode*)nodep); } - AstNode* valuep() const { return op2p(); } // op2 = Value - void valuep(AstNode* nodep) { addOp2p(nodep); } }; // === AstNodeDType === class AstAssocArrayDType final : public AstNodeDType { // Associative array data type, ie "[some_dtype]" - // Children: DTYPE (moved to refDTypep() in V3Width) - // Children: DTYPE (the key, which remains here as a pointer) + // @astgen op1 := childDTypep : Optional[AstNodeDType] // moved to refDTypep() in V3Width + // @astgen op2 := keyChildDTypep : AstNodeDType // the key, which remains here as a pointer private: AstNodeDType* m_refDTypep; // Elements of this type (after widthing) AstNodeDType* m_keyDTypep; // Keys of this type (after widthing) @@ -335,9 +327,6 @@ public: void dumpSmall(std::ostream& str) const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } AstNodeDType* getChild2DTypep() const override { return keyChildDTypep(); } - // op1 = Range of variable - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } @@ -347,9 +336,6 @@ public: // AstNodeDType* keyDTypep() const { return m_keyDTypep ? m_keyDTypep : keyChildDTypep(); } void keyDTypep(AstNodeDType* nodep) { m_keyDTypep = nodep; } - // op1 = Range of variable - AstNodeDType* keyChildDTypep() const { return VN_AS(op2p(), NodeDType); } - void keyChildDTypep(AstNodeDType* nodep) { setOp2p(nodep); } // METHODS AstBasicDType* basicp() const override { return nullptr; } AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } @@ -361,7 +347,7 @@ public: }; class AstBasicDType final : public AstNodeDType { // Builtin atomic/vectored data type - // Children: RANGE (converted to constant in V3Width) + // @astgen op1 := rangep : Optional[AstRange] // Range of variable private: struct Members { VBasicDTypeKwd m_keyword; // (also in VBasicTypeKey) What keyword created basic type @@ -415,8 +401,6 @@ public: BROKEN_RTN(dtypep() != this); return nullptr; } - AstRange* rangep() const { return VN_AS(op1p(), Range); } // op1 = Range of variable - void rangep(AstRange* nodep) { setNOp1p((AstNode*)nodep); } void setSignedState(const VSigning& signst) { // Note NOSIGN does NOT change the state; this is required by the parser if (signst == VSigning::UNSIGNED) { @@ -474,21 +458,18 @@ public: class AstBracketArrayDType final : public AstNodeDType { // Associative/Queue/Normal array data type, ie "[dtype_or_expr]" // only for early parsing then becomes another data type - // Children: DTYPE (moved to refDTypep() in V3Width) - // Children: DTYPE (the key) + // @astgen op1 := childDTypep : Optional[AstNodeDType] // moved to refDTypep() in V3Width + // @astgen op2 := elementsp : AstNode // ??? key dtype ??? public: - AstBracketArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp, AstNode* elementsp) + AstBracketArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType* childDTypep, + AstNode* elementsp) : ASTGEN_SUPER_BracketArrayDType(fl) { - setOp1p(dtp); // Only for parser - setOp2p(elementsp); // Only for parser + this->childDTypep(childDTypep); + this->elementsp(elementsp); } ASTGEN_MEMBERS_BracketArrayDType; bool similarDType(AstNodeDType* samep) const override { V3ERROR_NA_RETURN(false); } - // op1 = Range of variable - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } AstNodeDType* subDTypep() const override { return childDTypep(); } - // op2 = Range of variable - AstNode* elementsp() const { return op2p(); } // METHODS // Will be removed in V3Width, which relies on this // being a child not a dtype pointed node @@ -503,16 +484,16 @@ public: }; class AstClassRefDType final : public AstNodeDType { // Reference to a class - // Children: PINs (for parameter settings) + // @astgen op1 := paramsp: List[AstPin] private: AstClass* m_classp; // data type pointed to, BELOW the AstTypedef AstNodeModule* m_classOrPackagep = nullptr; // Package hierarchy public: - AstClassRefDType(FileLine* fl, AstClass* classp, AstNode* paramsp) + AstClassRefDType(FileLine* fl, AstClass* classp, AstPin* paramsp) : ASTGEN_SUPER_ClassRefDType(fl) , m_classp{classp} { - dtypep(this); - addNOp4p(paramsp); + this->dtypep(this); + this->addParamsp(paramsp); } ASTGEN_MEMBERS_ClassRefDType; // METHODS @@ -541,13 +522,13 @@ public: void classOrPackagep(AstNodeModule* nodep) { m_classOrPackagep = nodep; } AstClass* classp() const { return m_classp; } void classp(AstClass* nodep) { m_classp = nodep; } - AstPin* paramsp() const { return VN_AS(op4p(), Pin); } bool isCompound() const override { return true; } }; class AstConstDType final : public AstNodeDType { // const data type, ie "const some_dtype var_name [2:0]" // ConstDType are removed in V3LinkLValue and become AstVar::isConst. // When more generic types are supported AstConstDType will be propagated further. + // @astgen op1 := childDTypep : Optional[AstNodeDType] private: AstNodeDType* m_refDTypep = nullptr; // Inherit from this base data type public: @@ -575,9 +556,6 @@ public: return skipRefp()->similarDType(samep->skipRefp()); } AstNodeDType* getChildDTypep() const override { return childDTypep(); } - // op1 = Range of variable - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } @@ -598,6 +576,7 @@ class AstDefImplicitDType final : public AstNodeDType { // For parsing enum/struct/unions that are declared with a variable rather than typedef // This allows "var enum {...} a,b" to share the enum definition for both variables // After link, these become typedefs + // @astgen op1 := childDTypep : Optional[AstNodeDType] private: string m_name; void* m_containerp; // In what scope is the name unique, so we can know what are duplicate @@ -624,9 +603,6 @@ public: return type() == samep->type() && same(samep); } AstNodeDType* getChildDTypep() const override { return childDTypep(); } - // op1 = Range of variable - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } AstNodeDType* subDTypep() const override { return dtypep() ? dtypep() : childDTypep(); } void* containerp() const { return m_containerp; } // METHODS @@ -644,7 +620,7 @@ public: }; class AstDynArrayDType final : public AstNodeDType { // Dynamic array data type, ie "[]" - // Children: DTYPE (moved to refDTypep() in V3Width) + // @astgen op1 := childDTypep : Optional[AstNodeDType] // moved to refDTypep() in V3Width private: AstNodeDType* m_refDTypep = nullptr; // Elements of this type (after widthing) public: @@ -681,9 +657,6 @@ public: string prettyDTypeName() const override; void dumpSmall(std::ostream& str) const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } - // op1 = Range of variable - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } @@ -726,19 +699,20 @@ public: }; class AstEnumDType final : public AstNodeDType { // Parents: TYPEDEF/MODULE - // Children: ENUMVALUEs + // @astgen op1 := childDTypep : Optional[AstNodeDType] + // @astgen op2 := itemsp : List[AstEnumItem] private: string m_name; // Name from upper typedef, if any AstNodeDType* m_refDTypep = nullptr; // Elements are of this type after V3Width const int m_uniqueNum = 0; public: - AstEnumDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp, AstNode* itemsp) + AstEnumDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp, AstEnumItem* itemsp) : ASTGEN_SUPER_EnumDType(fl) , m_uniqueNum{uniqueNumInc()} { childDTypep(dtp); // Only for parser refDTypep(nullptr); - addNOp2p(itemsp); + addItemsp(itemsp); dtypep(nullptr); // V3Width will resolve widthFromSub(subDTypep()); } @@ -758,16 +732,12 @@ public: } bool similarDType(AstNodeDType* samep) const override { return this == samep; } AstNodeDType* getChildDTypep() const override { return childDTypep(); } - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } // op1 = Data type - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } - // op1 = Range of variable AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } string name() const override { return m_name; } void name(const string& flag) override { m_name = flag; } - AstEnumItem* itemsp() const { return VN_AS(op2p(), EnumItem); } // op2 = AstEnumItem's // METHODS AstBasicDType* basicp() const override { return subDTypep()->basicp(); } AstNodeDType* skipRefp() const override { return subDTypep()->skipRefp(); } @@ -839,6 +809,7 @@ public: class AstMemberDType final : public AstNodeDType { // A member of a struct/union // PARENT: AstNodeUOrStructDType + // @astgen op1 := childDTypep : Optional[AstNodeDType] private: AstNodeDType* m_refDTypep = nullptr; // Elements of this type (after widthing) string m_name; // Name of variable @@ -873,9 +844,6 @@ public: if (m_refDTypep && m_refDTypep->clonep()) m_refDTypep = m_refDTypep->clonep(); } AstNodeDType* getChildDTypep() const override { return childDTypep(); } - // op1 = Range of variable - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } @@ -908,6 +876,7 @@ public: class AstParamTypeDType final : public AstNodeDType { // Parents: MODULE // A parameter type statement; much like a var or typedef + // @astgen op1 := childDTypep : Optional[AstNodeDType] private: const VVarType m_varType; // Type of variable (for localparam vs. param) string m_name; // Name of variable @@ -922,9 +891,6 @@ public: } ASTGEN_MEMBERS_ParamTypeDType; AstNodeDType* getChildDTypep() const override { return childDTypep(); } - // op1 = Type assigning to - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } AstNodeDType* subDTypep() const override { return dtypep() ? dtypep() : childDTypep(); } AstBasicDType* basicp() const override { return subDTypep()->basicp(); } AstNodeDType* skipRefp() const override { return subDTypep()->skipRefp(); } @@ -976,20 +942,21 @@ public: }; class AstQueueDType final : public AstNodeDType { // Queue array data type, ie "[ $ ]" - // Children: DTYPE (moved to refDTypep() in V3Width) + // @astgen op1 := childDTypep : Optional[AstNodeDType] // moved to refDTypep() in V3Width + // @astgen op2 := boundp : Optional[AstNode] private: AstNodeDType* m_refDTypep = nullptr; // Elements of this type (after widthing) public: AstQueueDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp, AstNode* boundp) : ASTGEN_SUPER_QueueDType(fl) { - setNOp2p(boundp); - childDTypep(dtp); // Only for parser + this->childDTypep(dtp); + this->boundp(boundp); refDTypep(nullptr); dtypep(nullptr); // V3Width will resolve } AstQueueDType(FileLine* fl, AstNodeDType* dtp, AstNode* boundp) : ASTGEN_SUPER_QueueDType(fl) { - setNOp2p(boundp); + this->boundp(boundp); refDTypep(dtp); dtypep(dtp); } @@ -1015,13 +982,8 @@ public: void dumpSmall(std::ostream& str) const override; string prettyDTypeName() const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } - // op1 = Range of variable - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } - AstNode* boundp() const { return op2p(); } // op2 = Bound, nullptr = none - void boundp(AstNode* nodep) { setNOp2p(nodep); } inline int boundConst() const; AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } @@ -1038,6 +1000,9 @@ public: bool isCompound() const override { return true; } }; class AstRefDType final : public AstNodeDType { + // @astgen op1 := typeofp : AstNode + // @astgen op2 := classOrPackageOpp : Optional[AstNode] + // @astgen op3 := paramsp : List[AstPin] private: // Pre-Width must reference the Typeref, not what it points to, as some child // types like AstBracketArrayType will disappear and can't lose the handle @@ -1050,16 +1015,16 @@ public: AstRefDType(FileLine* fl, const string& name) : ASTGEN_SUPER_RefDType(fl) , m_name{name} {} - AstRefDType(FileLine* fl, const string& name, AstNode* classOrPackagep, AstNode* paramsp) + AstRefDType(FileLine* fl, const string& name, AstNode* classOrPackagep, AstPin* paramsp) : ASTGEN_SUPER_RefDType(fl) , m_name{name} { - setNOp3p(classOrPackagep); - addNOp4p(paramsp); + this->classOrPackageOpp(classOrPackagep); + addParamsp(paramsp); } class FlagTypeOfExpr {}; // type(expr) for parser only AstRefDType(FileLine* fl, FlagTypeOfExpr, AstNode* typeofp) : ASTGEN_SUPER_RefDType(fl) { - setOp2p(typeofp); + this->typeofp(typeofp); } ASTGEN_MEMBERS_RefDType; // METHODS @@ -1110,7 +1075,6 @@ public: int widthAlignBytes() const override { return dtypeSkipRefp()->widthAlignBytes(); } int widthTotalBytes() const override { return dtypeSkipRefp()->widthTotalBytes(); } void name(const string& flag) override { m_name = flag; } - // op1 = Range of variable AstNodeDType* dtypeSkipRefp() const { return subDTypep()->skipRefp(); } AstTypedef* typedefp() const { return m_typedefp; } void typedefp(AstTypedef* nodep) { m_typedefp = nodep; } @@ -1120,9 +1084,6 @@ public: void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } AstNodeModule* classOrPackagep() const { return m_classOrPackagep; } void classOrPackagep(AstNodeModule* nodep) { m_classOrPackagep = nodep; } - AstNode* typeofp() const { return op2p(); } - AstNode* classOrPackageOpp() const { return op3p(); } - AstPin* paramsp() const { return VN_AS(op4p(), Pin); } bool isCompound() const override { v3fatalSrc("call isCompound on subdata type, not reference"); return false; @@ -1130,7 +1091,7 @@ public: }; class AstUnsizedArrayDType final : public AstNodeDType { // Unsized/open-range Array data type, ie "some_dtype var_name []" - // Children: DTYPE (moved to refDTypep() in V3Width) + // @astgen op1 := childDTypep : Optional[AstNodeDType] // moved to refDTypep() in V3Width private: AstNodeDType* m_refDTypep; // Elements of this type (after widthing) public: @@ -1153,9 +1114,6 @@ public: bool similarDType(AstNodeDType* samep) const override; void dumpSmall(std::ostream& str) const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } - // op1 = Range of variable - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } @@ -1198,7 +1156,7 @@ public: }; class AstWildcardArrayDType final : public AstNodeDType { // Wildcard index type associative array data type, ie "some_dtype var_name [*]" - // Children: DTYPE (moved to refDTypep() in V3Width) + // @astgen op1 := childDTypep : Optional[AstNodeDType] // moved to refDTypep() in V3Width private: AstNodeDType* m_refDTypep; // Elements of this type (after widthing) public: @@ -1221,9 +1179,6 @@ public: bool similarDType(AstNodeDType* samep) const override; void dumpSmall(std::ostream& str) const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } - // op1 = Range of variable - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } @@ -1241,8 +1196,6 @@ public: // === AstNodeArrayDType === class AstPackArrayDType final : public AstNodeArrayDType { // Packed array data type, ie "some_dtype [2:0] var_name" - // Children: DTYPE (moved to refDTypep() in V3Width) - // Children: RANGE (array bounds) public: inline AstPackArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp, AstRange* rangep); inline AstPackArrayDType(FileLine* fl, AstNodeDType* dtp, AstRange* rangep); @@ -1252,15 +1205,13 @@ public: }; class AstUnpackArrayDType final : public AstNodeArrayDType { // Array data type, ie "some_dtype var_name [2:0]" - // Children: DTYPE (moved to refDTypep() in V3Width) - // Children: RANGE (array bounds) bool m_isCompound = false; // Non-POD subDType, or parent requires compound public: AstUnpackArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp, AstRange* rangep) : ASTGEN_SUPER_UnpackArrayDType(fl) { - childDTypep(dtp); // Only for parser + this->childDTypep(dtp); // Only for parser + this->rangep(rangep); refDTypep(nullptr); - setOp2p((AstNode*)rangep); dtypep(nullptr); // V3Width will resolve // For backward compatibility AstNodeArrayDType and others inherit // width and signing from the subDType/base type @@ -1268,8 +1219,8 @@ public: } AstUnpackArrayDType(FileLine* fl, AstNodeDType* dtp, AstRange* rangep) : ASTGEN_SUPER_UnpackArrayDType(fl) { + this->rangep(rangep); refDTypep(dtp); - setOp2p((AstNode*)rangep); dtypep(this); // For backward compatibility AstNodeArrayDType and others inherit // width and signing from the subDType/base type diff --git a/src/V3AstNodeMath.h b/src/V3AstNodeMath.h index e9defaef8..2d5a6f57f 100644 --- a/src/V3AstNodeMath.h +++ b/src/V3AstNodeMath.h @@ -59,23 +59,20 @@ public: bool isOpaque() const { return VN_IS(this, CvtPackString); } }; class AstNodeBiop VL_NOT_FINAL : public AstNodeMath { - // Binary math + // Binary expression + // @astgen op1 := lhsp : AstNode + // @astgen op2 := rhsp : AstNode protected: - AstNodeBiop(VNType t, FileLine* fl, AstNode* lhs, AstNode* rhs) + AstNodeBiop(VNType t, FileLine* fl, AstNode* lhsp, AstNode* rhsp) : AstNodeMath{t, fl} { - setOp1p(lhs); - setOp2p(rhs); + this->lhsp(lhsp); + this->rhsp(rhsp); } public: ASTGEN_MEMBERS_NodeBiop; // Clone single node, just get same type back. virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) = 0; - // ACCESSORS - AstNode* lhsp() const { return op1p(); } - AstNode* rhsp() const { return op2p(); } - void lhsp(AstNode* nodep) { return setOp1p(nodep); } - void rhsp(AstNode* nodep) { return setOp2p(nodep); } // METHODS // Set out to evaluation of a AstConst'ed virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) = 0; @@ -110,18 +107,14 @@ public: }; class AstNodeSel VL_NOT_FINAL : public AstNodeBiop { // Single bit range extraction, perhaps with non-constant selection or array selection + // @astgen alias op1 := fromp // Expression we are indexing into + // @astgen alias op2 := bitp // The index // TOOD: rename to idxp protected: AstNodeSel(VNType t, FileLine* fl, AstNode* fromp, AstNode* bitp) : AstNodeBiop{t, fl, fromp, bitp} {} public: ASTGEN_MEMBERS_NodeSel; - AstNode* fromp() const { - return op1p(); - } // op1 = Extracting what (nullptr=TBD during parsing) - void fromp(AstNode* nodep) { setOp1p(nodep); } - AstNode* bitp() const { return op2p(); } // op2 = Msb selection expression - void bitp(AstNode* nodep) { setOp2p(nodep); } int bitConst() const; bool hasDType() const override { return true; } }; @@ -152,26 +145,23 @@ public: bool doubleFlavor() const override { return true; } }; class AstNodeQuadop VL_NOT_FINAL : public AstNodeMath { - // Quaternary math + // 4-ary expression + // @astgen op1 := lhsp : AstNode + // @astgen op2 := rhsp : AstNode + // @astgen op3 := thsp : AstNode + // @astgen op4 := fhsp : AstNode protected: - AstNodeQuadop(VNType t, FileLine* fl, AstNode* lhs, AstNode* rhs, AstNode* ths, AstNode* fhs) + AstNodeQuadop(VNType t, FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* thsp, + AstNode* fhsp) : AstNodeMath{t, fl} { - setOp1p(lhs); - setOp2p(rhs); - setOp3p(ths); - setOp4p(fhs); + this->lhsp(lhsp); + this->rhsp(rhsp); + this->thsp(thsp); + this->fhsp(fhsp); } public: ASTGEN_MEMBERS_NodeQuadop; - AstNode* lhsp() const { return op1p(); } - AstNode* rhsp() const { return op2p(); } - AstNode* thsp() const { return op3p(); } - AstNode* fhsp() const { return op4p(); } - void lhsp(AstNode* nodep) { return setOp1p(nodep); } - void rhsp(AstNode* nodep) { return setOp2p(nodep); } - void thsp(AstNode* nodep) { return setOp3p(nodep); } - void fhsp(AstNode* nodep) { return setOp4p(nodep); } // METHODS // Set out to evaluation of a AstConst'ed virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs, @@ -203,23 +193,20 @@ public: void dump(std::ostream& str) const override; }; class AstNodeTriop VL_NOT_FINAL : public AstNodeMath { - // Trinary math + // Ternary expression + // @astgen op1 := lhsp : AstNode + // @astgen op2 := rhsp : AstNode + // @astgen op3 := thsp : AstNode protected: - AstNodeTriop(VNType t, FileLine* fl, AstNode* lhs, AstNode* rhs, AstNode* ths) + AstNodeTriop(VNType t, FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* thsp) : AstNodeMath{t, fl} { - setOp1p(lhs); - setOp2p(rhs); - setOp3p(ths); + this->lhsp(lhsp); + this->rhsp(rhsp); + this->thsp(thsp); } public: ASTGEN_MEMBERS_NodeTriop; - AstNode* lhsp() const { return op1p(); } - AstNode* rhsp() const { return op2p(); } - AstNode* thsp() const { return op3p(); } - void lhsp(AstNode* nodep) { return setOp1p(nodep); } - void rhsp(AstNode* nodep) { return setOp2p(nodep); } - void thsp(AstNode* nodep) { return setOp3p(nodep); } // METHODS void dump(std::ostream& str) const override; // Set out to evaluation of a AstConst'ed @@ -236,13 +223,16 @@ public: bool same(const AstNode*) const override { return true; } }; class AstNodeCond VL_NOT_FINAL : public AstNodeTriop { + // @astgen alias op1 := condp + // @astgen alias op2 := thenp + // @astgen alias op3 := elsep protected: - AstNodeCond(VNType t, FileLine* fl, AstNode* condp, AstNode* expr1p, AstNode* expr2p) - : AstNodeTriop{t, fl, condp, expr1p, expr2p} { - if (expr1p) { - dtypeFrom(expr1p); - } else if (expr2p) { - dtypeFrom(expr2p); + AstNodeCond(VNType t, FileLine* fl, AstNode* condp, AstNode* thenp, AstNode* elsep) + : AstNodeTriop{t, fl, condp, thenp, elsep} { + if (thenp) { + dtypeFrom(thenp); + } else if (elsep) { + dtypeFrom(elsep); } } @@ -250,9 +240,6 @@ public: ASTGEN_MEMBERS_NodeCond; void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs, const V3Number& ths) override; - AstNode* condp() const { return op1p(); } // op1 = Condition - AstNode* expr1p() const { return op2p(); } // op2 = If true... - AstNode* expr2p() const { return op3p(); } // op3 = If false... string emitVerilog() override { return "%k(%l %f? %r %k: %t)"; } string emitC() override { return "VL_COND_%nq%lq%rq%tq(%nw, %P, %li, %ri, %ti)"; } bool cleanOut() const override { return false; } // clean if e1 & e2 clean @@ -263,21 +250,20 @@ public: bool sizeMattersRhs() const override { return false; } bool sizeMattersThs() const override { return false; } int instrCount() const override { return INSTR_COUNT_BRANCH; } - virtual AstNode* cloneType(AstNode* condp, AstNode* expr1p, AstNode* expr2p) = 0; + virtual AstNode* cloneType(AstNode* condp, AstNode* thenp, AstNode* elsep) = 0; }; class AstNodeUniop VL_NOT_FINAL : public AstNodeMath { - // Unary math + // Unary expression + // @astgen op1 := lhsp : AstNode protected: AstNodeUniop(VNType t, FileLine* fl, AstNode* lhsp) : AstNodeMath{t, fl} { dtypeFrom(lhsp); - setOp1p(lhsp); + this->lhsp(lhsp); } public: ASTGEN_MEMBERS_NodeUniop; - AstNode* lhsp() const { return op1p(); } - void lhsp(AstNode* nodep) { return setOp1p(nodep); } // METHODS void dump(std::ostream& str) const override; // Set out to evaluation of a AstConst'ed lhs @@ -383,6 +369,7 @@ public: AstCFunc* funcp() const { return m_funcp; } }; class AstCMath final : public AstNodeMath { + // @astgen op1 := exprsp : List[AstNode] // Expressions to print private: const bool m_cleanOut; bool m_pure; // Pure optimizable @@ -392,7 +379,7 @@ public: : ASTGEN_SUPER_CMath(fl) , m_cleanOut{true} , m_pure{false} { - addOp1p(exprsp); + addExprsp(exprsp); dtypeFrom(exprsp); } inline AstCMath(FileLine* fl, const string& textStmt, int setwidth, bool cleanOut = true); @@ -403,19 +390,16 @@ public: string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } bool same(const AstNode* /*samep*/) const override { return true; } - void addBodysp(AstNode* nodep) { addNOp1p(nodep); } - AstNode* bodysp() const { return op1p(); } // op1 = expressions to print bool pure() const { return m_pure; } void pure(bool flag) { m_pure = flag; } }; class AstConsAssoc final : public AstNodeMath { // Construct an assoc array and return object, '{} - // Parents: math - // Children: expression (elements or other queues) + // @astgen op1 := defaultp : Optional[AstNode] public: AstConsAssoc(FileLine* fl, AstNode* defaultp) : ASTGEN_SUPER_ConsAssoc(fl) { - setNOp1p(defaultp); + this->defaultp(defaultp); } ASTGEN_MEMBERS_ConsAssoc; string emitVerilog() override { return "'{}"; } @@ -423,18 +407,17 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } int instrCount() const override { return widthInstrs(); } - AstNode* defaultp() const { return op1p(); } bool same(const AstNode* /*samep*/) const override { return true; } }; class AstConsDynArray final : public AstNodeMath { // Construct a queue and return object, '{}. '{lhs}, '{lhs. rhs} - // Parents: math - // Children: expression (elements or other queues) + // @astgen op1 := lhsp : Optional[AstNode] + // @astgen op2 := rhsp : Optional[AstNode] public: explicit AstConsDynArray(FileLine* fl, AstNode* lhsp = nullptr, AstNode* rhsp = nullptr) : ASTGEN_SUPER_ConsDynArray(fl) { - setNOp1p(lhsp); - setNOp2p(rhsp); + this->lhsp(lhsp); + this->rhsp(rhsp); } ASTGEN_MEMBERS_ConsDynArray; string emitVerilog() override { return "'{%l, %r}"; } @@ -442,19 +425,17 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } int instrCount() const override { return widthInstrs(); } - AstNode* lhsp() const { return op1p(); } // op1 = expression - AstNode* rhsp() const { return op2p(); } // op2 = expression bool same(const AstNode* /*samep*/) const override { return true; } }; class AstConsQueue final : public AstNodeMath { // Construct a queue and return object, '{}. '{lhs}, '{lhs. rhs} - // Parents: math - // Children: expression (elements or other queues) + // @astgen op1 := lhsp : Optional[AstNode] + // @astgen op2 := rhsp : Optional[AstNode] public: explicit AstConsQueue(FileLine* fl, AstNode* lhsp = nullptr, AstNode* rhsp = nullptr) : ASTGEN_SUPER_ConsQueue(fl) { - setNOp1p(lhsp); - setNOp2p(rhsp); + this->lhsp(lhsp); + this->rhsp(rhsp); } ASTGEN_MEMBERS_ConsQueue; string emitVerilog() override { return "'{%l, %r}"; } @@ -462,18 +443,15 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } int instrCount() const override { return widthInstrs(); } - AstNode* lhsp() const { return op1p(); } // op1 = expression - AstNode* rhsp() const { return op2p(); } // op2 = expression bool same(const AstNode* /*samep*/) const override { return true; } }; class AstConsWildcard final : public AstNodeMath { // Construct a wildcard assoc array and return object, '{} - // Parents: math - // Children: expression (elements or other queues) + // @astgen op1 := defaultp : Optional[AstNode] public: AstConsWildcard(FileLine* fl, AstNode* defaultp) : ASTGEN_SUPER_ConsWildcard(fl) { - setNOp1p(defaultp); + this->defaultp(defaultp); } ASTGEN_MEMBERS_ConsWildcard; string emitVerilog() override { return "'{}"; } @@ -481,7 +459,6 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } int instrCount() const override { return widthInstrs(); } - AstNode* defaultp() const { return op1p(); } bool same(const AstNode* /*samep*/) const override { return true; } }; class AstConst final : public AstNodeMath { @@ -662,18 +639,16 @@ class AstExprStmt final : public AstNodeMath { // Perform a statement, often assignment inside an expression/math node, // the parent gets passed the 'resultp()'. // resultp is evaluated AFTER the statement(s). + // @astgen op1 := stmtsp : List[AstNode] + // @astgen op2 := resultp : AstNode public: AstExprStmt(FileLine* fl, AstNode* stmtsp, AstNode* resultp) : ASTGEN_SUPER_ExprStmt(fl) { - addOp1p(stmtsp); - setOp2p(resultp); // Possibly in future nullptr could mean return rhsp() + addStmtsp(stmtsp); + this->resultp(resultp); dtypeFrom(resultp); } ASTGEN_MEMBERS_ExprStmt; - // ACCESSORS - AstNode* stmtsp() const { return op1p(); } - void addStmtsp(AstNode* nodep) { addOp1p(nodep); } - AstNode* resultp() const { return op2p(); } // METHODS string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -681,11 +656,13 @@ public: bool same(const AstNode*) const override { return true; } }; class AstFError final : public AstNodeMath { + // @astgen op1 := filep : AstNode + // @astgen op2 := strp : AstNode public: AstFError(FileLine* fl, AstNode* filep, AstNode* strp) : ASTGEN_SUPER_FError(fl) { - setOp1p(filep); - setOp2p(strp); + this->filep(filep); + this->strp(strp); } ASTGEN_MEMBERS_FError; string emitVerilog() override { return "%f$ferror(%l, %r)"; } @@ -695,25 +672,20 @@ public: virtual bool sizeMattersLhs() const { return false; } int instrCount() const override { return widthInstrs() * 64; } bool isPure() const override { return false; } // SPECIAL: $display has 'visual' ordering - void filep(AstNode* nodep) { setOp1p(nodep); } - AstNode* filep() const { return op1p(); } - void strp(AstNode* nodep) { setOp2p(nodep); } - AstNode* strp() const { return op2p(); } bool same(const AstNode* /*samep*/) const override { return true; } }; class AstFRead final : public AstNodeMath { - // Parents: expr - // Children: varrefs to load - // Children: file which must be a varref - // Children: low index - // Children: count + // @astgen op1 := memp : AstNode // VarRef for result + // @astgen op2 := filep : AstNode // file (must be a VarRef) + // @astgen op3 := startp : Optional[AstNode] // Offset + // @astgen op4 := countp : Optional[AstNode] // Size public: AstFRead(FileLine* fl, AstNode* memp, AstNode* filep, AstNode* startp, AstNode* countp) : ASTGEN_SUPER_FRead(fl) { - setOp1p(memp); - setOp2p(filep); - setNOp3p(startp); - setNOp4p(countp); + this->memp(memp); + this->filep(filep); + this->startp(startp); + this->countp(countp); } ASTGEN_MEMBERS_FRead; string verilogKwd() const override { return "$fread"; } @@ -725,22 +697,14 @@ public: bool isOutputter() const override { return true; } // SPECIAL: makes output bool cleanOut() const override { return false; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* memp() const { return op1p(); } - void memp(AstNode* nodep) { setOp1p(nodep); } - AstNode* filep() const { return op2p(); } - void filep(AstNode* nodep) { setOp2p(nodep); } - AstNode* startp() const { return op3p(); } - void startp(AstNode* nodep) { setNOp3p(nodep); } - AstNode* countp() const { return op4p(); } - void countp(AstNode* nodep) { setNOp4p(nodep); } }; class AstFRewind final : public AstNodeMath { // Parents: stmtlist - // Children: file which must be a varref + // @astgen op1 := filep : Optional[AstNode] public: AstFRewind(FileLine* fl, AstNode* filep) : ASTGEN_SUPER_FRewind(fl) { - setNOp2p(filep); + this->filep(filep); } ASTGEN_MEMBERS_FRewind; string verilogKwd() const override { return "$frewind"; } @@ -753,13 +717,10 @@ public: bool isUnlikely() const override { return true; } bool cleanOut() const override { return false; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* filep() const { return op2p(); } - void filep(AstNodeVarRef* nodep) { setNOp2p((AstNode*)nodep); } }; class AstFScanF final : public AstNodeMath { - // Parents: expr - // Children: file which must be a varref - // Children: varrefs to load + // @astgen op1 := exprsp : List[AstNode] // VarRefs for results + // @astgen op2 := filep : Optional[AstNode] // file (must be a VarRef) private: string m_text; @@ -767,8 +728,8 @@ public: AstFScanF(FileLine* fl, const string& text, AstNode* filep, AstNode* exprsp) : ASTGEN_SUPER_FScanF(fl) , m_text{text} { - addNOp1p(exprsp); - setNOp2p(filep); + addExprsp(exprsp); + this->filep(filep); } ASTGEN_MEMBERS_FScanF; string name() const override { return m_text; } @@ -783,24 +744,19 @@ public: bool same(const AstNode* samep) const override { return text() == static_cast(samep)->text(); } - AstNode* exprsp() const { return op1p(); } // op1 = Expressions to output - void exprsp(AstNode* nodep) { addOp1p(nodep); } // op1 = Expressions to output string text() const { return m_text; } // * = Text to display void text(const string& text) { m_text = text; } - AstNode* filep() const { return op2p(); } - void filep(AstNodeVarRef* nodep) { setNOp2p((AstNode*)nodep); } }; class AstFSeek final : public AstNodeMath { - // Parents: expr - // Children: file which must be a varref - // Children: offset - // Children: operation + // @astgen op1 := filep : AstNode // file (must be a VarRef) + // @astgen op2 := offset : Optional[AstNode] + // @astgen op3 := operation : Optional[AstNode] public: AstFSeek(FileLine* fl, AstNode* filep, AstNode* offset, AstNode* operation) : ASTGEN_SUPER_FSeek(fl) { - setOp2p(filep); - setNOp3p(offset); - setNOp4p(operation); + this->filep(filep); + this->offset(offset); + this->operation(operation); } ASTGEN_MEMBERS_FSeek; string verilogKwd() const override { return "$fseek"; } @@ -812,20 +768,14 @@ public: bool isOutputter() const override { return true; } // SPECIAL: makes output bool cleanOut() const override { return false; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* filep() const { return op2p(); } - void filep(AstNode* nodep) { setOp2p(nodep); } - AstNode* offset() const { return op3p(); } - void offset(AstNode* nodep) { setNOp3p(nodep); } - AstNode* operation() const { return op4p(); } - void operation(AstNode* nodep) { setNOp4p(nodep); } }; class AstFTell final : public AstNodeMath { // Parents: stmtlist - // Children: file which must be a varref + // @astgen op1 := filep : AstNode // file (must be a VarRef) public: AstFTell(FileLine* fl, AstNode* filep) : ASTGEN_SUPER_FTell(fl) { - setNOp2p(filep); + this->filep(filep); } ASTGEN_MEMBERS_FTell; string verilogKwd() const override { return "$ftell"; } @@ -838,17 +788,15 @@ public: bool isUnlikely() const override { return true; } bool cleanOut() const override { return false; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* filep() const { return op2p(); } - void filep(AstNodeVarRef* nodep) { setNOp2p((AstNode*)nodep); } }; class AstFell final : public AstNodeMath { // Verilog $fell - // Parents: math - // Children: expression + // @astgen op1 := exprp : AstNode + // @astgen op2 := sentreep : Optional[AstSenTree] public: AstFell(FileLine* fl, AstNode* exprp) : ASTGEN_SUPER_Fell(fl) { - addOp1p(exprp); + this->exprp(exprp); } ASTGEN_MEMBERS_Fell; string emitVerilog() override { return "$fell(%l)"; } @@ -856,35 +804,33 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { V3ERROR_NA_RETURN(""); } int instrCount() const override { return widthInstrs(); } - AstNode* exprp() const { return op1p(); } // op1 = expression - AstSenTree* sentreep() const { return VN_AS(op2p(), SenTree); } // op2 = clock domain - void sentreep(AstSenTree* sentreep) { addOp2p((AstNode*)sentreep); } // op2 = clock domain bool same(const AstNode* /*samep*/) const override { return true; } }; class AstGatePin final : public AstNodeMath { // Possibly expand a gate primitive input pin value to match the range of the gate primitive + // @astgen op1 := exprp : AstNode // Pin expression + // @astgen op2 := rangep : AstRange // Range of pin public: - AstGatePin(FileLine* fl, AstNode* lhsp, AstRange* rangep) + AstGatePin(FileLine* fl, AstNode* exprp, AstRange* rangep) : ASTGEN_SUPER_GatePin(fl) { - setOp1p(lhsp); - setOp2p((AstNode*)rangep); + this->exprp(exprp); + this->rangep(rangep); } ASTGEN_MEMBERS_GatePin; string emitVerilog() override { return "%l"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } - AstNode* exprp() const { return op1p(); } // op1 = Pin expression - AstRange* rangep() const { return VN_AS(op2p(), Range); } // op2 = Range of pin }; class AstImplication final : public AstNodeMath { // Verilog |-> |=> - // Parents: math - // Children: expression + // @astgen op1 := lhsp : AstNode + // @astgen op2 := rhsp : AstNode + // @astgen op3 := sentreep : AstSenTree public: - AstImplication(FileLine* fl, AstNode* lhs, AstNode* rhs) + AstImplication(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_Implication(fl) { - setOp1p(lhs); - setOp2p(rhs); + this->lhsp(lhsp); + this->rhsp(rhsp); } ASTGEN_MEMBERS_Implication; string emitVerilog() override { V3ERROR_NA_RETURN(""); } @@ -892,40 +838,33 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { V3ERROR_NA_RETURN(""); } int instrCount() const override { return widthInstrs(); } - AstNode* lhsp() const { return op1p(); } - AstNode* rhsp() const { return op2p(); } - void lhsp(AstNode* nodep) { return setOp1p(nodep); } - void rhsp(AstNode* nodep) { return setOp2p(nodep); } - AstSenTree* sentreep() const { return VN_AS(op4p(), SenTree); } // op4 = clock domain - void sentreep(AstSenTree* sentreep) { addOp4p((AstNode*)sentreep); } // op4 = clock domain bool same(const AstNode* /*samep*/) const override { return true; } }; class AstInside final : public AstNodeMath { + // @astgen op1 := exprp : AstNode + // @astgen op2 := itemsp : List[AstNode] public: AstInside(FileLine* fl, AstNode* exprp, AstNode* itemsp) : ASTGEN_SUPER_Inside(fl) { - addOp1p(exprp); - addOp2p(itemsp); + this->exprp(exprp); + this->addItemsp(itemsp); dtypeSetBit(); } ASTGEN_MEMBERS_Inside; - AstNode* exprp() const { return op1p(); } // op1 = LHS expression to compare with - // op2 = RHS, possibly a list of expr or AstInsideRange - AstNode* itemsp() const { return op2p(); } string emitVerilog() override { return "%l inside { %r }"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return false; } // NA }; class AstInsideRange final : public AstNodeMath { + // @astgen op1 := lhsp : AstNode + // @astgen op2 := rhsp : AstNode public: AstInsideRange(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_InsideRange(fl) { - addOp1p(lhsp); - addOp2p(rhsp); + this->lhsp(lhsp); + this->rhsp(rhsp); } ASTGEN_MEMBERS_InsideRange; - AstNode* lhsp() const { return op1p(); } // op1 = LHS - AstNode* rhsp() const { return op2p(); } // op2 = RHS string emitVerilog() override { return "[%l:%r]"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return false; } // NA @@ -958,7 +897,7 @@ public: }; class AstMemberSel final : public AstNodeMath { // Parents: math|stmt - // Children: varref|arraysel, math + // @astgen op1 := fromp : AstNode private: // Don't need the class we are extracting from, as the "fromp()"'s datatype can get us to it string m_name; @@ -967,13 +906,13 @@ public: AstMemberSel(FileLine* fl, AstNode* fromp, VFlagChildDType, const string& name) : ASTGEN_SUPER_MemberSel(fl) , m_name{name} { - setOp1p(fromp); + this->fromp(fromp); dtypep(nullptr); // V3Width will resolve } AstMemberSel(FileLine* fl, AstNode* fromp, AstNodeDType* dtp) : ASTGEN_SUPER_MemberSel(fl) , m_name{dtp->name()} { - setOp1p(fromp); + this->fromp(fromp); dtypep(dtp); } ASTGEN_MEMBERS_MemberSel; @@ -986,22 +925,18 @@ public: bool cleanOut() const override { return false; } bool same(const AstNode* samep) const override { return true; } // dtype comparison does it int instrCount() const override { return widthInstrs(); } - AstNode* fromp() const { - return op1p(); - } // op1 = Extracting what (nullptr=TBD during parsing) - void fromp(AstNode* nodep) { setOp1p(nodep); } AstVar* varp() const { return m_varp; } void varp(AstVar* nodep) { m_varp = nodep; } }; class AstNewCopy final : public AstNodeMath { // New as shallow copy // Parents: math|stmt - // Children: varref|arraysel, math + // @astgen op1 := rhsp : AstNode public: AstNewCopy(FileLine* fl, AstNode* rhsp) : ASTGEN_SUPER_NewCopy(fl) { dtypeFrom(rhsp); // otherwise V3Width will resolve - setNOp1p(rhsp); + this->rhsp(rhsp); } ASTGEN_MEMBERS_NewCopy; string emitVerilog() override { return "new"; } @@ -1009,18 +944,18 @@ public: bool cleanOut() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } int instrCount() const override { return widthInstrs(); } - AstNode* rhsp() const { return op1p(); } }; class AstNewDynamic final : public AstNodeMath { // New for dynamic array // Parents: math|stmt - // Children: varref|arraysel, math + // @astgen op1 := sizep : AstNode + // @astgen op2 := rhsp : Optional[AstNode] public: AstNewDynamic(FileLine* fl, AstNode* sizep, AstNode* rhsp) : ASTGEN_SUPER_NewDynamic(fl) { dtypeFrom(rhsp); // otherwise V3Width will resolve - setNOp1p(sizep); - setNOp2p(rhsp); + this->sizep(sizep); + this->rhsp(rhsp); } ASTGEN_MEMBERS_NewDynamic; string emitVerilog() override { return "new"; } @@ -1028,18 +963,17 @@ public: bool cleanOut() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } int instrCount() const override { return widthInstrs(); } - AstNode* sizep() const { return op1p(); } - AstNode* rhsp() const { return op2p(); } }; class AstPast final : public AstNodeMath { // Verilog $past - // Parents: math - // Children: expression + // @astgen op1 := exprp : AstNode + // @astgen op2 := ticksp : Optional[AstNode] + // @astgen op3 := sentreep : Optional[AstSenTree] public: AstPast(FileLine* fl, AstNode* exprp, AstNode* ticksp) : ASTGEN_SUPER_Past(fl) { - addOp1p(exprp); - addNOp2p(ticksp); + this->exprp(exprp); + this->ticksp(ticksp); } ASTGEN_MEMBERS_Past; string emitVerilog() override { V3ERROR_NA_RETURN(""); } @@ -1047,23 +981,25 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { V3ERROR_NA_RETURN(""); } int instrCount() const override { return widthInstrs(); } - AstNode* exprp() const { return op1p(); } // op1 = expression - AstNode* ticksp() const { return op2p(); } // op2 = ticks or nullptr means 1 - AstSenTree* sentreep() const { return VN_AS(op4p(), SenTree); } // op4 = clock domain - void sentreep(AstSenTree* sentreep) { addOp4p((AstNode*)sentreep); } // op4 = clock domain bool same(const AstNode* /*samep*/) const override { return true; } }; class AstPatMember final : public AstNodeMath { // Verilog '{a} or '{a{b}} // Parents: AstPattern // Children: expression, AstPattern, replication count + // Expression to assign or another AstPattern (list if replicated) + // @astgen op1 := lhssp : List[AstNode] + // @astgen op2 := keyp : Optional[AstNode] + // @astgen op3 := repp : Optional[AstNode] // replication count, or nullptr for count 1 private: bool m_default = false; public: - AstPatMember(FileLine* fl, AstNode* lhsp, AstNode* keyp, AstNode* repp) + AstPatMember(FileLine* fl, AstNode* lhssp, AstNode* keyp, AstNode* repp) : ASTGEN_SUPER_PatMember(fl) { - addOp1p(lhsp), setNOp2p(keyp), setNOp3p(repp); + this->addLhssp(lhssp); + this->keyp(keyp); + this->repp(repp); } ASTGEN_MEMBERS_PatMember; string emitVerilog() override { return lhssp() ? "%f{%r{%k%l}}" : "%l"; } @@ -1072,10 +1008,6 @@ public: bool cleanOut() const override { V3ERROR_NA_RETURN(""); } int instrCount() const override { return widthInstrs() * 2; } void dump(std::ostream& str = std::cout) const override; - // op1 = expression to assign or another AstPattern (list if replicated) - AstNode* lhssp() const { return op1p(); } - AstNode* keyp() const { return op2p(); } // op2 = assignment key (Const, id Text) - AstNode* repp() const { return op3p(); } // op3 = replication count, or nullptr for count 1 bool isDefault() const { return m_default; } void isDefault(bool flag) { m_default = flag; } }; @@ -1083,10 +1015,12 @@ class AstPattern final : public AstNodeMath { // Verilog '{a,b,c,d...} // Parents: AstNodeAssign, AstPattern, ... // Children: expression, AstPattern, AstPatReplicate + // @astgen op1 := childDTypep : Optional[AstNodeDType] + // @astgen op2 := itemsp : List[AstNode] // AstPatReplicate, AstPatMember, etc public: AstPattern(FileLine* fl, AstNode* itemsp) : ASTGEN_SUPER_Pattern(fl) { - addNOp2p(itemsp); + addItemsp(itemsp); } ASTGEN_MEMBERS_Pattern; string emitVerilog() override { V3ERROR_NA_RETURN(""); } @@ -1096,15 +1030,11 @@ public: int instrCount() const override { return widthInstrs(); } AstNodeDType* getChildDTypep() const override { return childDTypep(); } virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); } - // op1 = Type assigning to - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } - AstNode* itemsp() const { return op2p(); } // op2 = AstPatReplicate, AstPatMember, etc - void addItemsp(AstNode* nodep) { addOp2p(nodep); } }; class AstRand final : public AstNodeMath { // $random/$random(seed) or $urandom/$urandom(seed) // Return a random number, based upon width() + // @astgen op1 := seedp : Optional[AstNode] private: const bool m_urandom = false; // $urandom vs $random const bool m_reset = false; // Random reset, versus always random @@ -1118,7 +1048,7 @@ public: AstRand(FileLine* fl, AstNode* seedp, bool urandom) : ASTGEN_SUPER_Rand(fl) , m_urandom{urandom} { - setNOp1p(seedp); + this->seedp(seedp); } ASTGEN_MEMBERS_Rand; string emitVerilog() override { @@ -1141,18 +1071,17 @@ public: return !seedp() && !samep->seedp() && reset() == samep->reset() && urandom() == samep->urandom(); } - AstNode* seedp() const { return op1p(); } bool reset() const { return m_reset; } bool urandom() const { return m_urandom; } }; class AstRose final : public AstNodeMath { // Verilog $rose - // Parents: math - // Children: expression + // @astgen op1 := exprp : AstNode + // @astgen op2 := sentreep : Optional[AstSenTree] public: AstRose(FileLine* fl, AstNode* exprp) : ASTGEN_SUPER_Rose(fl) { - addOp1p(exprp); + this->exprp(exprp); } ASTGEN_MEMBERS_Rose; string emitVerilog() override { return "$rose(%l)"; } @@ -1160,15 +1089,11 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { V3ERROR_NA_RETURN(""); } int instrCount() const override { return widthInstrs(); } - AstNode* exprp() const { return op1p(); } // op1 = expression - AstSenTree* sentreep() const { return VN_AS(op2p(), SenTree); } // op2 = clock domain - void sentreep(AstSenTree* sentreep) { addOp2p((AstNode*)sentreep); } // op2 = clock domain bool same(const AstNode* /*samep*/) const override { return true; } }; class AstSScanF final : public AstNodeMath { - // Parents: expr - // Children: file which must be a varref - // Children: varrefs to load + // @astgen op1 := exprsp : List[AstNode] // VarRefs for results + // @astgen op2 := fromp : AstNode private: string m_text; @@ -1176,8 +1101,8 @@ public: AstSScanF(FileLine* fl, const string& text, AstNode* fromp, AstNode* exprsp) : ASTGEN_SUPER_SScanF(fl) , m_text{text} { - addNOp1p(exprsp); - setOp2p(fromp); + this->addExprsp(exprsp); + this->fromp(fromp); } ASTGEN_MEMBERS_SScanF; string name() const override { return m_text; } @@ -1192,21 +1117,16 @@ public: bool same(const AstNode* samep) const override { return text() == static_cast(samep)->text(); } - AstNode* exprsp() const { return op1p(); } // op1 = Expressions to output - void exprsp(AstNode* nodep) { addOp1p(nodep); } // op1 = Expressions to output string text() const { return m_text; } // * = Text to display void text(const string& text) { m_text = text; } - AstNode* fromp() const { return op2p(); } - void fromp(AstNode* nodep) { setOp2p(nodep); } }; class AstSampled final : public AstNodeMath { // Verilog $sampled - // Parents: math - // Children: expression + // @astgen op1 := exprp : AstNode public: AstSampled(FileLine* fl, AstNode* exprp) : ASTGEN_SUPER_Sampled(fl) { - addOp1p(exprp); + this->exprp(exprp); } ASTGEN_MEMBERS_Sampled; string emitVerilog() override { return "$sampled(%l)"; } @@ -1214,13 +1134,13 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { V3ERROR_NA_RETURN(""); } int instrCount() const override { return 0; } - AstNode* exprp() const { return op1p(); } // op1 = expression bool same(const AstNode* /*samep*/) const override { return true; } }; class AstScopeName final : public AstNodeMath { // For display %m and DPI context imports // Parents: DISPLAY - // Children: TEXT + // @astgen op1 := scopeAttrp : List[AstText] + // @astgen op2 := scopeEntrp : List[AstText] private: bool m_dpiExport = false; // Is for dpiExport const bool m_forFormat = false; // Is for a format %m @@ -1243,10 +1163,6 @@ public: string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } void dump(std::ostream& str = std::cout) const override; - AstText* scopeAttrp() const { return VN_AS(op1p(), Text); } - void scopeAttrp(AstNode* nodep) { addOp1p(nodep); } - AstText* scopeEntrp() const { return VN_AS(op2p(), Text); } - void scopeEntrp(AstNode* nodep) { addOp2p(nodep); } string scopeSymName() const { // Name for __Vscope variable including children return scopeNameFormatter(scopeAttrp()); } @@ -1266,13 +1182,15 @@ public: class AstSetAssoc final : public AstNodeMath { // Set an assoc array element and return object, '{} // Parents: math - // Children: expression (elements or other queues) + // @astgen op1 := lhsp : AstNode + // @astgen op2 := keyp : Optional[AstNode] + // @astgen op3 := valuep : AstNode public: AstSetAssoc(FileLine* fl, AstNode* lhsp, AstNode* keyp, AstNode* valuep) : ASTGEN_SUPER_SetAssoc(fl) { - setOp1p(lhsp); - setNOp2p(keyp); - setOp3p(valuep); + this->lhsp(lhsp); + this->keyp(keyp); + this->valuep(valuep); } ASTGEN_MEMBERS_SetAssoc; string emitVerilog() override { return "'{}"; } @@ -1280,21 +1198,20 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } int instrCount() const override { return widthInstrs(); } - AstNode* lhsp() const { return op1p(); } - AstNode* keyp() const { return op2p(); } - AstNode* valuep() const { return op3p(); } bool same(const AstNode* /*samep*/) const override { return true; } }; class AstSetWildcard final : public AstNodeMath { // Set a wildcard assoc array element and return object, '{} // Parents: math - // Children: expression (elements or other queues) + // @astgen op1 := lhsp : AstNode + // @astgen op2 := keyp : Optional[AstNode] + // @astgen op3 := valuep : AstNode public: AstSetWildcard(FileLine* fl, AstNode* lhsp, AstNode* keyp, AstNode* valuep) : ASTGEN_SUPER_SetWildcard(fl) { - setOp1p(lhsp); - setNOp2p(keyp); - setOp3p(valuep); + this->lhsp(lhsp); + this->keyp(keyp); + this->valuep(valuep); } ASTGEN_MEMBERS_SetWildcard; string emitVerilog() override { return "'{}"; } @@ -1302,19 +1219,16 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } int instrCount() const override { return widthInstrs(); } - AstNode* lhsp() const { return op1p(); } - AstNode* keyp() const { return op2p(); } - AstNode* valuep() const { return op3p(); } bool same(const AstNode* /*samep*/) const override { return true; } }; class AstStable final : public AstNodeMath { // Verilog $stable - // Parents: math - // Children: expression + // @astgen op1 := exprp : AstNode + // @astgen op2 := sentreep : Optional[AstSenTree] public: AstStable(FileLine* fl, AstNode* exprp) : ASTGEN_SUPER_Stable(fl) { - addOp1p(exprp); + this->exprp(exprp); } ASTGEN_MEMBERS_Stable; string emitVerilog() override { return "$stable(%l)"; } @@ -1322,17 +1236,15 @@ public: string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { V3ERROR_NA_RETURN(""); } int instrCount() const override { return widthInstrs(); } - AstNode* exprp() const { return op1p(); } // op1 = expression - AstSenTree* sentreep() const { return VN_AS(op2p(), SenTree); } // op2 = clock domain - void sentreep(AstSenTree* sentreep) { addOp2p((AstNode*)sentreep); } // op2 = clock domain bool same(const AstNode* /*samep*/) const override { return true; } }; class AstSystemF final : public AstNodeMath { // $system used as function + // @astgen op1 := lhsp : AstNode public: AstSystemF(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_SystemF(fl) { - setOp1p(lhsp); + this->lhsp(lhsp); } ASTGEN_MEMBERS_SystemF; string verilogKwd() const override { return "$system"; } @@ -1345,15 +1257,14 @@ public: bool isUnlikely() const override { return true; } bool cleanOut() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* lhsp() const { return op1p(); } }; class AstTestPlusArgs final : public AstNodeMath { - // Parents: expr - // Child: variable to set. If nullptr then this is a $test$plusargs instead of $value$plusargs + // Search expression. If nullptr then this is a $test$plusargs instead of $value$plusargs. + // @astgen op1 := searchp : Optional[AstNode] public: AstTestPlusArgs(FileLine* fl, AstNode* searchp) : ASTGEN_SUPER_TestPlusArgs(fl) { - setOp1p(searchp); + this->searchp(searchp); } ASTGEN_MEMBERS_TestPlusArgs; string verilogKwd() const override { return "$test$plusargs"; } @@ -1363,22 +1274,20 @@ public: bool isPredictOptimizable() const override { return false; } bool cleanOut() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* searchp() const { return op1p(); } // op1 = Search expression - void searchp(AstNode* nodep) { setOp1p(nodep); } }; class AstUCFunc final : public AstNodeMath { // User's $c function // Perhaps this should be an AstNodeListop; but there's only one list math right now + // @astgen op1 := exprsp : List[AstNode] // Expressions to print public: AstUCFunc(FileLine* fl, AstNode* exprsp) : ASTGEN_SUPER_UCFunc(fl) { - addNOp1p(exprsp); + this->addExprsp(exprsp); } ASTGEN_MEMBERS_UCFunc; bool cleanOut() const override { return false; } string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } - AstNode* bodysp() const { return op1p(); } // op1 = expressions to print bool isPure() const override { return false; } // SPECIAL: User may order w/other sigs bool isOutputter() const override { return true; } bool isGateOptimizable() const override { return false; } @@ -1401,13 +1310,14 @@ public: bool cleanOut() const override { return true; } }; class AstValuePlusArgs final : public AstNodeMath { - // Parents: expr - // Child: variable to set. If nullptr then this is a $test$plusargs instead of $value$plusargs + // Search expression. If nullptr then this is a $test$plusargs instead of $value$plusargs. + // @astgen op1 := searchp : Optional[AstNode] + // @astgen op2 := outp : AstNode // VarRef for result public: AstValuePlusArgs(FileLine* fl, AstNode* searchp, AstNode* outp) : ASTGEN_SUPER_ValuePlusArgs(fl) { - setOp1p(searchp); - setOp2p(outp); + this->searchp(searchp); + this->outp(outp); } ASTGEN_MEMBERS_ValuePlusArgs; string verilogKwd() const override { return "$value$plusargs"; } @@ -1418,10 +1328,6 @@ public: bool isPure() const override { return !outp(); } bool cleanOut() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* searchp() const { return op1p(); } // op1 = Search expression - void searchp(AstNode* nodep) { setOp1p(nodep); } - AstNode* outp() const { return op2p(); } // op2 = Expressions to output - void outp(AstNode* nodep) { setOp2p(nodep); } }; // === AstNodeBiop === @@ -2396,6 +2302,8 @@ public: class AstReplicate final : public AstNodeBiop { // Also used as a "Uniop" flavor of Concat, e.g. "{a}" // Verilog {rhs{lhs}} - Note rhsp() is the replicate value, not the lhsp() + // @astgen alias op1 := srcp + // @astgen alias op2 := countp public: AstReplicate(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_Replicate(fl, lhsp, rhsp) { @@ -3407,8 +3315,9 @@ class AstSel final : public AstNodeTriop { // Multiple bit range extraction // Parents: math|stmt // Children: varref|arraysel, math, constant math - // Tempting to have an access() style method here as LHS selects are quite - // different, but that doesn't play well with V3Inst and bidirects which don't know direction + // @astgen alias op1 := fromp + // @astgen alias op2 := lsbp + // @astgen alias op3 := widthp private: VNumRange m_declRange; // Range of the 'from' array if isRanged() is set, else invalid int m_declElWidth; // If a packed array, the number of bits per element @@ -3446,12 +3355,6 @@ public: bool sizeMattersThs() const override { return false; } bool same(const AstNode*) const override { return true; } int instrCount() const override { return widthInstrs() * (VN_CAST(lsbp(), Const) ? 3 : 10); } - AstNode* fromp() const { - return op1p(); - } // op1 = Extracting what (nullptr=TBD during parsing) - AstNode* lsbp() const { return op2p(); } // op2 = Msb selection expression - void lsbp(AstNode* const lsbp) { setOp2p(lsbp); } - AstNode* widthp() const { return op3p(); } // op3 = Width int widthConst() const { return VN_AS(widthp(), Const)->toSInt(); } int lsbConst() const { return VN_AS(lsbp(), Const)->toSInt(); } int msbConst() const { return lsbConst() + widthConst() - 1; } @@ -3464,7 +3367,7 @@ public: class AstSliceSel final : public AstNodeTriop { // Multiple array element extraction // Parents: math|stmt - // Children: varref|arraysel, math, constant math + // @astgen alias op1 := fromp private: VNumRange m_declRange; // Range of the 'from' array if isRanged() is set, else invalid public: @@ -3489,9 +3392,6 @@ public: bool sizeMattersThs() const override { return false; } bool same(const AstNode*) const override { return true; } int instrCount() const override { return 10; } // Removed before matters - AstNode* fromp() const { - return op1p(); - } // op1 = Extracting what (nullptr=TBD during parsing) // For widthConst()/loConst etc, see declRange().elements() and other VNumRange methods VNumRange& declRange() { return m_declRange; } const VNumRange& declRange() const { return m_declRange; } @@ -3528,11 +3428,11 @@ class AstCond final : public AstNodeCond { // Parents: MATH // Children: MATH public: - AstCond(FileLine* fl, AstNode* condp, AstNode* expr1p, AstNode* expr2p) - : ASTGEN_SUPER_Cond(fl, condp, expr1p, expr2p) {} + AstCond(FileLine* fl, AstNode* condp, AstNode* thenp, AstNode* elsep) + : ASTGEN_SUPER_Cond(fl, condp, thenp, elsep) {} ASTGEN_MEMBERS_Cond; - AstNode* cloneType(AstNode* condp, AstNode* expr1p, AstNode* expr2p) override { - return new AstCond(this->fileline(), condp, expr1p, expr2p); + AstNode* cloneType(AstNode* condp, AstNode* thenp, AstNode* elsep) override { + return new AstCond(this->fileline(), condp, thenp, elsep); } }; class AstCondBound final : public AstNodeCond { @@ -3540,11 +3440,11 @@ class AstCondBound final : public AstNodeCond { // Parents: MATH // Children: MATH public: - AstCondBound(FileLine* fl, AstNode* condp, AstNode* expr1p, AstNode* expr2p) - : ASTGEN_SUPER_CondBound(fl, condp, expr1p, expr2p) {} + AstCondBound(FileLine* fl, AstNode* condp, AstNode* thenp, AstNode* elsep) + : ASTGEN_SUPER_CondBound(fl, condp, thenp, elsep) {} ASTGEN_MEMBERS_CondBound; - AstNode* cloneType(AstNode* condp, AstNode* expr1p, AstNode* expr2p) override { - return new AstCondBound(this->fileline(), condp, expr1p, expr2p); + AstNode* cloneType(AstNode* condp, AstNode* thenp, AstNode* elsep) override { + return new AstCondBound(this->fileline(), condp, thenp, elsep); } }; diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index dfd4290f8..d628c384a 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -32,8 +32,8 @@ class AstNodeBlock VL_NOT_FINAL : public AstNode { // A Begin/fork block + // @astgen op1 := stmtsp : List[AstNode] // Parents: statement - // Children: statements private: string m_name; // Name of block bool m_unnamed; // Originally unnamed (name change does not affect this) @@ -41,7 +41,7 @@ protected: AstNodeBlock(VNType t, FileLine* fl, const string& name, AstNode* stmtsp) : AstNode{t, fl} , m_name{name} { - addNOp1p(stmtsp); + addStmtsp(stmtsp); m_unnamed = (name == ""); } @@ -50,13 +50,18 @@ public: void dump(std::ostream& str) const override; string name() const override { return m_name; } // * = Block name void name(const string& name) override { m_name = name; } - // op1 = Statements - AstNode* stmtsp() const { return op1p(); } // op1 = List of statements - void addStmtsp(AstNode* nodep) { addNOp1p(nodep); } bool unnamed() const { return m_unnamed; } bool isFirstInMyListOfStatements(AstNode* nodep) const override { return nodep == stmtsp(); } }; class AstNodeFTask VL_NOT_FINAL : public AstNode { + // Output variable in functions, nullptr in tasks + // @astgen op1 := fvarp : Optional[AstNode] + // Class/package scope + // @astgen op2 := classOrPackagep : Optional[AstNode] + // Statements/Ports/Vars + // @astgen op3 := stmtsp : List[AstNode] + // Scope name + // @astgen op4 := scopeNamep : Optional[AstScopeName] private: string m_name; // Name of task string m_cname; // Name of task if DPI import @@ -106,7 +111,7 @@ protected: , m_recursive{false} , m_underGenerate{false} , m_virtual{false} { - addNOp3p(stmtsp); + addStmtsp(stmtsp); cname(name); // Might be overridden by dpi import/export } @@ -120,23 +125,11 @@ public: void name(const string& name) override { m_name = name; } string cname() const { return m_cname; } void cname(const string& cname) { m_cname = cname; } - // op1 = Output variable (functions only, nullptr for tasks) - AstNode* fvarp() const { return op1p(); } - void addFvarp(AstNode* nodep) { addNOp1p(nodep); } bool isFunction() const { return fvarp() != nullptr; } - // op2 = Class/package scope - AstNode* classOrPackagep() const { return op2p(); } - void classOrPackagep(AstNode* nodep) { setNOp2p(nodep); } - // op3 = Statements/Ports/Vars - AstNode* stmtsp() const { return op3p(); } // op3 = List of statements - void addStmtsp(AstNode* nodep) { addNOp3p(nodep); } - // op4 = scope name - AstScopeName* scopeNamep() const { return VN_AS(op4p(), ScopeName); } // MORE ACCESSORS void dpiOpenParentInc() { ++m_dpiOpenParent; } void dpiOpenParentClear() { m_dpiOpenParent = 0; } uint64_t dpiOpenParent() const { return m_dpiOpenParent; } - void scopeNamep(AstNode* nodep) { setNOp4p(nodep); } void taskPublic(bool flag) { m_taskPublic = flag; } bool taskPublic() const { return m_taskPublic; } void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } @@ -184,7 +177,7 @@ public: class AstNodeFile VL_NOT_FINAL : public AstNode { // Emitted Otput file // Parents: NETLIST - // Children: AstTextBlock + // @astgen op1 := tblockp : Optional[AstTextBlock] private: string m_name; ///< Filename public: @@ -195,13 +188,14 @@ public: void dump(std::ostream& str) const override; string name() const override { return m_name; } bool same(const AstNode* /*samep*/) const override { return true; } - void tblockp(AstTextBlock* tblockp) { setOp1p((AstNode*)tblockp); } - AstTextBlock* tblockp() { return VN_AS(op1p(), TextBlock); } }; class AstNodeModule VL_NOT_FINAL : public AstNode { // A module, package, program or interface declaration; // something that can live directly under the TOP, // excluding $unit package stuff + // @astgen op1 := inlinesp : List[AstNode] + // @astgen op2 := stmtsp : List[AstNode] + // @astgen op3 := activesp : List[AstActive] private: string m_name; // Name of the module const string m_origName; // Name of the module, ignoring name() changes, for dot lookup @@ -239,12 +233,6 @@ public: bool maybePointedTo() const override { return true; } string name() const override { return m_name; } virtual bool timescaleMatters() const = 0; - AstNode* stmtsp() const { return op2p(); } // op2 = List of statements - AstActive* activesp() const { return VN_AS(op3p(), Active); } // op3 = List of i/sblocks - // METHODS - void addInlinesp(AstNode* nodep) { addOp1p(nodep); } - void addStmtp(AstNode* nodep) { addNOp2p(nodep); } - void addActivep(AstNode* nodep) { addOp3p(nodep); } // ACCESSORS void name(const string& name) override { m_name = name; } string origName() const override { return m_origName; } @@ -278,44 +266,38 @@ public: }; class AstNodePreSel VL_NOT_FINAL : public AstNode { // Something that becomes an AstSel + // @astgen op1 := fromp : AstNode + // @astgen op2 := rhsp : AstNode + // @astgen op3 := thsp : Optional[AstNode] + // @astgen op4 := attrp : Optional[AstAttrOf] protected: - AstNodePreSel(VNType t, FileLine* fl, AstNode* fromp, AstNode* rhs, AstNode* ths) + AstNodePreSel(VNType t, FileLine* fl, AstNode* fromp, AstNode* rhsp, AstNode* thsp) : AstNode{t, fl} { - setOp1p(fromp); - setOp2p(rhs); - setNOp3p(ths); + this->fromp(fromp); + this->rhsp(rhsp); + this->thsp(thsp); } public: ASTGEN_MEMBERS_NodePreSel; - AstNode* fromp() const { return op1p(); } - AstNode* rhsp() const { return op2p(); } - AstNode* thsp() const { return op3p(); } - AstAttrOf* attrp() const { return VN_AS(op4p(), AttrOf); } - void fromp(AstNode* nodep) { return setOp1p(nodep); } - void rhsp(AstNode* nodep) { return setOp2p(nodep); } - void thsp(AstNode* nodep) { return setOp3p(nodep); } - void attrp(AstAttrOf* nodep) { return setOp4p(reinterpret_cast(nodep)); } // METHODS bool same(const AstNode*) const override { return true; } }; class AstNodeProcedure VL_NOT_FINAL : public AstNode { // IEEE procedure: initial, final, always + // @astgen op2 := stmtsp : List[AstNode] // Note: op1 is used in some sub-types only bool m_suspendable = false; // Is suspendable by a Delay, EventControl, etc. - // protected: - AstNodeProcedure(VNType t, FileLine* fl, AstNode* bodysp) + AstNodeProcedure(VNType t, FileLine* fl, AstNode* stmtsp) : AstNode{t, fl} { - addNOp2p(bodysp); + addStmtsp(stmtsp); } public: ASTGEN_MEMBERS_NodeProcedure; // METHODS void dump(std::ostream& str) const override; - AstNode* bodysp() const { return op2p(); } // op2 = Statements to evaluate - void addStmtp(AstNode* nodep) { addOp2p(nodep); } - bool isJustOneBodyStmt() const { return bodysp() && !bodysp()->nextp(); } + bool isJustOneBodyStmt() const { return stmtsp() && !stmtsp()->nextp(); } bool isSuspendable() const { return m_suspendable; } void setSuspendable() { m_suspendable = true; } }; @@ -349,13 +331,17 @@ public: void dump(std::ostream& str = std::cout) const override; }; class AstNodeAssign VL_NOT_FINAL : public AstNodeStmt { + // Iteration is in order, and we want rhsp to be visited first (which is the execution order) + // @astgen op1 := rhsp : AstNode + // @astgen op2 := lhsp : AstNode + // @astgen op3 := timingControlp : Optional[AstNode] protected: AstNodeAssign(VNType t, FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* timingControlp = nullptr) : AstNodeStmt{t, fl} { - setOp1p(rhsp); - setOp2p(lhsp); - setNOp3p(timingControlp); + this->rhsp(rhsp); + this->lhsp(lhsp); + this->timingControlp(timingControlp); dtypeFrom(lhsp); } @@ -363,14 +349,6 @@ public: ASTGEN_MEMBERS_NodeAssign; // Clone single node, just get same type back. virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) = 0; - // So iteration hits the RHS which is "earlier" in execution order, it's op1, not op2 - AstNode* rhsp() const { return op1p(); } // op1 = Assign from - AstNode* lhsp() const { return op2p(); } // op2 = Assign to - // op3 = Timing controls (delays, event controls) - AstNode* timingControlp() const { return op3p(); } - void timingControlp(AstNode* const np) { setNOp3p(np); } - void rhsp(AstNode* np) { setOp1p(np); } - void lhsp(AstNode* np) { setOp2p(np); } bool hasDType() const override { return true; } virtual bool cleanRhs() const { return true; } int instrCount() const override { return widthInstrs(); } @@ -381,6 +359,8 @@ public: }; class AstNodeCCall VL_NOT_FINAL : public AstNodeStmt { // A call of a C++ function, perhaps a AstCFunc or perhaps globally named + // @astgen op2 := argsp : List[AstNode] // Note: op1 used by some sub-types only + // // Functions are not statements, while tasks are. AstNodeStmt needs isStatement() to deal. AstCFunc* m_funcp; string m_argTypes; @@ -389,7 +369,7 @@ protected: AstNodeCCall(VNType t, FileLine* fl, AstCFunc* funcp, AstNode* argsp = nullptr) : AstNodeStmt{t, fl, true} , m_funcp{funcp} { - addNOp2p(argsp); + addArgsp(argsp); } public: @@ -402,7 +382,6 @@ public: const AstNodeCCall* const asamep = static_cast(samep); return (funcp() == asamep->funcp() && argTypes() == asamep->argTypes()); } - AstNode* exprsp() const { return op2p(); } // op2 = expressions to print bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } bool isPure() const override; @@ -411,34 +390,29 @@ public: void funcp(AstCFunc* funcp) { m_funcp = funcp; } void argTypes(const string& str) { m_argTypes = str; } string argTypes() const { return m_argTypes; } - // op1p reserved for AstCMethodCall - AstNode* argsp() const { return op2p(); } - void addArgsp(AstNode* nodep) { addOp2p(nodep); } }; class AstNodeCase VL_NOT_FINAL : public AstNodeStmt { + // @astgen op1 := exprp : AstNode // Condition (scurtinee) expression + // @astgen op2 := itemsp : List[AstCaseItem] + // @astgen op3 := notParallelp : List[AstNode] // assertion code for non-full case's protected: - AstNodeCase(VNType t, FileLine* fl, AstNode* exprp, AstNode* casesp) + AstNodeCase(VNType t, FileLine* fl, AstNode* exprp, AstCaseItem* itemsp) : AstNodeStmt{t, fl} { - setOp1p(exprp); - addNOp2p(casesp); + this->exprp(exprp); + this->addItemsp(itemsp); } public: ASTGEN_MEMBERS_NodeCase; int instrCount() const override { return INSTR_COUNT_BRANCH; } - AstNode* exprp() const { return op1p(); } // op1 = case condition - AstCaseItem* itemsp() const { - return VN_AS(op2p(), CaseItem); - } // op2 = list of case expressions - AstNode* notParallelp() const { return op3p(); } // op3 = assertion code for non-full case's - void addItemsp(AstNode* nodep) { addOp2p(nodep); } - void addNotParallelp(AstNode* nodep) { setOp3p(nodep); } }; class AstNodeCoverOrAssert VL_NOT_FINAL : public AstNodeStmt { // Cover or Assert // Parents: {statement list} - // Children: expression, report string -private: + // @astgen op1 := propp : AstNode + // @astgen op2 := sentreep : Optional[AstSenTree] + // op3 used by some sub-types only + // @astgen op4 := passsp: List[AstNode] // Statments when propp is passing/truthly const bool m_immediate; // Immediate assertion/cover string m_name; // Name to report public: @@ -447,22 +421,23 @@ public: : AstNodeStmt{t, fl} , m_immediate{immediate} , m_name{name} { - addOp1p(propp); - addNOp4p(passsp); + this->propp(propp); + this->addPasssp(passsp); } ASTGEN_MEMBERS_NodeCoverOrAssert; string name() const override { return m_name; } // * = Var name bool same(const AstNode* samep) const override { return samep->name() == name(); } void name(const string& name) override { m_name = name; } void dump(std::ostream& str = std::cout) const override; - AstNode* propp() const { return op1p(); } // op1 = property - AstSenTree* sentreep() const { return VN_AS(op2p(), SenTree); } // op2 = clock domain - void sentreep(AstSenTree* sentreep) { addOp2p((AstNode*)sentreep); } // op2 = clock domain - AstNode* passsp() const { return op4p(); } // op4 = statements (assert/cover passes) bool immediate() const { return m_immediate; } }; class AstNodeFTaskRef VL_NOT_FINAL : public AstNodeStmt { // A reference to a task (or function) + // @astgen op1 := namep : AstNode + // op2 used by some sub-types only + // @astgen op3 := pinsp : List[AstNode] + // @astgen op4 := scopeNamep : Optional[AstScopeName] + // // Functions are not statements, while tasks are. AstNodeStmt needs isStatement() to deal. private: AstNodeFTask* m_taskp = nullptr; // [AfterLink] Pointer to task referenced @@ -474,13 +449,13 @@ private: protected: AstNodeFTaskRef(VNType t, FileLine* fl, bool statement, AstNode* namep, AstNode* pinsp) : AstNodeStmt{t, fl, statement} { - setOp1p(namep); - addNOp3p(pinsp); + this->namep(namep); + this->addPinsp(pinsp); } AstNodeFTaskRef(VNType t, FileLine* fl, bool statement, const string& name, AstNode* pinsp) : AstNodeStmt{t, fl, statement} , m_name{name} { - addNOp3p(pinsp); + this->addPinsp(pinsp); } public: @@ -501,58 +476,46 @@ public: void classOrPackagep(AstNodeModule* nodep) { m_classOrPackagep = nodep; } bool pli() const { return m_pli; } void pli(bool flag) { m_pli = flag; } - // op1 = namep - AstNode* namep() const { return op1p(); } - // op2 = reserved for AstMethodCall - // op3 = Pin interconnection list - AstNode* pinsp() const { return op3p(); } - void addPinsp(AstNode* nodep) { addOp3p(nodep); } - // op4 = scope tracking - AstScopeName* scopeNamep() const { return VN_AS(op4p(), ScopeName); } - void scopeNamep(AstNode* nodep) { setNOp4p(nodep); } }; class AstNodeFor VL_NOT_FINAL : public AstNodeStmt { + // @astgen op1 := initsp : List[AstNode] + // @astgen op2 := condp : AstNode + // @astgen op3 := incsp : List[AstNode] + // @astgen op4 := stmtsp : List[AstNode] protected: AstNodeFor(VNType t, FileLine* fl, AstNode* initsp, AstNode* condp, AstNode* incsp, - AstNode* bodysp) + AstNode* stmtsp) : AstNodeStmt{t, fl} { - addNOp1p(initsp); - setOp2p(condp); - addNOp3p(incsp); - addNOp4p(bodysp); + this->addInitsp(initsp); + this->condp(condp); + this->addIncsp(incsp); + this->addStmtsp(stmtsp); } public: ASTGEN_MEMBERS_NodeFor; - AstNode* initsp() const { return op1p(); } // op1 = initial statements - AstNode* condp() const { return op2p(); } // op2 = condition to continue - AstNode* incsp() const { return op3p(); } // op3 = increment statements - AstNode* bodysp() const { return op4p(); } // op4 = body of loop bool isGateOptimizable() const override { return false; } int instrCount() const override { return INSTR_COUNT_BRANCH; } bool same(const AstNode* /*samep*/) const override { return true; } }; class AstNodeIf VL_NOT_FINAL : public AstNodeStmt { + // @astgen op1 := condp : AstNode + // @astgen op2 := thensp : List[AstNode] + // @astgen op3 := elsesp : List[AstNode] private: VBranchPred m_branchPred; // Branch prediction as taken/untaken? bool m_isBoundsCheck; // True if this if node was inserted for array bounds checking protected: - AstNodeIf(VNType t, FileLine* fl, AstNode* condp, AstNode* ifsp, AstNode* elsesp) + AstNodeIf(VNType t, FileLine* fl, AstNode* condp, AstNode* thensp, AstNode* elsesp) : AstNodeStmt{t, fl} { - setOp1p(condp); - addNOp2p(ifsp); - addNOp3p(elsesp); + this->condp(condp); + this->addThensp(thensp); + this->addElsesp(elsesp); isBoundsCheck(false); } public: ASTGEN_MEMBERS_NodeIf; - AstNode* condp() const { return op1p(); } // op1 = condition - AstNode* ifsp() const { return op2p(); } // op2 = list of true statements - AstNode* elsesp() const { return op3p(); } // op3 = list of false statements - void condp(AstNode* newp) { setOp1p(newp); } - void addIfsp(AstNode* newp) { addOp2p(newp); } - void addElsesp(AstNode* newp) { addOp3p(newp); } bool isGateOptimizable() const override { return false; } bool isGateDedupable() const override { return true; } int instrCount() const override { return INSTR_COUNT_BRANCH; } @@ -562,21 +525,25 @@ public: void isBoundsCheck(bool flag) { m_isBoundsCheck = flag; } bool isBoundsCheck() const { return m_isBoundsCheck; } bool isFirstInMyListOfStatements(AstNode* n) const override { - return n == ifsp() || n == elsesp(); + return n == thensp() || n == elsesp(); } }; class AstNodeReadWriteMem VL_NOT_FINAL : public AstNodeStmt { -private: + // @astgen op1 := filenamep : AstNode + // @astgen op2 := memp : AstNode + // @astgen op3 := lsbp : Optional[AstNode] + // @astgen op4 := msbp : Optional[AstNode] + const bool m_isHex; // readmemh, not readmemb public: AstNodeReadWriteMem(VNType t, FileLine* fl, bool hex, AstNode* filenamep, AstNode* memp, AstNode* lsbp, AstNode* msbp) : AstNodeStmt(t, fl) , m_isHex(hex) { - setOp1p(filenamep); - setOp2p(memp); - setNOp3p(lsbp); - setNOp4p(msbp); + this->filenamep(filenamep); + this->memp(memp); + this->lsbp(lsbp); + this->msbp(msbp); } ASTGEN_MEMBERS_NodeReadWriteMem; bool isGateOptimizable() const override { return false; } @@ -588,10 +555,6 @@ public: return isHex() == static_cast(samep)->isHex(); } bool isHex() const { return m_isHex; } - AstNode* filenamep() const { return op1p(); } - AstNode* memp() const { return op2p(); } - AstNode* lsbp() const { return op3p(); } - AstNode* msbp() const { return op4p(); } virtual const char* cFuncPrefixp() const = 0; }; class AstNodeText VL_NOT_FINAL : public AstNode { @@ -632,7 +595,8 @@ public: class AstActive final : public AstNode { // Block of code with sensitivity activation // Parents: MODULE | CFUNC - // Children: SENTREE, statements + // @astgen op1 := sensesStorep : Optional[AstSenTree] // Moved into m_sensesp in V3Active + // @astgen op2 := stmtsp : List[AstNode] // Logic private: string m_name; AstSenTree* m_sensesp; @@ -652,54 +616,45 @@ public: // Statements are broken into pieces, as some must come before others. void sensesp(AstSenTree* nodep) { m_sensesp = nodep; } AstSenTree* sensesp() const { return m_sensesp; } - // op1 = Sensitivity tree, if a clocked block in early stages - void sensesStorep(AstSenTree* nodep) { addOp1p((AstNode*)nodep); } - AstSenTree* sensesStorep() const { return VN_AS(op1p(), SenTree); } - // op2 = Logic - AstNode* stmtsp() const { return op2p(); } - void addStmtsp(AstNode* nodep) { addOp2p(nodep); } // METHODS inline bool hasClocked() const; inline bool hasCombo() const; }; class AstArg final : public AstNode { // An argument to a function/task -private: + // @astgen op1 := exprp : Optional[AstNode] // nullptr if omitted string m_name; // Pin name, or "" for number based interconnect public: AstArg(FileLine* fl, const string& name, AstNode* exprp) : ASTGEN_SUPER_Arg(fl) , m_name{name} { - setNOp1p(exprp); + this->exprp(exprp); } ASTGEN_MEMBERS_Arg; string name() const override { return m_name; } // * = Pin name, ""=go by number void name(const string& name) override { m_name = name; } - void exprp(AstNode* nodep) { addOp1p(nodep); } - // op1 = Expression connected to pin, nullptr if unconnected - AstNode* exprp() const { return op1p(); } bool emptyConnectNoNext() const { return !exprp() && name() == "" && !nextp(); } }; class AstAttrOf final : public AstNode { -private: // Return a value of a attribute, for example a LSB or array LSB of a signal + // @astgen op1 := fromp : Optional[AstNode] + // @astgen op2 := dimp : Optional[AstNode] VAttrType m_attrType; // What sort of extraction public: AstAttrOf(FileLine* fl, VAttrType attrtype, AstNode* fromp = nullptr, AstNode* dimp = nullptr) : ASTGEN_SUPER_AttrOf(fl) { - setNOp1p(fromp); - setNOp2p(dimp); + this->fromp(fromp); + this->dimp(dimp); m_attrType = attrtype; } ASTGEN_MEMBERS_AttrOf; - AstNode* fromp() const { return op1p(); } - AstNode* dimp() const { return op2p(); } VAttrType attrType() const { return m_attrType; } void dump(std::ostream& str = std::cout) const override; }; class AstBind final : public AstNode { // Parents: MODULE // Children: CELL + // @astgen op1 := cellsp : List[AstNode] private: string m_name; // Binding to name public: @@ -707,18 +662,21 @@ public: : ASTGEN_SUPER_Bind(fl) , m_name{name} { UASSERT_OBJ(VN_IS(cellsp, Cell), cellsp, "Only instances allowed to be bound"); - addNOp1p(cellsp); + this->addCellsp(cellsp); } ASTGEN_MEMBERS_Bind; // ACCESSORS string name() const override { return m_name; } // * = Bind Target name void name(const string& name) override { m_name = name; } - AstNode* cellsp() const { return op1p(); } // op1 = cells }; class AstCFunc final : public AstNode { // C++ function // Parents: MODULE/SCOPE - // Children: VAR/statements + // If adding node accessors, see below emptyBody + // @astgen op1 := argsp : List[AstNode] + // @astgen op2 := initsp : List[AstNode] + // @astgen op3 := stmtsp : List[AstNode] + // @astgen op4 := finalsp : List[AstNode] private: AstScope* m_scopep; string m_name; @@ -853,16 +811,6 @@ public: void dpiTraceInit(bool flag) { m_dpiTraceInit = flag; } bool dpiTraceInit() const { return m_dpiTraceInit; } bool isCoroutine() const { return m_rtnType == "VlCoroutine"; } - // - // If adding node accessors, see below emptyBody - AstNode* argsp() const { return op1p(); } - void addArgsp(AstNode* nodep) { addOp1p(nodep); } - AstNode* initsp() const { return op2p(); } - void addInitsp(AstNode* nodep) { addOp2p(nodep); } - AstNode* stmtsp() const { return op3p(); } - void addStmtsp(AstNode* nodep) { addOp3p(nodep); } - AstNode* finalsp() const { return op4p(); } - void addFinalsp(AstNode* nodep) { addOp4p(nodep); } // Special methods bool emptyBody() const { return argsp() == nullptr && initsp() == nullptr && stmtsp() == nullptr @@ -888,36 +836,33 @@ public: }; class AstCaseItem final : public AstNode { // Single item of a case statement - // Parents: CASE - // condsp Children: MATH (Null condition used for default block) - // bodysp Children: Statements + // @astgen op1 := condsp : List[AstNode] + // @astgen op2 := stmtsp : List[AstNode] public: - AstCaseItem(FileLine* fl, AstNode* condsp, AstNode* bodysp) + AstCaseItem(FileLine* fl, AstNode* condsp, AstNode* stmtsp) : ASTGEN_SUPER_CaseItem(fl) { - addNOp1p(condsp); - addNOp2p(bodysp); + this->addCondsp(condsp); + this->addStmtsp(stmtsp); } ASTGEN_MEMBERS_CaseItem; int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } - AstNode* condsp() const { return op1p(); } // op1 = list of possible matching expressions - AstNode* bodysp() const { return op2p(); } // op2 = what to do - void condsp(AstNode* nodep) { setOp1p(nodep); } - void addBodysp(AstNode* newp) { addOp2p(newp); } bool isDefault() const { return condsp() == nullptr; } - bool isFirstInMyListOfStatements(AstNode* n) const override { return n == bodysp(); } + bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); } }; class AstCast final : public AstNode { - // Cast to appropriate data type - note lhsp is value, to match AstTypedef, AstCCast, etc + // Cast to appropriate data type + // @astgen op1 := fromp : AstNode + // @astgen op2 := childDTypep : Optional[AstNodeDType] public: - AstCast(FileLine* fl, AstNode* lhsp, VFlagChildDType, AstNodeDType* dtp) + AstCast(FileLine* fl, AstNode* fromp, VFlagChildDType, AstNodeDType* dtp) : ASTGEN_SUPER_Cast(fl) { - setOp1p(lhsp); - setOp2p(dtp); + this->fromp(fromp); + this->childDTypep(dtp); dtypeFrom(dtp); } - AstCast(FileLine* fl, AstNode* lhsp, AstNodeDType* dtp) + AstCast(FileLine* fl, AstNode* fromp, AstNodeDType* dtp) : ASTGEN_SUPER_Cast(fl) { - setOp1p(lhsp); + this->fromp(fromp); dtypeFrom(dtp); } ASTGEN_MEMBERS_Cast; @@ -926,20 +871,18 @@ public: virtual bool cleanOut() const { V3ERROR_NA_RETURN(true); } virtual bool cleanLhs() const { return true; } virtual bool sizeMattersLhs() const { return false; } - AstNode* lhsp() const { return op1p(); } - AstNode* fromp() const { return lhsp(); } - void lhsp(AstNode* nodep) { setOp1p(nodep); } AstNodeDType* getChildDTypep() const override { return childDTypep(); } - AstNodeDType* childDTypep() const { return VN_AS(op2p(), NodeDType); } virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); } }; class AstCastParse final : public AstNode { // Cast to appropriate type, where we haven't determined yet what the data type is + // @astgen op1 := lhsp : AstNode + // @astgen op2 := dtp : AstNode public: AstCastParse(FileLine* fl, AstNode* lhsp, AstNode* dtp) : ASTGEN_SUPER_CastParse(fl) { - setOp1p(lhsp); - setOp2p(dtp); + this->lhsp(lhsp); + this->dtp(dtp); } ASTGEN_MEMBERS_CastParse; virtual string emitVerilog() { return "((%d)'(%l))"; } @@ -947,16 +890,16 @@ public: virtual bool cleanOut() const { V3ERROR_NA_RETURN(true); } virtual bool cleanLhs() const { return true; } virtual bool sizeMattersLhs() const { return false; } - AstNode* lhsp() const { return op1p(); } - AstNode* dtp() const { return op2p(); } }; class AstCastSize final : public AstNode { // Cast to specific size; signed/twostate inherited from lower element per IEEE + // @astgen op1 := lhsp : AstNode + // @astgen op2 := rhsp : AstNode public: AstCastSize(FileLine* fl, AstNode* lhsp, AstConst* rhsp) : ASTGEN_SUPER_CastSize(fl) { - setOp1p(lhsp); - setOp2p((AstNode*)rhsp); + this->lhsp(lhsp); + this->rhsp(rhsp); } ASTGEN_MEMBERS_CastSize; // No hasDType because widthing removes this node before the hasDType check @@ -964,12 +907,13 @@ public: virtual bool cleanOut() const { V3ERROR_NA_RETURN(true); } virtual bool cleanLhs() const { return true; } virtual bool sizeMattersLhs() const { return false; } - AstNode* lhsp() const { return op1p(); } - AstNode* rhsp() const { return op2p(); } }; class AstCell final : public AstNode { // A instantiation cell or interface call (don't know which until link) -private: + // @astgen op1 := pinsp : List[AstPin] // List of port assignments + // @astgen op2 := paramsp : List[AstPin] // List of parameter assignments + // @astgen op3 := rangep : Optional[AstRange] // Range for arrayed instances + // @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references FileLine* m_modNameFileline; // Where module the cell instances token was string m_name; // Cell name string m_origName; // Original name before dot addition @@ -989,9 +933,9 @@ public: , m_hasIfaceVar{false} , m_recursive{false} , m_trace{true} { - addNOp1p((AstNode*)pinsp); - addNOp2p((AstNode*)paramsp); - setNOp3p((AstNode*)rangep); + this->addPinsp(pinsp); + this->addParamsp(paramsp); + this->rangep(rangep); } ASTGEN_MEMBERS_Cell; // No cloneRelink, we presume cloneee's want the same module linkages @@ -1006,17 +950,7 @@ public: string modName() const { return m_modName; } // * = Instance name void modName(const string& name) { m_modName = name; } FileLine* modNameFileline() const { return m_modNameFileline; } - AstPin* pinsp() const { return VN_AS(op1p(), Pin); } // op1 = List of cell ports - // op2 = List of parameter #(##) values - AstPin* paramsp() const { return VN_AS(op2p(), Pin); } - // op3 = Range of arrayed instants (nullptr=not ranged) - AstRange* rangep() const { return VN_AS(op3p(), Range); } - // op4 = List of interface references - AstIntfRef* intfRefp() const { return VN_AS(op4p(), IntfRef); } AstNodeModule* modp() const { return m_modp; } // [AfterLink] = Pointer to module instantiated - void addPinsp(AstPin* nodep) { addOp1p((AstNode*)nodep); } - void addParamsp(AstPin* nodep) { addOp2p((AstNode*)nodep); } - void addIntfRefp(AstIntfRef* nodep) { addOp4p((AstNode*)nodep); } void modp(AstNodeModule* nodep) { m_modp = nodep; } bool hasIfaceVar() const { return m_hasIfaceVar; } void hasIfaceVar(bool flag) { m_hasIfaceVar = flag; } @@ -1027,18 +961,17 @@ public: }; class AstCellArrayRef final : public AstNode { // As-of-yet unlinkable reference into an array of cells -private: + // @astgen op1 := selp : List[AstNode] // Select expression string m_name; // Array name public: - AstCellArrayRef(FileLine* fl, const string& name, AstNode* selectExprp) + AstCellArrayRef(FileLine* fl, const string& name, AstNode* selp) : ASTGEN_SUPER_CellArrayRef(fl) , m_name{name} { - addNOp1p(selectExprp); + this->addSelp(selp); } ASTGEN_MEMBERS_CellArrayRef; // ACCESSORS string name() const override { return m_name; } // * = Array name - AstNode* selp() const { return op1p(); } // op1 = Select expression }; class AstCellInline final : public AstNode { // A instantiation cell that was removed by inlining @@ -1073,49 +1006,49 @@ public: }; class AstCellRef final : public AstNode { // As-of-yet unlinkable reference into a cell + // @astgen op1 := cellp : AstNode + // @astgen op2 := exprp : AstNode private: string m_name; // Cell name public: AstCellRef(FileLine* fl, const string& name, AstNode* cellp, AstNode* exprp) : ASTGEN_SUPER_CellRef(fl) , m_name{name} { - addNOp1p(cellp); - addNOp2p(exprp); + this->cellp(cellp); + this->exprp(exprp); } ASTGEN_MEMBERS_CellRef; // ACCESSORS string name() const override { return m_name; } // * = Array name - AstNode* cellp() const { return op1p(); } // op1 = Cell - AstNode* exprp() const { return op2p(); } // op2 = Expression }; class AstClassExtends final : public AstNode { // Children: List of AstParseRef for packages/classes // during early parse, then moves to dtype + // @astgen op1 := childDTypep : Optional[AstNodeDType] + // @astgen op2 := classOrPkgsp : Optional[AstNode] public: AstClassExtends(FileLine* fl, AstNode* classOrPkgsp) : ASTGEN_SUPER_ClassExtends(fl) { - setNOp2p(classOrPkgsp); // Only for parser + this->classOrPkgsp(classOrPkgsp); // Only for parser } ASTGEN_MEMBERS_ClassExtends; bool hasDType() const override { return true; } string verilogKwd() const override { return "extends"; } - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } - AstNode* classOrPkgsp() const { return op2p(); } AstClass* classp() const; // Class being extended (after link) }; class AstClassOrPackageRef final : public AstNode { + // @astgen op1 := paramsp : List[AstPin] private: string m_name; // Node not NodeModule to appease some early parser usage AstNode* m_classOrPackageNodep; // Package hierarchy public: AstClassOrPackageRef(FileLine* fl, const string& name, AstNode* classOrPackageNodep, - AstNode* paramsp) + AstPin* paramsp) : ASTGEN_SUPER_ClassOrPackageRef(fl) , m_name{name} , m_classOrPackageNodep{classOrPackageNodep} { - addNOp4p(paramsp); + this->addParamsp(paramsp); } ASTGEN_MEMBERS_ClassOrPackageRef; // METHODS @@ -1139,25 +1072,23 @@ public: AstNodeModule* classOrPackagep() const; AstPackage* packagep() const { return VN_CAST(classOrPackageNodep(), Package); } void classOrPackagep(AstNodeModule* nodep) { m_classOrPackageNodep = (AstNode*)nodep; } - AstPin* paramsp() const { return VN_AS(op4p(), Pin); } }; class AstClocking final : public AstNode { // Set default clock region // Parents: MODULE - // Children: Assertions + // @astgen op1 := sensesp : List[AstSenItem] + // @astgen op2 := bodysp : List[AstNode] public: AstClocking(FileLine* fl, AstSenItem* sensesp, AstNode* bodysp) : ASTGEN_SUPER_Clocking(fl) { - addOp1p((AstNode*)sensesp); - addNOp2p(bodysp); + this->addSensesp(sensesp); + this->addBodysp(bodysp); } ASTGEN_MEMBERS_Clocking; - // op1 = Sensitivity list - AstSenItem* sensesp() const { return VN_AS(op1p(), SenItem); } - AstNode* bodysp() const { return op2p(); } // op2 = Body }; class AstConstPool final : public AstNode { // Container for const static data + // @astgen op1 := modulep : AstModule // m_modp below TODO: fix this mess std::unordered_multimap m_tables; // Constant tables (unpacked arrays) std::unordered_multimap m_consts; // Constant tables (scalars) AstModule* const m_modp; // The Module holding the Scope below ... @@ -1189,8 +1120,7 @@ public: class AstDefParam final : public AstNode { // A defparam assignment // Parents: MODULE - // Children: math -private: + // @astgen op1 := rhsp : AstNode string m_name; // Name of variable getting set string m_path; // Dotted cellname to set parameter of public: @@ -1198,24 +1128,25 @@ public: : ASTGEN_SUPER_DefParam(fl) , m_name{name} , m_path{path} { - setOp1p(rhsp); + this->rhsp(rhsp); } string name() const override { return m_name; } // * = Scope name ASTGEN_MEMBERS_DefParam; bool same(const AstNode*) const override { return true; } - AstNode* rhsp() const { return op1p(); } // op1 = Assign from string path() const { return m_path; } }; class AstDot final : public AstNode { // A dot separating paths in an AstVarXRef, AstFuncRef or AstTaskRef // These are eliminated in the link stage + // @astgen op1 := lhsp : AstNode + // @astgen op2 := rhsp : AstNode const bool m_colon; // Is a "::" instead of a "." (lhs must be package/class) public: AstDot(FileLine* fl, bool colon, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_Dot(fl) , m_colon{colon} { - setOp1p(lhsp); - setOp2p(rhsp); + this->lhsp(lhsp); + this->rhsp(rhsp); } ASTGEN_MEMBERS_Dot; // For parser, make only if non-null package @@ -1224,9 +1155,6 @@ public: return new AstDot(fl, true, packageOrClassp, rhsp); } void dump(std::ostream& str) const override; - AstNode* lhsp() const { return op1p(); } - void rhsp(AstNode* nodep) { setOp2p(nodep); } - AstNode* rhsp() const { return op2p(); } bool colon() const { return m_colon; } }; class AstDpiExport final : public AstNode { @@ -1249,7 +1177,7 @@ public: }; class AstElabDisplay final : public AstNode { // Parents: stmtlist - // Children: SFORMATF to generate print string + // @astgen op1 := fmtp : List[AstSFormatF] private: VDisplayType m_displayType; @@ -1272,8 +1200,6 @@ public: int instrCount() const override { return INSTR_COUNT_PLI; } VDisplayType displayType() const { return m_displayType; } void displayType(VDisplayType type) { m_displayType = type; } - void fmtp(AstSFormatF* nodep) { addOp1p((AstNode*)nodep); } // op1 = To-String formatter - AstSFormatF* fmtp() const { return VN_AS(op1p(), SFormatF); } }; class AstEmpty final : public AstNode { // Represents something missing, e.g. a missing argument in FOREACH @@ -1290,7 +1216,10 @@ class AstExecGraph final : public AstNode { // // The AstMTaskBody nodes are also children of this node, so we can visit // them without traversing the graph. -private: + // + // @astgen op1 := mTaskBodiesp : List[AstMTaskBody] + // In later phases, the statements that start the parallel execution + // @astgen op2 := stmtsp : List[AstNode] V3Graph* const m_depGraphp; // contains ExecMTask vertices const string m_name; // Name of this AstExecGraph (for uniqueness at code generation) @@ -1305,22 +1234,17 @@ public: string name() const override { return m_name; } V3Graph* depGraphp() { return m_depGraphp; } const V3Graph* depGraphp() const { return m_depGraphp; } - // op1: The mtask bodies - AstMTaskBody* mTaskBodiesp() const { return VN_AS(op1p(), MTaskBody); } - void addMTaskBodyp(AstMTaskBody* bodyp) { addOp1p((AstNode*)bodyp); } - // op2: In later phases, the statements that start the parallel execution - void addStmtsp(AstNode* stmtp) { addOp2p(stmtp); } }; class AstImplicit final : public AstNode { // Create implicit wires and do nothing else, for gates that are ignored // Parents: MODULE + // @astgen op1 := exprsp : List[AstNode] public: AstImplicit(FileLine* fl, AstNode* exprsp) : ASTGEN_SUPER_Implicit(fl) { - addNOp1p(exprsp); + this->addExprsp(exprsp); } ASTGEN_MEMBERS_Implicit; - AstNode* exprsp() const { return op1p(); } // op1 = Assign from }; class AstInitArray final : public AstNode { // Set a var to a map of values @@ -1328,7 +1252,9 @@ class AstInitArray final : public AstNode { // If default is specified, the vector may be sparse, and not provide each value. // Key values are C++ array style, with lo() at index 0 // Parents: ASTVAR::init() - // Children: AstInitItem + // @astgen op1 := defaultp : Optional[AstNode] // Default, if sparse + // @astgen op2 := initsp : List[AstNode] // Initial value expressions + // public: using KeyItemMap = std::map; @@ -1338,7 +1264,7 @@ public: AstInitArray(FileLine* fl, AstNodeDType* newDTypep, AstNode* defaultp) : ASTGEN_SUPER_InitArray(fl) { dtypep(newDTypep); - addNOp1p(defaultp); + this->defaultp(defaultp); } ASTGEN_MEMBERS_InitArray; void dump(std::ostream& str) const override; @@ -1350,12 +1276,9 @@ public: // of children list, and instead use map-vs-map key/value compare return m_map == static_cast(samep)->m_map; } - AstNode* defaultp() const { return op1p(); } // op1 = Default if sparse - void defaultp(AstNode* newp) { setOp1p(newp); } - AstNode* initsp() const { return op2p(); } // op2 = Initial value expressions void addValuep(AstNode* newp) { addIndexValuep(m_map.size(), newp); } const KeyItemMap& map() const { return m_map; } - AstNode* addIndexValuep(uint64_t index, AstNode* newp); + void addIndexValuep(uint64_t index, AstNode* newp); AstNode* getIndexValuep(uint64_t index) const; AstNode* getIndexDefaultedValuep(uint64_t index) const; }; @@ -1363,17 +1286,16 @@ class AstInitItem final : public AstNode { // Container for a item in an init array // This container is present so that the value underneath may get replaced with a new nodep // and the upper AstInitArray's map will remain correct (pointing to this InitItem) + // @astgen op1 := valuep : AstNode public: // Parents: INITARRAY AstInitItem(FileLine* fl, AstNode* valuep) : ASTGEN_SUPER_InitItem(fl) { - addOp1p(valuep); + this->valuep(valuep); } ASTGEN_MEMBERS_InitItem; bool maybePointedTo() const override { return true; } bool hasDType() const override { return false; } // See valuep()'s dtype instead - AstNode* valuep() const { return op1p(); } // op1 = Value - void valuep(AstNode* nodep) { addOp1p(nodep); } }; class AstIntfRef final : public AstNode { // An interface reference @@ -1388,7 +1310,7 @@ public: }; class AstMTaskBody final : public AstNode { // Hold statements for each MTask -private: + // @astgen op1 := stmtsp : List[AstNode] ExecMTask* m_execMTaskp = nullptr; public: @@ -1399,8 +1321,6 @@ public: BROKEN_RTN(!m_execMTaskp); return nullptr; } - AstNode* stmtsp() const { return op1p(); } - void addStmtsp(AstNode* nodep) { addOp1p(nodep); } void addStmtsFirstp(AstNode* nodep) { if (stmtsp()) { stmtsp()->addHereThisAsNext(nodep); @@ -1414,18 +1334,17 @@ public: }; class AstModport final : public AstNode { // A modport in an interface -private: + // @astgen op1 := varsp : List[AstNode] string m_name; // Name of the modport public: AstModport(FileLine* fl, const string& name, AstNode* varsp) : ASTGEN_SUPER_Modport(fl) , m_name{name} { - addNOp1p(varsp); + this->addVarsp(varsp); } string name() const override { return m_name; } bool maybePointedTo() const override { return true; } ASTGEN_MEMBERS_Modport; - AstNode* varsp() const { return op1p(); } // op1 = List of Vars }; class AstModportFTaskRef final : public AstNode { // An import/export referenced under a modport @@ -1478,7 +1397,10 @@ class AstNetlist final : public AstNode { // All modules are under this single top node. // Parents: none // Children: MODULEs & CFILEs -private: + // @astgen op1 := modulesp : List[AstNodeModule] + // @astgen op2 := filesp : List[AstNodeFile] + // @astgen op3 := miscsp : List[AstNode] + AstTypeTable* const m_typeTablep; // Reference to top type table, for faster lookup AstConstPool* const m_constPoolp; // Reference to constant pool, for faster lookup AstPackage* m_dollarUnitPkgp = nullptr; // $unit @@ -1500,16 +1422,9 @@ public: void cloneRelink() override { V3ERROR_NA; } string name() const override { return "$root"; } void dump(std::ostream& str) const override; - AstNodeModule* modulesp() const { // op1 = List of modules - return VN_AS(op1p(), NodeModule); - } AstNodeModule* topModulep() const { // Top module in hierarchy return modulesp(); // First one in the list, for now } - void addModulep(AstNodeModule* modulep) { addOp1p((AstNode*)modulep); } - AstNodeFile* filesp() const { return VN_AS(op2p(), NodeFile); } // op2 = List of files - void addFilesp(AstNodeFile* filep) { addOp2p((AstNode*)filep); } - void addMiscsp(AstNode* nodep) { addOp3p(nodep); } AstTypeTable* typeTablep() { return m_typeTablep; } AstConstPool* constPoolp() { return m_constPoolp; } AstPackage* dollarUnitPkgp() const { return m_dollarUnitPkgp; } @@ -1587,8 +1502,9 @@ class AstParseRef final : public AstNode { // We don't know which at parse time due to bison constraints // The link stages will replace this with AstVarRef, or AstTaskRef, etc. // Parents: math|stmt - // Children: TEXT|DOT|SEL*|TASK|FUNC (or expression under sel) -private: + // @astgen op1 := lhsp : Optional[AstNode] + // @astgen op2 := ftaskrefp : Optional[AstNodeFTaskRef] + VParseRefExp m_expect; // Type we think it should resolve to string m_name; @@ -1598,8 +1514,8 @@ public: : ASTGEN_SUPER_ParseRef(fl) , m_expect{expect} , m_name{name} { - setNOp1p(lhsp); - setNOp2p((AstNode*)ftaskrefp); + this->lhsp(lhsp); + this->ftaskrefp(ftaskrefp); } ASTGEN_MEMBERS_ParseRef; void dump(std::ostream& str) const override; @@ -1611,14 +1527,10 @@ public: void name(const string& name) override { m_name = name; } VParseRefExp expect() const { return m_expect; } void expect(VParseRefExp exp) { m_expect = exp; } - // op1 = Components - AstNode* lhsp() const { return op1p(); } // op1 = List of statements - AstNode* ftaskrefp() const { return op2p(); } // op2 = Function/task reference - // op2 = Function/task reference - void ftaskrefp(AstNodeFTaskRef* nodep) { setNOp2p((AstNode*)nodep); } }; class AstPin final : public AstNode { - // A pin on a cell + // A port or parameter assignment on an instantiaton + // @astgen op1 := exprp : Optional[AstNode] // Expression connected (nullptr if unconnected) private: int m_pinNum; // Pin number string m_name; // Pin name, or "" for number based interconnect @@ -1631,7 +1543,7 @@ public: : ASTGEN_SUPER_Pin(fl) , m_pinNum{pinNum} , m_name{name} { - setNOp1p(exprp); + this->exprp(exprp); } inline AstPin(FileLine* fl, int pinNum, AstVarRef* varname, AstNode* exprp); ASTGEN_MEMBERS_Pin; @@ -1642,9 +1554,6 @@ public: string prettyOperatorName() const override; bool dotStar() const { return name() == ".*"; } // Fake name for .* connections until linked int pinNum() const { return m_pinNum; } - void exprp(AstNode* nodep) { addOp1p(nodep); } - // op1 = Expression connected to pin, nullptr if unconnected - AstNode* exprp() const { return op1p(); } AstVar* modVarp() const { return m_modVarp; } // [After Link] Pointer to variable void modVarp(AstVar* nodep) { m_modVarp = nodep; } // [After Link] Pointer to variable @@ -1657,9 +1566,9 @@ public: }; class AstPort final : public AstNode { // A port (in/out/inout) on a module -private: - int m_pinNum; // Pin number - string m_name; // Name of pin + // @astgen op1 := exprp : Optional[AstNode] // Expression connected to port + const int m_pinNum; // Pin number + const string m_name; // Name of pin public: AstPort(FileLine* fl, int pinnum, const string& name) : ASTGEN_SUPER_Port(fl) @@ -1668,10 +1577,8 @@ public: ASTGEN_MEMBERS_Port; string name() const override { return m_name; } // * = Port name int pinNum() const { return m_pinNum; } // * = Pin number, for order based instantiation - AstNode* exprp() const { return op1p(); } // op1 = Expression connected to port }; class AstPragma final : public AstNode { -private: const VPragmaType m_pragType; // Type of pragma public: // Pragmas don't result in any output code, they're just flags that affect @@ -1690,42 +1597,43 @@ class AstPropClocked final : public AstNode { // A clocked property // Parents: ASSERT|COVER (property) // Children: SENITEM, Properties + // @astgen op1 := sensesp : Optional[AstSenItem] + // @astgen op2 := disablep : Optional[AstNode] + // @astgen op3 := propp : AstNode public: AstPropClocked(FileLine* fl, AstSenItem* sensesp, AstNode* disablep, AstNode* propp) : ASTGEN_SUPER_PropClocked(fl) { - addNOp1p((AstNode*)sensesp); - addNOp2p(disablep); - addOp3p(propp); + this->sensesp(sensesp); + this->disablep(disablep); + this->propp(propp); } ASTGEN_MEMBERS_PropClocked; bool hasDType() const override { return true; } // Used under Cover, which expects a bool child - AstSenItem* sensesp() const { return VN_AS(op1p(), SenItem); } // op1 = Sensitivity list - AstNode* disablep() const { return op2p(); } // op2 = disable - AstNode* propp() const { return op3p(); } // op3 = property }; class AstPull final : public AstNode { -private: - bool m_direction; + // @astgen op1 := lhsp : AstNode + + const bool m_direction; public: AstPull(FileLine* fl, AstNode* lhsp, bool direction) : ASTGEN_SUPER_Pull(fl) , m_direction{direction} { - setOp1p(lhsp); + this->lhsp(lhsp); } ASTGEN_MEMBERS_Pull; bool same(const AstNode* samep) const override { return direction() == static_cast(samep)->direction(); } - void lhsp(AstNode* np) { setOp1p(np); } - AstNode* lhsp() const { return op1p(); } // op1 = Assign to uint32_t direction() const { return (uint32_t)m_direction; } }; class AstSFormatF final : public AstNode { // Convert format to string, generally under an AstDisplay or AstSFormat // Also used as "real" function for /*verilator sformat*/ functions + // @astgen op1 := exprsp : List[AstNode] + // @astgen op2 := scopeNamep : Optional[AstScopeName] string m_text; const bool m_hidden; // Under display, etc bool m_hasFormat; // Has format code @@ -1741,8 +1649,7 @@ public: , m_hasFormat{true} , m_missingArgChar{missingArgChar} { dtypeSetString(); - addNOp1p(exprsp); - addNOp2p(nullptr); + addExprsp(exprsp); } AstSFormatF(FileLine* fl, NoFormat, AstNode* exprsp, char missingArgChar = 'd', bool hidden = true) @@ -1752,8 +1659,7 @@ public: , m_hasFormat{false} , m_missingArgChar{missingArgChar} { dtypeSetString(); - addNOp1p(exprsp); - addNOp2p(nullptr); + addExprsp(exprsp); } ASTGEN_MEMBERS_SFormatF; string name() const override { return m_text; } @@ -1763,12 +1669,8 @@ public: return text() == static_cast(samep)->text(); } string verilogKwd() const override { return "$sformatf"; } - void addExprsp(AstNode* nodep) { addOp1p(nodep); } // op1 = Expressions to output - AstNode* exprsp() const { return op1p(); } // op1 = Expressions to output string text() const { return m_text; } // * = Text to display void text(const string& text) { m_text = text; } - AstScopeName* scopeNamep() const { return VN_AS(op2p(), ScopeName); } - void scopeNamep(AstNode* nodep) { setNOp2p(nodep); } bool formatScopeTracking() const { // Track scopeNamep(); Ok if false positive return (name().find("%m") != string::npos || name().find("%M") != string::npos); } @@ -1783,7 +1685,9 @@ class AstScope final : public AstNode { // A particular usage of a cell // Parents: MODULE // Children: NODEBLOCK -private: + // @astgen op1 := varsp : List[AstVarScope] + // @astgen op2 := blocksp : List[AstNode] // Logic blocks/AstActive/AstCFunc + // An AstScope->name() is special: . indicates an uninlined scope, __DOT__ an inlined scope string m_name; // Name AstScope* const m_aboveScopep; // Scope above this one in the hierarchy (nullptr if top) @@ -1807,12 +1711,6 @@ public: string nameDotless() const; string nameVlSym() const { return ((string("vlSymsp->")) + nameDotless()); } AstNodeModule* modp() const { return m_modp; } - // op1: AstVarScope's - AstVarScope* varsp() const { return VN_AS(op1p(), VarScope); } - void addVarp(AstVarScope* nodep) { addOp1p((AstNode*)nodep); } - // op2: Logic blocks/AstActive/AstExecGraph - AstNode* blocksp() const { return op2p(); } - void addActivep(AstNode* nodep) { addOp2p(nodep); } // AstScope* aboveScopep() const { return m_aboveScopep; } AstCell* aboveCellp() const { return m_aboveCellp; } @@ -1825,23 +1723,21 @@ public: class AstSelLoopVars final : public AstNode { // Parser only concept "[id, id, id]" for a foreach statement // Unlike normal selects elements is a list + // @astgen op1 := fromp : AstNode + // @astgen op2 := elementsp : List[AstNode] public: AstSelLoopVars(FileLine* fl, AstNode* fromp, AstNode* elementsp) : ASTGEN_SUPER_SelLoopVars(fl) { - setOp1p(fromp); - addNOp2p(elementsp); + this->fromp(fromp); + this->addElementsp(elementsp); } ASTGEN_MEMBERS_SelLoopVars; bool same(const AstNode* /*samep*/) const override { return true; } bool maybePointedTo() const override { return false; } - AstNode* fromp() const { return op1p(); } - void fromp(AstNode* nodep) { setOp1p(nodep); } - AstNode* elementsp() const { return op2p(); } }; class AstSenItem final : public AstNode { // Parents: SENTREE - // Children: (optional) VARREF -private: + // @astgen op1 := sensp : AstNode // Sensitivity expression VEdgeType m_edgeType; // Edge type public: class Combo {}; // for constructor type-overload selection @@ -1853,7 +1749,7 @@ public: AstSenItem(FileLine* fl, VEdgeType edgeType, AstNode* senp) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{edgeType} { - setOp1p(senp); + this->sensp(senp); } AstSenItem(FileLine* fl, Combo) : ASTGEN_SUPER_SenItem(fl) @@ -1883,10 +1779,7 @@ public: m_edgeType = type; editCountInc(); } - // op1 = Expression sensitized, if any - AstNode* sensp() const { return op1p(); } - void sensp(AstNode* const nodep) { setOp1p(nodep); } - AstNodeVarRef* varrefp() const { return VN_CAST(op1p(), NodeVarRef); } + AstNodeVarRef* varrefp() const { return VN_CAST(sensp(), NodeVarRef); } // bool isClocked() const { return edgeType().clockedStmt(); } bool isCombo() const { return edgeType() == VEdgeType::ET_COMBO; } @@ -1898,23 +1791,18 @@ public: bool isNever() const { return edgeType() == VEdgeType::ET_NEVER; } }; class AstSenTree final : public AstNode { - // A list of senitems - // Parents: MODULE | SBLOCK - // Children: SENITEM list -private: + // A sensitivity list + // @astgen op1 := sensesp : List[AstSenItem] bool m_multi = false; // Created from combo logic by ORing multiple clock domains public: AstSenTree(FileLine* fl, AstSenItem* sensesp) : ASTGEN_SUPER_SenTree(fl) { - addNOp1p(sensesp); + this->addSensesp(sensesp); } ASTGEN_MEMBERS_SenTree; void dump(std::ostream& str) const override; bool maybePointedTo() const override { return true; } bool isMulti() const { return m_multi; } - // op1 = Sensitivity list - AstSenItem* sensesp() const { return VN_AS(op1p(), SenItem); } - void addSensesp(AstSenItem* nodep) { addOp1p(nodep); } void multi(bool flag) { m_multi = true; } // METHODS bool hasClocked() const; // Includes a clocked statement @@ -1948,26 +1836,26 @@ public: void dump(std::ostream& str) const override; }; class AstTopScope final : public AstNode { - // A singleton, held under the top level AstModule. Holds the top level AstScope, - // and after V3ActiveTop, the global list of AstSenTrees (list of unique sensitivity lists). - // Parent: Top level AstModule - // Children: AstSenTree, AstScope + // A singleton, held under the top level AstModule. Holds the top level + // AstScope, and after V3ActiveTop, the global list of AstSenTrees (list of + // unique sensitivity lists). + // + // @astgen op1 := senTreesp : List[AstSenTree] // Globally unique sensitivity lists + // @astgen op2 := scopep : AstScope // The AstScope of the top-leveL + friend class AstNetlist; // Only the AstNetlist can create one AstTopScope(FileLine* fl, AstScope* ascopep) : ASTGEN_SUPER_TopScope(fl) { - addOp2p(ascopep); + this->scopep(ascopep); } public: ASTGEN_MEMBERS_TopScope; bool maybePointedTo() const override { return true; } - AstSenTree* senTreesp() const { return VN_AS(op1p(), SenTree); } - void addSenTreep(AstSenTree* nodep) { addOp1p((AstNode*)nodep); } - AstScope* scopep() const { return VN_AS(op2p(), Scope); } }; class AstTypeTable final : public AstNode { // Container for hash of standard data types - // Children: NODEDTYPEs + // @astgen op1 := typesp : List[AstNodeDType] AstEmptyQueueDType* m_emptyQueuep = nullptr; AstQueueDType* m_queueIndexp = nullptr; AstVoidDType* m_voidp = nullptr; @@ -1987,8 +1875,6 @@ public: return nullptr; } void cloneRelink() override { V3ERROR_NA; } - AstNodeDType* typesp() const { return VN_AS(op1p(), NodeDType); } // op1 = List of dtypes - void addTypesp(AstNodeDType* nodep) { addOp1p(nodep); } AstBasicDType* findBasicDType(FileLine* fl, VBasicDTypeKwd kwd); AstBasicDType* findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd, int width, int widthMin, VSigning numeric); @@ -2003,7 +1889,9 @@ public: void dump(std::ostream& str = std::cout) const override; }; class AstTypedef final : public AstNode { -private: + // @astgen op1 := childDTypep : Optional[AstNodeDType] + // @astgen op4 := attrsp : List[AstNode] // Attributes during early parse + string m_name; bool m_attrPublic = false; string m_tag; // Holds the string of the verilator tag -- used in XML output. @@ -2019,12 +1907,7 @@ public: ASTGEN_MEMBERS_Typedef; void dump(std::ostream& str) const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } - // op1 = Type assigning to - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); } - void addAttrsp(AstNode* nodep) { addNOp4p(nodep); } - AstNode* attrsp() const { return op4p(); } // op4 = Attributes during early parse // METHODS string name() const override { return m_name; } bool maybePointedTo() const override { return true; } @@ -2050,14 +1933,13 @@ public: bool maybePointedTo() const override { return true; } }; class AstUdpTable final : public AstNode { + // @astgen op1 := linesp : List[AstUdpTableLine] public: - AstUdpTable(FileLine* fl, AstNode* bodysp) + AstUdpTable(FileLine* fl, AstUdpTableLine* linesp) : ASTGEN_SUPER_UdpTable(fl) { - addNOp1p(bodysp); + this->addLinesp(linesp); } ASTGEN_MEMBERS_UdpTable; - // op1 = List of UdpTableLines - AstUdpTableLine* bodysp() const { return VN_AS(op1p(), UdpTableLine); } }; class AstUdpTableLine final : public AstNode { string m_text; @@ -2072,24 +1954,29 @@ public: }; class AstUnlinkedRef final : public AstNode { // As-of-yet unlinkable Ref -private: - string m_name; // Var name + // @astgen op1 := refp : AstNode + // @astgen op2 := cellrefp : AstNode + + string m_name; // Var name // TODO: There is no way to access this, fix or remove public: - AstUnlinkedRef(FileLine* fl, AstNode* refp, const string& name, AstNode* crp) + AstUnlinkedRef(FileLine* fl, AstNode* refp, const string& name, AstNode* cellrefp) : ASTGEN_SUPER_UnlinkedRef(fl) , m_name{name} { - addNOp1p(refp); - addNOp2p(crp); + this->refp(refp); + this->cellrefp(cellrefp); } ASTGEN_MEMBERS_UnlinkedRef; - // ACCESSORS - string name() const override { return m_name; } // * = Var name - AstNode* refp() const { return op1p(); } // op1 = VarXRef or AstNodeFTaskRef - AstNode* cellrefp() const { return op2p(); } // op2 = CellArrayRef or CellRef }; class AstVar final : public AstNode { // A variable (in/out/wire/reg/param) inside a module -private: + // + // @astgen op1 := childDTypep : Optional[AstNodeDType] + // @astgen op2 := delayp : Optional[AstNode] // Net delay + // Initial value that never changes (static const), or constructor argument for + // MTASKSTATE variables + // @astgen op3 := valuep : Optional[AstNode] + // @astgen op4 := attrsp : List[AstNode] // Attributes during early parse + string m_name; // Name of variable string m_origName; // Original name before dot addition string m_tag; // Holds the string of the verilator tag -- used in XML output. @@ -2282,23 +2169,10 @@ public: string vlPropDecl(const string& propName) const; // Return VerilatorVarProps declaration void combineType(VVarType type); AstNodeDType* getChildDTypep() const override { return childDTypep(); } - // op1 = Range of variable - AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } - // op2 = Net delay - AstNode* delayp() const { return op2p(); } - void delayp(AstNode* const nodep) { setNOp2p(nodep); } AstNodeDType* dtypeSkipRefp() const { return subDTypep()->skipRefp(); } // (Slow) recurse down to find basic data type (Note don't need virtual - // AstVar isn't a NodeDType) AstBasicDType* basicp() const { return subDTypep()->basicp(); } - // op3 = Initial value that never changes (static const), or constructor argument for - // MTASKSTATE variables - AstNode* valuep() const { return op3p(); } - // It's valuep(), not constp(), as may be more complicated than an AstConst - void valuep(AstNode* nodep) { setOp3p(nodep); } - void addAttrsp(AstNode* nodep) { addNOp4p(nodep); } - AstNode* attrsp() const { return op4p(); } // op4 = Attributes during early parse - void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); } void ansi(bool flag) { m_ansi = flag; } void declTyped(bool flag) { m_declTyped = flag; } @@ -2508,8 +2382,8 @@ public: class AstBegin final : public AstNodeBlock { // A Begin/end named block, only exists shortly after parsing until linking // Parents: statement - // Children: statements -private: + // @astgen op2 := genforp : Optional[AstNode] + bool m_generate; // Underneath a generate const bool m_implied; // Not inserted by user public: @@ -2521,10 +2395,6 @@ public: , m_implied{implied} {} ASTGEN_MEMBERS_Begin; void dump(std::ostream& str) const override; - // op1p is statements in NodeBlock - AstNode* genforp() const { return op2p(); } // op2 = GENFOR, if applicable, - // might NOT be a GenFor, as loop unrolling replaces with Begin - void addGenforp(AstGenFor* nodep) { addOp2p((AstNode*)nodep); } void generate(bool flag) { m_generate = flag; } bool generate() const { return m_generate; } bool implied() const { return m_implied; } @@ -2550,9 +2420,9 @@ public: class AstFunc final : public AstNodeFTask { // A function inside a module public: - AstFunc(FileLine* fl, const string& name, AstNode* stmtp, AstNode* fvarsp) + AstFunc(FileLine* fl, const string& name, AstNode* stmtp, AstNode* fvarp) : ASTGEN_SUPER_Func(fl, name, stmtp) { - addNOp1p(fvarsp); + this->fvarp(fvarp); } ASTGEN_MEMBERS_Func; bool hasDType() const override { return true; } @@ -2600,6 +2470,7 @@ public: // === AstNodeModule === class AstClass final : public AstNodeModule { + // @astgen op4 := extendsp : Optional[AstClassExtends] // TYPES using MemberNameMap = std::map; // MEMBERS @@ -2619,16 +2490,13 @@ public: const char* broken() const override; void cloneRelink() override; bool timescaleMatters() const override { return false; } - // op1/op2/op3 in AstNodeModule AstClassPackage* classOrPackagep() const { return m_classOrPackagep; } void classOrPackagep(AstClassPackage* classpackagep) { m_classOrPackagep = classpackagep; } - AstNode* membersp() const { return stmtsp(); } // op2 = List of statements + AstNode* membersp() const { return stmtsp(); } void addMembersp(AstNode* nodep) { insertCache(nodep); - addStmtp(nodep); + addStmtsp(nodep); } - AstClassExtends* extendsp() const { return VN_AS(op4p(), ClassExtends); } - void extendsp(AstNode* nodep) { addNOp4p(nodep); } void clearCache() { m_members.clear(); } void repairCache(); AstNode* findMember(const string& name) const { @@ -2756,27 +2624,27 @@ public: // === AstNodeProcedure === class AstAlways final : public AstNodeProcedure { + // @astgen op1 := sensesp : Optional[AstSenTree] // Sensitivity list iff clocked const VAlwaysKwd m_keyword; public: - AstAlways(FileLine* fl, VAlwaysKwd keyword, AstSenTree* sensesp, AstNode* bodysp) - : ASTGEN_SUPER_Always(fl, bodysp) + AstAlways(FileLine* fl, VAlwaysKwd keyword, AstSenTree* sensesp, AstNode* stmtsp) + : ASTGEN_SUPER_Always(fl, stmtsp) , m_keyword{keyword} { - addNOp1p(sensesp); + this->sensesp(sensesp); } ASTGEN_MEMBERS_Always; // void dump(std::ostream& str) const override; - AstSenTree* sensesp() const { return VN_AS(op1p(), SenTree); } // op1 = Sensitivity list - void sensesp(AstSenTree* nodep) { setOp1p(nodep); } VAlwaysKwd keyword() const { return m_keyword; } }; class AstAlwaysPost final : public AstNodeProcedure { // Like always but post assignments for memory assignment IFs + // @astgen op1 := sensesp : Optional[AstSenTree] // Sensitivity list iff clocked public: - AstAlwaysPost(FileLine* fl, AstSenTree* sensesp, AstNode* bodysp) - : ASTGEN_SUPER_AlwaysPost(fl, bodysp) { - addNOp1p(sensesp); + AstAlwaysPost(FileLine* fl, AstSenTree* sensesp, AstNode* stmtsp) + : ASTGEN_SUPER_AlwaysPost(fl, stmtsp) { + this->sensesp(sensesp); } ASTGEN_MEMBERS_AlwaysPost; }; @@ -2784,36 +2652,36 @@ class AstAlwaysPostponed final : public AstNodeProcedure { // Like always but postponement scheduling region public: - AstAlwaysPostponed(FileLine* fl, AstNode* bodysp) - : ASTGEN_SUPER_AlwaysPostponed(fl, bodysp) {} + AstAlwaysPostponed(FileLine* fl, AstNode* stmtsp) + : ASTGEN_SUPER_AlwaysPostponed(fl, stmtsp) {} ASTGEN_MEMBERS_AlwaysPostponed; }; class AstFinal final : public AstNodeProcedure { public: - AstFinal(FileLine* fl, AstNode* bodysp) - : ASTGEN_SUPER_Final(fl, bodysp) {} + AstFinal(FileLine* fl, AstNode* stmtsp) + : ASTGEN_SUPER_Final(fl, stmtsp) {} ASTGEN_MEMBERS_Final; }; class AstInitial final : public AstNodeProcedure { public: - AstInitial(FileLine* fl, AstNode* bodysp) - : ASTGEN_SUPER_Initial(fl, bodysp) {} + AstInitial(FileLine* fl, AstNode* stmtsp) + : ASTGEN_SUPER_Initial(fl, stmtsp) {} ASTGEN_MEMBERS_Initial; }; class AstInitialAutomatic final : public AstNodeProcedure { // Automatic variable initialization // That is, it runs every function start, or class construction public: - AstInitialAutomatic(FileLine* fl, AstNode* bodysp) - : ASTGEN_SUPER_InitialAutomatic(fl, bodysp) {} + AstInitialAutomatic(FileLine* fl, AstNode* stmtsp) + : ASTGEN_SUPER_InitialAutomatic(fl, stmtsp) {} ASTGEN_MEMBERS_InitialAutomatic; }; class AstInitialStatic final : public AstNodeProcedure { // Static variable initialization // That is, it runs at the beginning of simulation, before 'initial' blocks public: - AstInitialStatic(FileLine* fl, AstNode* bodysp) - : ASTGEN_SUPER_InitialStatic(fl, bodysp) {} + AstInitialStatic(FileLine* fl, AstNode* stmtsp) + : ASTGEN_SUPER_InitialStatic(fl, stmtsp) {} ASTGEN_MEMBERS_InitialStatic; }; @@ -2821,10 +2689,11 @@ public: class AstBracketRange final : public AstNodeRange { // Parser only concept "[lhsp]", a AstUnknownRange, QueueRange or Range, // unknown until lhsp type is determined + // @astgen op1 := elementsp : AstNode public: AstBracketRange(FileLine* fl, AstNode* elementsp) : ASTGEN_SUPER_BracketRange(fl) { - setOp1p(elementsp); + this->elementsp(elementsp); } ASTGEN_MEMBERS_BracketRange; virtual string emitC() { V3ERROR_NA_RETURN(""); } @@ -2833,21 +2702,20 @@ public: // Will be removed in V3Width, which relies on this // being a child not a dtype pointed node bool maybePointedTo() const override { return false; } - AstNode* elementsp() const { return op1p(); } }; class AstRange final : public AstNodeRange { // Range specification, for use under variables and cells + // @astgen op1 := leftp : AstNode + // @astgen op2 := rightp : AstNode public: AstRange(FileLine* fl, AstNode* leftp, AstNode* rightp) : ASTGEN_SUPER_Range(fl) { - setOp2p(leftp); - setOp3p(rightp); + this->leftp(leftp); + this->rightp(rightp); } inline AstRange(FileLine* fl, int left, int right); inline AstRange(FileLine* fl, const VNumRange& range); ASTGEN_MEMBERS_Range; - AstNode* leftp() const { return op2p(); } - AstNode* rightp() const { return op3p(); } inline int leftConst() const; inline int rightConst() const; int hiConst() const { @@ -2891,21 +2759,19 @@ public: class AstAlwaysPublic final : public AstNodeStmt { // "Fake" sensitivity created by /*verilator public_flat_rw @(edgelist)*/ // Body statements are just AstVarRefs to the public signals + // @astgen op1 := sensesp : List[AstSenTree] + // @astgen op2 := stmtsp : List[AstNode] public: - AstAlwaysPublic(FileLine* fl, AstSenTree* sensesp, AstNode* bodysp) + AstAlwaysPublic(FileLine* fl, AstSenTree* sensesp, AstNode* stmtsp) : ASTGEN_SUPER_AlwaysPublic(fl) { - addNOp1p(sensesp); - addNOp2p(bodysp); + addSensesp(sensesp); + addStmtsp(stmtsp); } ASTGEN_MEMBERS_AlwaysPublic; bool same(const AstNode* /*samep*/) const override { return true; } - // - AstSenTree* sensesp() const { return VN_AS(op1p(), SenTree); } // op1 = Sensitivity list - AstNode* bodysp() const { return op2p(); } // op2 = Statements to evaluate - void addStmtp(AstNode* nodep) { addOp2p(nodep); } // Special accessors - bool isJustOneBodyStmt() const { return bodysp() && !bodysp()->nextp(); } - bool isFirstInMyListOfStatements(AstNode* n) const override { return n == bodysp(); } + bool isJustOneBodyStmt() const { return stmtsp() && !stmtsp()->nextp(); } + bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); } }; class AstBreak final : public AstNodeStmt { public: @@ -2919,13 +2785,13 @@ public: }; class AstCAwait final : public AstNodeStmt { // Emit C++'s co_await statement - // Children: expression + // @astgen op1 := exprp : AstNode AstSenTree* m_sensesp; // Sentree related to this await public: - AstCAwait(FileLine* fl, AstNode* exprsp, AstSenTree* sensesp = nullptr) + AstCAwait(FileLine* fl, AstNode* exprp, AstSenTree* sensesp = nullptr) : ASTGEN_SUPER_CAwait(fl) , m_sensesp{sensesp} { - setNOp1p(exprsp); + this->exprp(exprp); } ASTGEN_MEMBERS_CAwait; bool isTimingControl() const override { return true; } @@ -2937,8 +2803,6 @@ public: if (m_sensesp && m_sensesp->clonep()) m_sensesp = m_sensesp->clonep(); } void dump(std::ostream& str) const override; - AstNode* exprp() const { return op1p(); } // op1 = awaited expression - void exprp(AstNode* const nodep) { setNOp1p(nodep); } AstSenTree* sensesp() const { return m_sensesp; } void clearSensesp() { m_sensesp = nullptr; } }; @@ -2946,6 +2810,8 @@ class AstCMethodHard final : public AstNodeStmt { // A reference to a "C" hardcoded member task (or function) // PARENTS: stmt/math // Not all calls are statments vs math. AstNodeStmt needs isStatement() to deal. + // @astgen op1 := fromp : AstNode // Subject of method call + // @astgen op2 := pinsp : List[AstNode] // Arguments private: string m_name; // Name of method bool m_pure = false; // Pure optimizable @@ -2954,15 +2820,16 @@ public: AstNode* pinsp = nullptr) : ASTGEN_SUPER_CMethodHard(fl, false) , m_name{name} { - setOp1p(fromp); + // TODO: this constructor is exactly the same as the other, bar the ignored tag argument + this->fromp(fromp); + this->addPinsp(pinsp); dtypep(nullptr); // V3Width will resolve - addNOp2p(pinsp); } AstCMethodHard(FileLine* fl, AstNode* fromp, const string& name, AstNode* pinsp = nullptr) : ASTGEN_SUPER_CMethodHard(fl, false) , m_name{name} { - setOp1p(fromp); - addNOp2p(pinsp); + this->fromp(fromp); + this->addPinsp(pinsp); } ASTGEN_MEMBERS_CMethodHard; string name() const override { return m_name; } // * = Var name @@ -2978,61 +2845,49 @@ public: statement(true); dtypeSetVoid(); } - AstNode* fromp() const { - return op1p(); - } // op1 = Extracting what (nullptr=TBD during parsing) - void fromp(AstNode* nodep) { setOp1p(nodep); } - AstNode* pinsp() const { return op2p(); } // op2 = Pin interconnection list - void addPinsp(AstNode* nodep) { addOp2p(nodep); } }; class AstCReset final : public AstNodeStmt { // Reset variable at startup + // @astgen op1 := varrefp : AstVarRef public: - AstCReset(FileLine* fl, AstVarRef* exprsp) + AstCReset(FileLine* fl, AstVarRef* varrefp) : ASTGEN_SUPER_CReset(fl) { - addNOp1p((AstNode*)exprsp); + this->varrefp(varrefp); } ASTGEN_MEMBERS_CReset; bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } bool same(const AstNode* /*samep*/) const override { return true; } - AstVarRef* varrefp() const { return VN_AS(op1p(), VarRef); } // op1 = varref to reset }; class AstCReturn final : public AstNodeStmt { // C++ return from a function - // Parents: CFUNC/statement - // Children: Math + // @astgen op1 := lhsp : AstNode public: AstCReturn(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_CReturn(fl) { - setOp1p(lhsp); + this->lhsp(lhsp); } ASTGEN_MEMBERS_CReturn; int instrCount() const override { return widthInstrs(); } bool same(const AstNode* /*samep*/) const override { return true; } - // - AstNode* lhsp() const { return op1p(); } }; class AstCStmt final : public AstNodeStmt { // Emit C statement + // @astgen op1 := exprsp : List[AstNode] public: AstCStmt(FileLine* fl, AstNode* exprsp) : ASTGEN_SUPER_CStmt(fl) { - addNOp1p(exprsp); + this->addExprsp(exprsp); } inline AstCStmt(FileLine* fl, const string& textStmt); ASTGEN_MEMBERS_CStmt; bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } bool same(const AstNode* /*samep*/) const override { return true; } - void addBodysp(AstNode* nodep) { addNOp1p(nodep); } - AstNode* bodysp() const { return op1p(); } // op1 = expressions to print }; class AstComment final : public AstNodeStmt { // Some comment to put into the output stream // Parents: {statement list} - // Children: none -private: const bool m_showAt; // Show "at " const string m_name; // Text of comment public: @@ -3143,13 +2998,15 @@ public: class AstCoverToggle final : public AstNodeStmt { // Toggle analysis of given signal // Parents: MODULE - // Children: AstCoverInc, orig var, change det var + // @astgen op1 := incp : AstCoverInc + // @astgen op2 := origp : AstNode + // @astgen op3 := changep : AstNode public: AstCoverToggle(FileLine* fl, AstCoverInc* incp, AstNode* origp, AstNode* changep) : ASTGEN_SUPER_CoverToggle(fl) { - setOp1p(incp); - setOp2p(origp); - setOp3p(changep); + this->incp(incp); + this->origp(origp); + this->changep(changep); } ASTGEN_MEMBERS_CoverToggle; int instrCount() const override { return 3 + INSTR_COUNT_BRANCH + INSTR_COUNT_LD; } @@ -3160,27 +3017,20 @@ public: return false; // Though the AstCoverInc under this is an outputter } // but isPure() true - AstCoverInc* incp() const { return VN_AS(op1p(), CoverInc); } - void incp(AstCoverInc* nodep) { setOp1p(nodep); } - AstNode* origp() const { return op2p(); } - AstNode* changep() const { return op3p(); } }; class AstDelay final : public AstNodeStmt { // Delay statement + // @astgen op1 := lhsp : AstNode // Delay value + // @astgen op2 := stmtsp : List[AstNode] // Statements under delay public: AstDelay(FileLine* fl, AstNode* lhsp, AstNode* stmtsp) : ASTGEN_SUPER_Delay(fl) { - setOp1p(lhsp); - setNOp2p(stmtsp); + this->lhsp(lhsp); + this->addStmtsp(stmtsp); } ASTGEN_MEMBERS_Delay; bool isTimingControl() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } - // - AstNode* lhsp() const { return op1p(); } // op1 = delay value - void lhsp(AstNode* nodep) { setOp1p(nodep); } - void stmtsp(AstNode* nodep) { setOp2p(nodep); } // op2 = statements under delay - AstNode* stmtsp() const { return op2p(); } }; class AstDisable final : public AstNodeStmt { private: @@ -3205,8 +3055,8 @@ public: }; class AstDisplay final : public AstNodeStmt { // Parents: stmtlist - // Children: file which must be a varref - // Children: SFORMATF to generate print string + // @astgen op1 := fmtp : AstSFormatF + // @astgen op2 := filep : Optional[AstNode] // file (must be a VarRef) private: VDisplayType m_displayType; @@ -3215,15 +3065,15 @@ public: AstNode* exprsp, char missingArgChar = 'd') : ASTGEN_SUPER_Display(fl) , m_displayType{dispType} { - setOp1p(new AstSFormatF{fl, text, true, exprsp, missingArgChar}); - setNOp3p(filep); + this->fmtp(new AstSFormatF{fl, text, true, exprsp, missingArgChar}); + this->filep(filep); } AstDisplay(FileLine* fl, VDisplayType dispType, AstNode* filep, AstNode* exprsp, char missingArgChar = 'd') : ASTGEN_SUPER_Display(fl) , m_displayType{dispType} { - setOp1p(new AstSFormatF{fl, AstSFormatF::NoFormat(), exprsp, missingArgChar}); - setNOp3p(filep); + this->fmtp(new AstSFormatF{fl, AstSFormatF::NoFormat(), exprsp, missingArgChar}); + this->filep(filep); } ASTGEN_MEMBERS_Display; void dump(std::ostream& str) const override; @@ -3248,21 +3098,17 @@ public: void displayType(VDisplayType type) { m_displayType = type; } // * = Add a newline for $display bool addNewline() const { return displayType().addNewline(); } - void fmtp(AstSFormatF* nodep) { addOp1p(nodep); } // op1 = To-String formatter - AstSFormatF* fmtp() const { return VN_AS(op1p(), SFormatF); } - AstNode* filep() const { return op3p(); } - void filep(AstNodeVarRef* nodep) { setNOp3p((AstNode*)nodep); } }; class AstDumpCtl final : public AstNodeStmt { // $dumpon etc // Parents: expr - // Child: expr based on type of control statement + // @astgen op1 := exprp : Optional[AstNode] // Expression based on type of control statement const VDumpCtlType m_ctlType; // Type of operation public: AstDumpCtl(FileLine* fl, VDumpCtlType ctlType, AstNode* exprp = nullptr) : ASTGEN_SUPER_DumpCtl(fl) , m_ctlType{ctlType} { - setNOp1p(exprp); + this->exprp(exprp); } ASTGEN_MEMBERS_DumpCtl; string verilogKwd() const override { return ctlType().ascii(); } @@ -3272,33 +3118,29 @@ public: virtual bool cleanOut() const { return true; } bool same(const AstNode* /*samep*/) const override { return true; } VDumpCtlType ctlType() const { return m_ctlType; } - AstNode* exprp() const { return op1p(); } // op2 = Expressions to output - void exprp(AstNode* nodep) { setOp1p(nodep); } }; class AstEventControl final : public AstNodeStmt { // Parents: stmtlist - // Children: sentree, stmtlist + // @astgen op1 := sensesp : Optional[AstSenTree] + // @astgen op2 := stmtsp : List[AstNode] public: AstEventControl(FileLine* fl, AstSenTree* sensesp, AstNode* stmtsp) : ASTGEN_SUPER_EventControl(fl) { - setNOp1p(sensesp); - addNOp2p(stmtsp); + this->sensesp(sensesp); + this->addStmtsp(stmtsp); } ASTGEN_MEMBERS_EventControl; string verilogKwd() const override { return "@(%l) %r"; } bool isTimingControl() const override { return true; } int instrCount() const override { return 0; } - AstSenTree* sensesp() const { return VN_AS(op1p(), SenTree); } - AstNode* stmtsp() const { return op2p(); } - void stmtsp(AstNode* stmtsp) { setNOp2p(stmtsp); } }; class AstFClose final : public AstNodeStmt { // Parents: stmtlist - // Children: file which must be a varref + // @astgen op1 := filep : AstNode // file (must be a VarRef) public: AstFClose(FileLine* fl, AstNode* filep) : ASTGEN_SUPER_FClose(fl) { - setNOp2p(filep); + this->filep(filep); } ASTGEN_MEMBERS_FClose; string verilogKwd() const override { return "$fclose"; } @@ -3308,16 +3150,14 @@ public: bool isOutputter() const override { return true; } bool isUnlikely() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* filep() const { return op2p(); } - void filep(AstNodeVarRef* nodep) { setNOp2p((AstNode*)nodep); } }; class AstFFlush final : public AstNodeStmt { // Parents: stmtlist - // Children: file which must be a varref + // @astgen op1 := filep : Optional[AstNode] // file (must be a VarRef) public: AstFFlush(FileLine* fl, AstNode* filep) : ASTGEN_SUPER_FFlush(fl) { - setNOp2p(filep); + this->filep(filep); } ASTGEN_MEMBERS_FFlush; string verilogKwd() const override { return "$fflush"; } @@ -3327,17 +3167,18 @@ public: bool isOutputter() const override { return true; } bool isUnlikely() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* filep() const { return op2p(); } - void filep(AstNodeVarRef* nodep) { setNOp2p((AstNode*)nodep); } }; class AstFOpen final : public AstNodeStmt { // Although a system function in IEEE, here a statement which sets the file pointer (MCD) + // @astgen op1 := filep : AstNode + // @astgen op2 := filenamep : AstNode + // @astgen op3 := modep : AstNode public: AstFOpen(FileLine* fl, AstNode* filep, AstNode* filenamep, AstNode* modep) : ASTGEN_SUPER_FOpen(fl) { - setOp1p(filep); - setOp2p(filenamep); - setOp3p(modep); + this->filep(filep); + this->filenamep(filenamep); + this->modep(modep); } ASTGEN_MEMBERS_FOpen; string verilogKwd() const override { return "$fopen"; } @@ -3347,17 +3188,16 @@ public: bool isOutputter() const override { return true; } bool isUnlikely() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* filep() const { return op1p(); } - AstNode* filenamep() const { return op2p(); } - AstNode* modep() const { return op3p(); } }; class AstFOpenMcd final : public AstNodeStmt { // Although a system function in IEEE, here a statement which sets the file pointer (MCD) + // @astgen op1 := filep : AstNode + // @astgen op2 := filenamep : AstNode public: AstFOpenMcd(FileLine* fl, AstNode* filep, AstNode* filenamep) : ASTGEN_SUPER_FOpenMcd(fl) { - setOp1p(filep); - setOp2p(filenamep); + this->filep(filep); + this->filenamep(filenamep); } ASTGEN_MEMBERS_FOpenMcd; string verilogKwd() const override { return "$fopen"; } @@ -3367,8 +3207,6 @@ public: bool isOutputter() const override { return true; } bool isUnlikely() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* filep() const { return op1p(); } - AstNode* filenamep() const { return op2p(); } }; class AstFinish final : public AstNodeStmt { public: @@ -3385,36 +3223,38 @@ public: }; class AstFireEvent final : public AstNodeStmt { // '-> _' and '->> _' event trigger statements - bool m_delayed; // Delayed (->>) vs non-delayed (->) + // @astgen op1 := operandp : AstNode + const bool m_delayed; // Delayed (->>) vs non-delayed (->) public: AstFireEvent(FileLine* fl, AstNode* operandp, bool delayed) : ASTGEN_SUPER_FireEvent(fl) , m_delayed{delayed} { - setOp1p(operandp); + this->operandp(operandp); } ASTGEN_MEMBERS_FireEvent; - AstNode* operandp() const { return op1p(); } bool isDelayed() const { return m_delayed; } }; class AstForeach final : public AstNodeStmt { + // @astgen op1 := arrayp : AstNode + // @astgen op2 := stmtsp : List[AstNode] public: - AstForeach(FileLine* fl, AstNode* arrayp, AstNode* bodysp) + AstForeach(FileLine* fl, AstNode* arrayp, AstNode* stmtsp) : ASTGEN_SUPER_Foreach(fl) { - setOp1p(arrayp); - addNOp4p(bodysp); + this->arrayp(arrayp); + this->addStmtsp(stmtsp); } ASTGEN_MEMBERS_Foreach; - AstNode* arrayp() const { return op1p(); } // op1 = array and index vars - AstNode* bodysp() const { return op4p(); } // op4 = body of loop bool isGateOptimizable() const override { return false; } int instrCount() const override { return INSTR_COUNT_BRANCH; } bool same(const AstNode* /*samep*/) const override { return true; } - bool isFirstInMyListOfStatements(AstNode* n) const override { return n == bodysp(); } + bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); } }; class AstJumpBlock final : public AstNodeStmt { // Block of code including a JumpGo and JumpLabel // Parents: {statement list} // Children: {statement list, with JumpGo and JumpLabel below} + // @astgen op1 := stmtsp : List[AstNode] + // @astgen op2 := endStmtsp : List[AstNode] private: AstJumpLabel* m_labelp = nullptr; // [After V3Jump] Pointer to declaration int m_labelNum = 0; // Set by V3EmitCSyms to tell final V3Emit what to increment @@ -3422,7 +3262,7 @@ public: // After construction must call ->labelp to associate with appropriate label AstJumpBlock(FileLine* fl, AstNode* stmtsp) : ASTGEN_SUPER_JumpBlock(fl) { - addNOp1p(stmtsp); + this->addStmtsp(stmtsp); } const char* broken() const override; void cloneRelink() override; @@ -3430,11 +3270,6 @@ public: int instrCount() const override { return 0; } bool maybePointedTo() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } - // op1 = Statements - AstNode* stmtsp() const { return op1p(); } // op1 = List of statements - void addStmtsp(AstNode* nodep) { addNOp1p(nodep); } - AstNode* endStmtsp() const { return op2p(); } // op1 = List of end-of-block - void addEndStmtsp(AstNode* nodep) { addNOp2p(nodep); } int labelNum() const { return m_labelNum; } void labelNum(int flag) { m_labelNum = flag; } AstJumpLabel* labelp() const { return m_labelp; } @@ -3533,57 +3368,57 @@ public: }; class AstRelease final : public AstNodeStmt { // Procedural 'release' statement + // @astgen op1 := lhsp : AstNode public: AstRelease(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_Release(fl) { - setOp1p(lhsp); + this->lhsp(lhsp); } ASTGEN_MEMBERS_Release; - AstNode* lhsp() const { return op1p(); } }; class AstRepeat final : public AstNodeStmt { + // @astgen op1 := countp : AstNode + // @astgen op2 := stmtsp : List[AstNode] public: - AstRepeat(FileLine* fl, AstNode* countp, AstNode* bodysp) + AstRepeat(FileLine* fl, AstNode* countp, AstNode* stmtsp) : ASTGEN_SUPER_Repeat(fl) { - setOp2p(countp); - addNOp3p(bodysp); + this->countp(countp); + this->addStmtsp(stmtsp); } ASTGEN_MEMBERS_Repeat; - AstNode* countp() const { return op2p(); } // op2 = condition to continue - AstNode* bodysp() const { return op3p(); } // op3 = body of loop bool isGateOptimizable() const override { return false; } // Not relevant - converted to FOR int instrCount() const override { return INSTR_COUNT_BRANCH; } bool same(const AstNode* /*samep*/) const override { return true; } - bool isFirstInMyListOfStatements(AstNode* n) const override { return n == bodysp(); } + bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); } }; class AstReturn final : public AstNodeStmt { + // @astgen op1 := lhsp : Optional[AstNode] public: explicit AstReturn(FileLine* fl, AstNode* lhsp = nullptr) : ASTGEN_SUPER_Return(fl) { - setNOp1p(lhsp); + this->lhsp(lhsp); } ASTGEN_MEMBERS_Return; string verilogKwd() const override { return "return"; } - AstNode* lhsp() const { return op1p(); } bool isBrancher() const override { return true; // SPECIAL: We don't process code after breaks } }; class AstSFormat final : public AstNodeStmt { // Parents: statement container - // Children: string to load - // Children: SFORMATF to generate print string + // @astgen op1 := fmtp : AstSFormatF + // @astgen op2 := lhsp : AstNode public: AstSFormat(FileLine* fl, AstNode* lhsp, const string& text, AstNode* exprsp, char missingArgChar = 'd') : ASTGEN_SUPER_SFormat(fl) { - setOp1p(new AstSFormatF(fl, text, true, exprsp, missingArgChar)); - setOp3p(lhsp); + this->fmtp(new AstSFormatF{fl, text, true, exprsp, missingArgChar}); + this->lhsp(lhsp); } AstSFormat(FileLine* fl, AstNode* lhsp, AstNode* exprsp, char missingArgChar = 'd') : ASTGEN_SUPER_SFormat(fl) { - setOp1p(new AstSFormatF(fl, AstSFormatF::NoFormat(), exprsp, missingArgChar)); - setOp3p(lhsp); + this->fmtp(new AstSFormatF{fl, AstSFormatF::NoFormat(), exprsp, missingArgChar}); + this->lhsp(lhsp); } ASTGEN_MEMBERS_SFormat; const char* broken() const override { @@ -3598,10 +3433,6 @@ public: virtual bool cleanOut() const { return false; } int instrCount() const override { return INSTR_COUNT_PLI; } bool same(const AstNode* /*samep*/) const override { return true; } - void fmtp(AstSFormatF* nodep) { addOp1p(nodep); } // op1 = To-String formatter - AstSFormatF* fmtp() const { return VN_AS(op1p(), SFormatF); } - AstNode* lhsp() const { return op3p(); } - void lhsp(AstNode* nodep) { setOp3p(nodep); } }; class AstStop final : public AstNodeStmt { public: @@ -3618,12 +3449,11 @@ public: }; class AstSysFuncAsTask final : public AstNodeStmt { // Call what is normally a system function (with a return) in a non-return context - // Parents: stmtlist - // Children: a system function + // @astgen op1 := lhsp : AstNode public: - AstSysFuncAsTask(FileLine* fl, AstNode* exprsp) + AstSysFuncAsTask(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_SysFuncAsTask(fl) { - addNOp1p(exprsp); + this->lhsp(lhsp); } ASTGEN_MEMBERS_SysFuncAsTask; string verilogKwd() const override { return ""; } @@ -3633,16 +3463,13 @@ public: bool isOutputter() const override { return false; } int instrCount() const override { return 0; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* lhsp() const { return op1p(); } // op1 = Expressions to eval - void lhsp(AstNode* nodep) { addOp1p(nodep); } // op1 = Expressions to eval }; class AstSysIgnore final : public AstNodeStmt { - // Parents: stmtlist - // Children: varrefs or exprs + // @astgen op1 := exprsp : List[AstNode] // Expressions to output (???) public: AstSysIgnore(FileLine* fl, AstNode* exprsp) : ASTGEN_SUPER_SysIgnore(fl) { - addNOp1p(exprsp); + this->addExprsp(exprsp); } ASTGEN_MEMBERS_SysIgnore; string verilogKwd() const override { return "$ignored"; } @@ -3651,15 +3478,14 @@ public: bool isPure() const override { return false; } // Though deleted before opt bool isOutputter() const override { return true; } // Though deleted before opt int instrCount() const override { return INSTR_COUNT_PLI; } - AstNode* exprsp() const { return op1p(); } // op1 = Expressions to output - void exprsp(AstNode* nodep) { addOp1p(nodep); } // op1 = Expressions to output }; class AstSystemT final : public AstNodeStmt { // $system used as task + // @astgen op1 := lhsp : AstNode public: AstSystemT(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_SystemT(fl) { - setOp1p(lhsp); + this->lhsp(lhsp); } ASTGEN_MEMBERS_SystemT; string verilogKwd() const override { return "$system"; } @@ -3669,18 +3495,21 @@ public: bool isOutputter() const override { return true; } bool isUnlikely() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } - AstNode* lhsp() const { return op1p(); } }; class AstTimeFormat final : public AstNodeStmt { // Parents: stmtlist + // @astgen op1 := unitsp : AstNode + // @astgen op2 := precisionp : AstNode + // @astgen op3 := suffixp : AstNode + // @astgen op4 := widthp : AstNode public: AstTimeFormat(FileLine* fl, AstNode* unitsp, AstNode* precisionp, AstNode* suffixp, AstNode* widthp) : ASTGEN_SUPER_TimeFormat(fl) { - setOp1p(unitsp); - setOp2p(precisionp); - setOp3p(suffixp); - setOp4p(widthp); + this->unitsp(unitsp); + this->precisionp(precisionp); + this->suffixp(suffixp); + this->widthp(widthp); } ASTGEN_MEMBERS_TimeFormat; string verilogKwd() const override { return "$timeformat"; } @@ -3689,16 +3518,12 @@ public: bool isPure() const override { return false; } bool isOutputter() const override { return true; } int instrCount() const override { return INSTR_COUNT_PLI; } - AstNode* unitsp() const { return op1p(); } - AstNode* precisionp() const { return op2p(); } - AstNode* suffixp() const { return op3p(); } - AstNode* widthp() const { return op4p(); } }; class AstTraceDecl final : public AstNodeStmt { // Trace point declaration // Separate from AstTraceInc; as a declaration can't be deleted // Parents: {statement list} - // Children: expression being traced + // @astgen op1 := valuep : AstNode // Expressio being traced private: uint32_t m_code = 0; // Trace identifier code; converted to ASCII by trace routines const string m_showname; // Name of variable @@ -3723,7 +3548,7 @@ public: , m_declKwd{varp->declKwd()} , m_declDirection{varp->declDirection()} { dtypeFrom(valuep); - addNOp1p(valuep); + this->valuep(valuep); } void dump(std::ostream& str) const override; int instrCount() const override { return 100; } // Large... @@ -3742,13 +3567,11 @@ public: VVarType varType() const { return m_varType; } VBasicDTypeKwd declKwd() const { return m_declKwd; } VDirection declDirection() const { return m_declDirection; } - AstNode* valuep() const { return op1p(); } }; class AstTraceInc final : public AstNodeStmt { // Trace point dump - // Parents: {statement list} - // Children: op1: things to emit before this node, - // op2: expression being traced (from decl) + // @astgen op1 := precondsp : List[AstNode] // Statements to emit before this node + // @astgen op2 := valuep : AstNode // Expression being traced (from decl) private: AstTraceDecl* m_declp; // Pointer to declaration @@ -3762,7 +3585,8 @@ public: , m_full{full} , m_baseCode{baseCode} { dtypeFrom(declp); - addOp2p(declp->valuep()->cloneTree(true)); + this->valuep( + declp->valuep()->cloneTree(true)); // TODO: maybe use reference to TraceDecl instead? } ASTGEN_MEMBERS_TraceInc; const char* broken() const override { @@ -3782,10 +3606,6 @@ public: bool isPredictOptimizable() const override { return false; } bool isOutputter() const override { return true; } // but isPure() true - // op1 = Statements before the value - AstNode* precondsp() const { return op1p(); } - void addPrecondsp(AstNode* newp) { addOp1p(newp); } - AstNode* valuep() const { return op2p(); } AstTraceDecl* declp() const { return m_declp; } bool full() const { return m_full; } uint32_t baseCode() const { return m_baseCode; } @@ -3812,13 +3632,13 @@ public: }; class AstUCStmt final : public AstNodeStmt { // User $c statement + // @astgen op1 := exprsp : List[AstNode] public: AstUCStmt(FileLine* fl, AstNode* exprsp) : ASTGEN_SUPER_UCStmt(fl) { - addNOp1p(exprsp); + this->addExprsp(exprsp); } ASTGEN_MEMBERS_UCStmt; - AstNode* bodysp() const { return op1p(); } // op1 = expressions to print bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } bool isPure() const override { return false; } @@ -3826,16 +3646,16 @@ public: bool same(const AstNode* /*samep*/) const override { return true; } }; class AstWait final : public AstNodeStmt { + // @astgen op1 := condp : AstNode + // @astgen op2 := stmtsp : List[AstNode] public: - AstWait(FileLine* fl, AstNode* condp, AstNode* bodysp) + AstWait(FileLine* fl, AstNode* condp, AstNode* stmtsp) : ASTGEN_SUPER_Wait(fl) { - setOp2p(condp); - addNOp3p(bodysp); + this->condp(condp); + this->addStmtsp(stmtsp); } ASTGEN_MEMBERS_Wait; - AstNode* condp() const { return op2p(); } // op2 = condition - AstNode* bodysp() const { return op3p(); } // op3 = statements after wait - bool isFirstInMyListOfStatements(AstNode* n) const override { return n == bodysp(); } + bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); } bool isTimingControl() const override { return true; } }; class AstWaitFork final : public AstNodeStmt { @@ -3846,22 +3666,18 @@ public: ASTGEN_MEMBERS_WaitFork; }; class AstWhile final : public AstNodeStmt { + // @astgen op1 := precondsp : List[AstNode] + // @astgen op2 := condp : AstNode + // @astgen op3 := stmtsp : List[AstNode] + // @astgen op4 := incsp : List[AstNode] public: - AstWhile(FileLine* fl, AstNode* condp, AstNode* bodysp = nullptr, AstNode* incsp = nullptr) + AstWhile(FileLine* fl, AstNode* condp, AstNode* stmtsp = nullptr, AstNode* incsp = nullptr) : ASTGEN_SUPER_While(fl) { - setOp2p(condp); - addNOp3p(bodysp); - addNOp4p(incsp); + this->condp(condp); + this->addStmtsp(stmtsp); + this->addIncsp(incsp); } ASTGEN_MEMBERS_While; - // op1 = prepare statements for condition (exec every loop) - AstNode* precondsp() const { return op1p(); } - AstNode* condp() const { return op2p(); } // op2 = condition to continue - AstNode* bodysp() const { return op3p(); } // op3 = body of loop - AstNode* incsp() const { return op4p(); } // op4 = increment (if from a FOR loop) - void addPrecondsp(AstNode* newp) { addOp1p(newp); } - void addBodysp(AstNode* newp) { addOp3p(newp); } - void addIncsp(AstNode* newp) { addOp4p(newp); } bool isGateOptimizable() const override { return false; } int instrCount() const override { return INSTR_COUNT_BRANCH; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -3869,7 +3685,7 @@ public: void addBeforeStmt(AstNode* newp, AstNode* belowp) override; // Stop statement searchback here void addNextStmt(AstNode* newp, AstNode* belowp) override; - bool isFirstInMyListOfStatements(AstNode* n) const override { return n == bodysp(); } + bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); } }; class AstWith final : public AstNodeStmt { // Used as argument to method, then to AstCMethodHard @@ -3878,13 +3694,16 @@ class AstWith final : public AstNodeStmt { // Children: LambdaArgRef that declares the item variable // Children: LambdaArgRef that declares the item.index variable // Children: math (equation establishing the with) + // @astgen op1 := indexArgRefp : AstLambdaArgRef + // @astgen op2 := valueArgRefp : AstLambdaArgRef + // @astgen op3 := exprp : AstNode public: AstWith(FileLine* fl, AstLambdaArgRef* indexArgRefp, AstLambdaArgRef* valueArgRefp, AstNode* exprp) : ASTGEN_SUPER_With(fl) { - addOp1p((AstNode*)indexArgRefp); - addOp2p((AstNode*)valueArgRefp); - addNOp3p(exprp); + this->indexArgRefp(indexArgRefp); + this->valueArgRefp(valueArgRefp); + this->exprp(exprp); } ASTGEN_MEMBERS_With; bool same(const AstNode* /*samep*/) const override { return true; } @@ -3894,28 +3713,23 @@ public: BROKEN_RTN(!valueArgRefp()); // varp needed to know lambda's arg dtype return nullptr; } - // - AstLambdaArgRef* indexArgRefp() const { return VN_AS(op1p(), LambdaArgRef); } - AstLambdaArgRef* valueArgRefp() const { return VN_AS(op2p(), LambdaArgRef); } - AstNode* exprp() const { return op3p(); } }; class AstWithParse final : public AstNodeStmt { // In early parse, FUNC(index) WITH equation-using-index // Replaced with AstWith // Parents: math|stmt // Children: funcref, math + // @astgen op1 := funcrefp : AstNode + // @astgen op2 := exprp : Optional[AstNode] public: AstWithParse(FileLine* fl, bool stmt, AstNode* funcrefp, AstNode* exprp) : ASTGEN_SUPER_WithParse(fl) { statement(stmt); - setOp1p(funcrefp); - addNOp2p(exprp); + this->funcrefp(funcrefp); + this->exprp(exprp); } ASTGEN_MEMBERS_WithParse; bool same(const AstNode* /*samep*/) const override { return true; } - // - AstNode* funcrefp() const { return op1p(); } - AstNode* exprp() const { return op2p(); } }; // === AstNodeAssign === @@ -4003,12 +3817,11 @@ public: }; class AstAssignW final : public AstNodeAssign { // Like assign, but wire/assign's in verilog, the only setting of the specified variable + // @astgen op4 := strengthSpecp : Optional[AstStrengthSpec] public: AstAssignW(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* timingControlp = nullptr) : ASTGEN_SUPER_AssignW(fl, lhsp, rhsp, timingControlp) {} ASTGEN_MEMBERS_AssignW; - AstStrengthSpec* strengthSpecp() const { return VN_AS(op4p(), StrengthSpec); } - void strengthSpecp(AstStrengthSpec* const strengthSpecp) { setOp4p((AstNode*)strengthSpecp); } AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; return new AstAssignW{fileline(), lhsp, rhsp, controlp}; @@ -4037,11 +3850,11 @@ public: class AstCMethodCall final : public AstNodeCCall { // C++ method call // Parents: Anything above a statement - // Children: Args to the function + // @astgen op1 := fromp : AstNode public: AstCMethodCall(FileLine* fl, AstNode* fromp, AstCFunc* funcp, AstNode* argsp = nullptr) : ASTGEN_SUPER_CMethodCall(fl, funcp, argsp) { - setOp1p(fromp); + this->fromp(fromp); } ASTGEN_MEMBERS_CMethodCall; const char* broken() const override { @@ -4049,10 +3862,6 @@ public: BROKEN_RTN(!fromp()); return nullptr; } - AstNode* fromp() const { - return op1p(); - } // op1 = Extracting what (nullptr=TBD during parsing) - void fromp(AstNode* nodep) { setOp1p(nodep); } }; class AstCNew final : public AstNodeCCall { // C++ new() call @@ -4071,8 +3880,6 @@ public: class AstCase final : public AstNodeCase { // Case statement // Parents: {statement list} - // exprp Children: MATHs - // casesp Children: CASEITEMs private: VCaseType m_casex; // 0=case, 1=casex, 2=casez bool m_fullPragma = false; // Synthesis full_case @@ -4081,8 +3888,8 @@ private: bool m_unique0Pragma = false; // unique0 case bool m_priorityPragma = false; // priority case public: - AstCase(FileLine* fl, VCaseType casex, AstNode* exprp, AstNode* casesp) - : ASTGEN_SUPER_Case(fl, exprp, casesp) + AstCase(FileLine* fl, VCaseType casex, AstNode* exprp, AstCaseItem* itemsp) + : ASTGEN_SUPER_Case(fl, exprp, itemsp) , m_casex{casex} {} ASTGEN_MEMBERS_Case; string verilogKwd() const override { return casez() ? "casez" : casex() ? "casex" : "case"; } @@ -4108,44 +3915,41 @@ public: class AstGenCase final : public AstNodeCase { // Generate Case statement // Parents: {statement list} - // exprp Children: MATHs - // casesp Children: CASEITEMs public: - AstGenCase(FileLine* fl, AstNode* exprp, AstNode* casesp) - : ASTGEN_SUPER_GenCase(fl, exprp, casesp) {} + AstGenCase(FileLine* fl, AstNode* exprp, AstCaseItem* itemsp) + : ASTGEN_SUPER_GenCase(fl, exprp, itemsp) {} ASTGEN_MEMBERS_GenCase; }; // === AstNodeCoverOrAssert === class AstAssert final : public AstNodeCoverOrAssert { + // @astgen op3 := failsp: List[AstNode] // Statments when propp is failing/falsey public: ASTGEN_MEMBERS_Assert; AstAssert(FileLine* fl, AstNode* propp, AstNode* passsp, AstNode* failsp, bool immediate, const string& name = "") : ASTGEN_SUPER_Assert(fl, propp, passsp, immediate, name) { - addNOp3p(failsp); + this->addFailsp(failsp); } - AstNode* failsp() const { return op3p(); } // op3 = if assertion fails }; class AstAssertIntrinsic final : public AstNodeCoverOrAssert { // A $cast or other compiler inserted assert, that must run even without --assert option + // @astgen op3 := failsp: List[AstNode] // Statments when propp is failing/falsey public: ASTGEN_MEMBERS_AssertIntrinsic; AstAssertIntrinsic(FileLine* fl, AstNode* propp, AstNode* passsp, AstNode* failsp, bool immediate, const string& name = "") : ASTGEN_SUPER_AssertIntrinsic(fl, propp, passsp, immediate, name) { - addNOp3p(failsp); + this->addFailsp(failsp); } - AstNode* failsp() const { return op3p(); } // op3 = if assertion fails }; class AstCover final : public AstNodeCoverOrAssert { + // @astgen op3 := coverincsp: List[AstNode] // Coverage node public: ASTGEN_MEMBERS_Cover; AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, bool immediate, const string& name = "") : ASTGEN_SUPER_Cover(fl, propp, stmtsp, immediate, name) {} - AstNode* coverincp() const { return op3p(); } // op3 = coverage node - void coverincp(AstCoverInc* nodep) { addOp3p(nodep); } // op3 = coverage node virtual bool immediate() const { return false; } }; class AstRestrict final : public AstNodeCoverOrAssert { @@ -4171,16 +3975,18 @@ class AstMethodCall final : public AstNodeFTaskRef { // PARENTS: stmt/math // Not all calls are statments vs math. AstNodeStmt needs isStatement() to deal. // Don't need the class we are extracting from, as the "fromp()"'s datatype can get us to it + // @astgen op2 := fromp : AstNode + // public: AstMethodCall(FileLine* fl, AstNode* fromp, VFlagChildDType, const string& name, AstNode* pinsp) : ASTGEN_SUPER_MethodCall(fl, false, name, pinsp) { - setOp2p(fromp); + this->fromp(fromp); dtypep(nullptr); // V3Width will resolve } AstMethodCall(FileLine* fl, AstNode* fromp, const string& name, AstNode* pinsp) : ASTGEN_SUPER_MethodCall(fl, false, name, pinsp) { - setOp2p(fromp); + this->fromp(fromp); } ASTGEN_MEMBERS_MethodCall; const char* broken() const override { @@ -4194,10 +4000,6 @@ public: statement(true); dtypeSetVoid(); } - AstNode* fromp() const { - return op2p(); - } // op2 = Extracting what (nullptr=TBD during parsing) - void fromp(AstNode* nodep) { setOp2p(nodep); } }; class AstNew final : public AstNodeFTaskRef { // New as constructor @@ -4228,16 +4030,16 @@ public: // === AstNodeFor === class AstGenFor final : public AstNodeFor { public: - AstGenFor(FileLine* fl, AstNode* initsp, AstNode* condp, AstNode* incsp, AstNode* bodysp) - : ASTGEN_SUPER_GenFor(fl, initsp, condp, incsp, bodysp) {} + AstGenFor(FileLine* fl, AstNode* initsp, AstNode* condp, AstNode* incsp, AstNode* stmtsp) + : ASTGEN_SUPER_GenFor(fl, initsp, condp, incsp, stmtsp) {} ASTGEN_MEMBERS_GenFor; }; // === AstNodeIf === class AstGenIf final : public AstNodeIf { public: - AstGenIf(FileLine* fl, AstNode* condp, AstNode* ifsp, AstNode* elsesp) - : ASTGEN_SUPER_GenIf(fl, condp, ifsp, elsesp) {} + AstGenIf(FileLine* fl, AstNode* condp, AstNode* thensp, AstNode* elsesp) + : ASTGEN_SUPER_GenIf(fl, condp, thensp, elsesp) {} ASTGEN_MEMBERS_GenIf; }; class AstIf final : public AstNodeIf { @@ -4246,8 +4048,8 @@ private: bool m_unique0Pragma = false; // unique0 case bool m_priorityPragma = false; // priority case public: - AstIf(FileLine* fl, AstNode* condp, AstNode* ifsp = nullptr, AstNode* elsesp = nullptr) - : ASTGEN_SUPER_If(fl, condp, ifsp, elsesp) {} + AstIf(FileLine* fl, AstNode* condp, AstNode* thensp = nullptr, AstNode* elsesp = nullptr) + : ASTGEN_SUPER_If(fl, condp, thensp, elsesp) {} ASTGEN_MEMBERS_If; bool uniquePragma() const { return m_uniquePragma; } void uniquePragma(bool flag) { m_uniquePragma = flag; } @@ -4335,7 +4137,7 @@ public: ASTGEN_MEMBERS_Text; }; class AstTextBlock final : public AstNodeSimpleText { -private: + // @astgen op1 := nodesp : List[AstNode] bool m_commas; // Comma separate emitted children public: explicit AstTextBlock(FileLine* fl, const string& textp = "", bool tracking = false, @@ -4345,10 +4147,8 @@ public: ASTGEN_MEMBERS_TextBlock; void commas(bool flag) { m_commas = flag; } bool commas() const { return m_commas; } - AstNode* nodesp() const { return op1p(); } - void addNodep(AstNode* nodep) { addOp1p(nodep); } void addText(FileLine* fl, const string& textp, bool tracking = false) { - addNodep(new AstText(fl, textp, tracking)); + addNodesp(new AstText(fl, textp, tracking)); } }; diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index cb3ccca4e..2b149d3d0 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -188,8 +188,8 @@ void AstBasicDType::init(VBasicDTypeKwd kwd, VSigning numer, int wantwidth, int widthForce(rangep->elementsConst(), rangep->elementsConst()); // Maybe unknown if parameters underneath it } - setNOp1p(rangep); - dtypep(this); + this->rangep(rangep); + this->dtypep(this); } void AstBasicDType::cvtRangeConst() { @@ -660,25 +660,15 @@ AstVar* AstVar::scVarRecurse(AstNode* nodep) { } else { return nullptr; } - } else if (VN_IS(nodep, VarRef)) { - if (VN_AS(nodep, VarRef)->varp()->isSc()) { - return VN_AS(nodep, VarRef)->varp(); + } else if (AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) { + if (vrefp->varp()->isSc()) { + return vrefp->varp(); } else { return nullptr; } - } else if (VN_IS(nodep, ArraySel)) { - if (nodep->op1p()) { - if (AstVar* p = scVarRecurse(nodep->op1p())) return p; - } - if (nodep->op2p()) { - if (AstVar* p = scVarRecurse(nodep->op2p())) return p; - } - if (nodep->op3p()) { - if (AstVar* p = scVarRecurse(nodep->op3p())) return p; - } - if (nodep->op4p()) { - if (AstVar* p = scVarRecurse(nodep->op4p())) return p; - } + } else if (AstArraySel* const arraySelp = VN_CAST(nodep, ArraySel)) { + if (AstVar* const p = scVarRecurse(arraySelp->fromp())) return p; + if (AstVar* const p = scVarRecurse(arraySelp->bitp())) return p; } return nullptr; } @@ -918,18 +908,18 @@ AstVarScope* AstScope::createTemp(const string& name, unsigned width) { FileLine* const flp = fileline(); AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, VFlagBitPacked{}, static_cast(width)}; - modp()->addStmtp(varp); + modp()->addStmtsp(varp); AstVarScope* const vscp = new AstVarScope{flp, this, varp}; - addVarp(vscp); + addVarsp(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); + modp()->addStmtsp(varp); AstVarScope* const vscp = new AstVarScope{flp, this, varp}; - addVarp(vscp); + addVarsp(vscp); return vscp; } @@ -1117,8 +1107,8 @@ AstConstPool::AstConstPool(FileLine* fl) : ASTGEN_SUPER_ConstPool(fl) , m_modp{new AstModule(fl, "@CONST-POOL@")} , m_scopep{new AstScope(fl, m_modp, "@CONST-POOL@", nullptr, nullptr)} { - addOp1p(m_modp); - m_modp->addStmtp(m_scopep); + this->modulep(m_modp); + m_modp->addStmtsp(m_scopep); } const char* AstConstPool::broken() const { BROKEN_RTN(m_modp && !m_modp->brokeExists()); @@ -1132,9 +1122,9 @@ AstVarScope* AstConstPool::createNewEntry(const string& name, AstNode* initp) { varp->isConst(true); varp->isStatic(true); varp->valuep(initp->cloneTree(false)); - m_modp->addStmtp(varp); + m_modp->addStmtsp(varp); AstVarScope* const varScopep = new AstVarScope(fl, m_scopep, varp); - m_scopep->addVarp(varScopep); + m_scopep->addVarsp(varScopep); return varScopep; } @@ -1280,7 +1270,7 @@ void AstWhile::addBeforeStmt(AstNode* newp, AstNode* belowp) { } else if (belowp == condp()) { // Goes before condition, IE in preconditions addPrecondsp(newp); - } else if (belowp == bodysp()) { + } else if (belowp == stmtsp()) { // Was first statement in body, so new front belowp->addHereThisAsNext(newp); } else { @@ -1297,12 +1287,12 @@ void AstWhile::addNextStmt(AstNode* newp, AstNode* belowp) { belowp->addNextHere(newp); } else if (belowp == condp()) { // Becomes first statement in body, body may have been empty - if (bodysp()) { - bodysp()->addHereThisAsNext(newp); + if (stmtsp()) { + stmtsp()->addHereThisAsNext(newp); } else { - addBodysp(newp); + addStmtsp(newp); } - } else if (belowp == bodysp()) { + } else if (belowp == stmtsp()) { // Next statement in body belowp->addNextHere(newp); } else { @@ -1571,19 +1561,15 @@ void AstInitArray::cloneRelink() { if (it->second->clonep()) it->second = it->second->clonep(); } } -AstNode* AstInitArray::addIndexValuep(uint64_t index, AstNode* newp) { - // Returns old value, caller must garbage collect - AstNode* oldp = nullptr; +void AstInitArray::addIndexValuep(uint64_t index, AstNode* newp) { const auto it = m_map.find(index); if (it != m_map.end()) { - oldp = it->second->valuep(); it->second->valuep(newp); } else { AstInitItem* const itemp = new AstInitItem(fileline(), newp); m_map.emplace(index, itemp); - addOp2p(itemp); + addInitsp(itemp); } - return oldp; } AstNode* AstInitArray::getIndexValuep(uint64_t index) const { const auto it = m_map.find(index); @@ -1859,7 +1845,7 @@ AstPackage* AstNetlist::dollarUnitPkgAddp() { m_dollarUnitPkgp->inLibrary(true); m_dollarUnitPkgp->modTrace(false); // may reconsider later m_dollarUnitPkgp->internal(true); - addModulep(m_dollarUnitPkgp); + addModulesp(m_dollarUnitPkgp); } return m_dollarUnitPkgp; } @@ -1867,7 +1853,7 @@ void AstNetlist::createTopScope(AstScope* scopep) { UASSERT(scopep, "Must not be nullptr"); UASSERT_OBJ(!m_topScopep, scopep, "TopScope already exits"); m_topScopep = new AstTopScope{scopep->modp()->fileline(), scopep}; - scopep->modp()->addStmtp(v3Global.rootp()->topScopep()); + scopep->modp()->addStmtsp(v3Global.rootp()->topScopep()); } void AstNodeModule::dump(std::ostream& str) const { this->AstNode::dump(str); diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp index 9401c577c..ffa5d336c 100644 --- a/src/V3Begin.cpp +++ b/src/V3Begin.cpp @@ -116,7 +116,7 @@ private: } } else { // Move to module - m_modp->addStmtp(nodep); + m_modp->addStmtsp(nodep); } } @@ -229,7 +229,7 @@ private: UINFO(8, " rename to " << nodep->name() << endl); // Move to module nodep->unlinkFrBack(); - m_modp->addStmtp(nodep); + m_modp->addStmtsp(nodep); } iterateChildren(nodep); } @@ -248,10 +248,10 @@ private: const string scname = nodep->forFormat() ? m_displayScope : m_namedScope; if (!scname.empty()) { // To keep correct visual order, must add before other Text's - AstNode* const afterp = nodep->scopeAttrp(); + AstText* const afterp = nodep->scopeAttrp(); if (afterp) afterp->unlinkFrBackWithNext(); - nodep->scopeAttrp(new AstText{nodep->fileline(), string("__DOT__") + scname}); - if (afterp) nodep->scopeAttrp(afterp); + nodep->addScopeAttrp(new AstText{nodep->fileline(), string("__DOT__") + scname}); + if (afterp) nodep->addScopeAttrp(afterp); } iterateChildren(nodep); } diff --git a/src/V3Branch.cpp b/src/V3Branch.cpp index eed4e49fc..8cd525687 100644 --- a/src/V3Branch.cpp +++ b/src/V3Branch.cpp @@ -70,7 +70,7 @@ private: { // Do if reset(); - iterateAndNextNull(nodep->ifsp()); + iterateAndNextNull(nodep->thensp()); const int ifLikely = m_likely; const int ifUnlikely = m_unlikely; // Do else diff --git a/src/V3Broken.cpp b/src/V3Broken.cpp index dd4978eca..2be4316d6 100644 --- a/src/V3Broken.cpp +++ b/src/V3Broken.cpp @@ -275,9 +275,9 @@ private: pushLocalScope(); processEnter(nodep); processAndIterate(nodep->condp()); - if (AstNode* const ifsp = nodep->ifsp()) { + if (AstNode* const thensp = nodep->thensp()) { pushLocalScope(); - processAndIterateList(ifsp); + processAndIterateList(thensp); popLocalScope(); } if (AstNode* const elsesp = nodep->elsesp()) { diff --git a/src/V3CCtors.cpp b/src/V3CCtors.cpp index 27c884947..62156ca31 100644 --- a/src/V3CCtors.cpp +++ b/src/V3CCtors.cpp @@ -77,7 +77,7 @@ private: if (!preventUnusedStmt.empty()) { funcp->addStmtsp(new AstCStmt{m_modp->fileline(), preventUnusedStmt}); } - m_modp->addStmtp(funcp); + m_modp->addStmtsp(funcp); m_numStmts = 0; return funcp; } @@ -136,7 +136,7 @@ void V3CCtors::evalAsserts() { funcp->isLoose(true); funcp->slow(false); funcp->ifdef("VL_DEBUG"); - modp->addStmtp(funcp); + modp->addStmtsp(funcp); for (AstNode* np = modp->stmtsp(); np; np = np->nextp()) { if (AstVar* const varp = VN_CAST(np, Var)) { if (varp->isPrimaryInish() && !varp->isSc()) { @@ -211,7 +211,7 @@ void V3CCtors::cctorsAll() { // If can be referred to by base pointer, need virtual delete funcp->isVirtual(classp->isExtended()); funcp->slow(false); - modp->addStmtp(funcp); + modp->addStmtsp(funcp); } } } diff --git a/src/V3CUse.cpp b/src/V3CUse.cpp index 5bf6bfe69..adc33fccd 100644 --- a/src/V3CUse.cpp +++ b/src/V3CUse.cpp @@ -51,7 +51,7 @@ class CUseVisitor final : public VNVisitor { void addNewUse(AstNode* nodep, VUseType useType, const string& name) { if (m_didUse.emplace(useType, name).second) { AstCUse* const newp = new AstCUse{nodep->fileline(), useType, name}; - m_modp->addStmtp(newp); + m_modp->addStmtsp(newp); UINFO(8, "Insert " << newp << endl); } } diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 58a160301..d469978e2 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -245,7 +245,7 @@ private: // Convert valueItem from AstCaseItem* to the expression // Not done earlier, as we may now have a nullptr because it's just a ";" NOP branch for (uint32_t i = 0; i < numCases; ++i) { - m_valueItem[i] = VN_AS(m_valueItem[i], CaseItem)->bodysp(); + m_valueItem[i] = VN_AS(m_valueItem[i], CaseItem)->stmtsp(); } return true; // All is fine } @@ -346,7 +346,7 @@ private: itemp = VN_AS(itemp->nextp(), CaseItem)) { if (!itemp->condsp()) { // Default clause. Just make true, we'll optimize it away later - itemp->condsp(new AstConst(itemp->fileline(), AstConst::BitTrue())); + itemp->addCondsp(new AstConst(itemp->fileline(), AstConst::BitTrue())); hadDefault = true; } else { // Expressioned clause @@ -397,7 +397,7 @@ private: } } // Replace expression in tree - itemp->condsp(ifexprp); + itemp->addCondsp(ifexprp); } } VL_DO_DANGLING(cexprp->deleteTree(), cexprp); @@ -420,7 +420,7 @@ private: AstIf* itemnextp = nullptr; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - AstNode* const istmtsp = itemp->bodysp(); // Maybe null -- no action. + AstNode* const istmtsp = itemp->stmtsp(); // Maybe null -- no action. if (istmtsp) istmtsp->unlinkFrBackWithNext(); // Expressioned clause AstNode* const ifexprp = itemp->condsp()->unlinkFrBack(); @@ -452,7 +452,7 @@ private: if (itemnextp) { itemnextp->addElsesp(newp); } else { - groupnextp->addIfsp(newp); // First in a new group + groupnextp->addThensp(newp); // First in a new group } itemnextp = newp; } diff --git a/src/V3Changed.cpp b/src/V3Changed.cpp index f99b40284..067d0d0a7 100644 --- a/src/V3Changed.cpp +++ b/src/V3Changed.cpp @@ -67,7 +67,7 @@ public: m_tlChgFuncp->isStatic(false); m_tlChgFuncp->isLoose(true); m_tlChgFuncp->declPrivate(true); - m_scopetopp->addActivep(m_tlChgFuncp); + m_scopetopp->addBlocksp(m_tlChgFuncp); // Each change detection function needs at least one AstChangeDet // to ensure that V3EmitC outputs the necessary code. maybeCreateMidChg(); @@ -86,7 +86,7 @@ public: m_chgFuncp->isStatic(false); m_chgFuncp->isLoose(true); m_chgFuncp->declPrivate(true); - m_scopetopp->addActivep(m_chgFuncp); + m_scopetopp->addBlocksp(m_chgFuncp); // Add a top call to it AstCCall* const callp = new AstCCall{m_scopetopp->fileline(), m_chgFuncp}; @@ -218,9 +218,9 @@ public: // CHANGEDET(VARREF(_last), VARREF(var)) AstVar* const newvarp = new AstVar{varp->fileline(), VVarType::MODULETEMP, newvarname, varp}; - m_state.m_topModp->addStmtp(newvarp); + m_state.m_topModp->addStmtsp(newvarp); m_newvscp = new AstVarScope{m_vscp->fileline(), m_state.m_scopetopp, newvarp}; - m_state.m_scopetopp->addVarp(m_newvscp); + m_state.m_scopetopp->addVarsp(m_newvscp); m_varEqnp = new AstVarRef{m_vscp->fileline(), m_vscp, VAccess::READ}; m_newLvEqnp = new AstVarRef{m_vscp->fileline(), m_newvscp, VAccess::WRITE}; diff --git a/src/V3Class.cpp b/src/V3Class.cpp index 52a7c0f1f..f89be682d 100644 --- a/src/V3Class.cpp +++ b/src/V3Class.cpp @@ -54,7 +54,7 @@ private: // Move this class nodep->name(m_prefix + nodep->name()); nodep->unlinkFrBack(); - v3Global.rootp()->addModulep(nodep); + v3Global.rootp()->addModulesp(nodep); // Make containing package // Note origName is the same as the class origName so errors look correct AstClassPackage* const packagep @@ -62,7 +62,7 @@ private: packagep->name(nodep->name() + "__Vclpkg"); nodep->classOrPackagep(packagep); packagep->classp(nodep); - v3Global.rootp()->addModulep(packagep); + v3Global.rootp()->addModulesp(packagep); // Add package to hierarchy AstCell* const cellp = new AstCell{packagep->fileline(), packagep->fileline(), @@ -72,7 +72,7 @@ private: nullptr, nullptr}; cellp->modp(packagep); - v3Global.rootp()->topModulep()->addStmtp(cellp); + v3Global.rootp()->topModulep()->addStmtsp(cellp); // Find class's scope // Alternative would be to move this and related to V3Scope const AstScope* classScopep = nullptr; @@ -85,7 +85,7 @@ private: AstScope* const scopep = new AstScope{nodep->fileline(), packagep, classScopep->name(), classScopep->aboveScopep(), classScopep->aboveCellp()}; - packagep->addStmtp(scopep); + packagep->addStmtsp(scopep); // Iterate VL_RESTORER(m_prefix); VL_RESTORER(m_classPackagep); @@ -178,15 +178,15 @@ public: AstScope* const scopep = moved.second; UINFO(9, "moving " << nodep << " to " << scopep << endl); if (VN_IS(nodep, NodeFTask)) { - scopep->addActivep(nodep->unlinkFrBack()); + scopep->addBlocksp(nodep->unlinkFrBack()); } else if (VN_IS(nodep, Var)) { AstVarScope* const vscp = VN_AS(nodep->user1p(), VarScope); vscp->scopep(scopep); vscp->unlinkFrBack(); - scopep->addVarp(vscp); + scopep->addVarsp(vscp); } else if (VN_IS(nodep, Initial) || VN_IS(nodep, InitialStatic)) { nodep->unlinkFrBack(); - scopep->addActivep(nodep); + scopep->addBlocksp(nodep); } else { nodep->v3fatalSrc("Bad case"); } @@ -196,7 +196,7 @@ public: AstNodeModule* const modp = moved.second; UINFO(9, "moving " << nodep << " to " << modp << endl); nodep->unlinkFrBack(); - modp->addStmtp(nodep); + modp->addStmtsp(nodep); } } }; diff --git a/src/V3Clean.cpp b/src/V3Clean.cpp index 64fa0dcba..7817304a2 100644 --- a/src/V3Clean.cpp +++ b/src/V3Clean.cpp @@ -236,7 +236,7 @@ private: setClean(nodep, false); // We always clean, as we don't trust those pesky users. if (!VN_IS(nodep->backp(), And)) insertClean(nodep); - ensureCleanAndNext(nodep->bodysp()); + ensureCleanAndNext(nodep->exprsp()); } void visit(AstTraceDecl* nodep) override { // No cleaning, or would loose pointer to enum @@ -259,7 +259,7 @@ private: void visit(AstNodeCond* nodep) override { iterateChildren(nodep); ensureClean(nodep->condp()); - setClean(nodep, isClean(nodep->expr1p()) && isClean(nodep->expr2p())); + setClean(nodep, isClean(nodep->thenp()) && isClean(nodep->elsep())); } void visit(AstWhile* nodep) override { iterateChildren(nodep); @@ -276,7 +276,7 @@ private: } void visit(AstUCStmt* nodep) override { iterateChildren(nodep); - ensureCleanAndNext(nodep->bodysp()); + ensureCleanAndNext(nodep->exprsp()); } void visit(AstNodeCCall* nodep) override { iterateChildren(nodep); diff --git a/src/V3Clock.cpp b/src/V3Clock.cpp index 2159fd0e6..f0682852c 100644 --- a/src/V3Clock.cpp +++ b/src/V3Clock.cpp @@ -98,10 +98,10 @@ private: FileLine* const flp = vscp->fileline(); AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()}; newvarp->noReset(true); // Reset by below assign - m_scopep->modp()->addStmtp(newvarp); + m_scopep->modp()->addStmtsp(newvarp); AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; vscp->user1p(newvscp); - m_scopep->addVarp(newvscp); + m_scopep->addVarsp(newvscp); // At the top of _eval, assign them AstAssign* const finalp = new AstAssign{flp, new AstVarRef{flp, newvscp, VAccess::WRITE}, new AstVarRef{flp, vscp, VAccess::READ}}; @@ -133,7 +133,7 @@ private: // We could add another IF to detect posedges, and only increment if so. // It's another whole branch though versus a potential memory miss. // We'll go with the miss. - newp->addIfsp(new AstAssign(nodep->fileline(), changeWrp, origp->cloneTree(false))); + newp->addThensp(new AstAssign(nodep->fileline(), changeWrp, origp->cloneTree(false))); nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); } @@ -159,7 +159,7 @@ private: } // Move statements to if - m_lastIfp->addIfsp(stmtsp); + m_lastIfp->addThensp(stmtsp); // Dispose of the AstActive VL_DO_DANGLING(nodep->deleteTree(), nodep); diff --git a/src/V3Common.cpp b/src/V3Common.cpp index 76521a3fd..f64018c5b 100644 --- a/src/V3Common.cpp +++ b/src/V3Common.cpp @@ -43,7 +43,7 @@ static void makeVlToString(AstClass* nodep) { AstNode* const exprp = new AstCMath{nodep->fileline(), "obj ? obj->to_string() : \"null\"", 0}; exprp->dtypeSetString(); funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp}); - nodep->addStmtp(funcp); + nodep->addStmtsp(funcp); } static void makeToString(AstClass* nodep) { AstCFunc* const funcp = new AstCFunc{nodep->fileline(), "to_string", nullptr, "std::string"}; @@ -54,7 +54,7 @@ static void makeToString(AstClass* nodep) { = new AstCMath{nodep->fileline(), R"(std::string{"'{"} + to_string_middle() + "}")", 0}; exprp->dtypeSetString(); funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp}); - nodep->addStmtp(funcp); + nodep->addStmtsp(funcp); } static void makeToStringMiddle(AstClass* nodep) { AstCFunc* const funcp @@ -96,7 +96,7 @@ static void makeToStringMiddle(AstClass* nodep) { funcp->addStmtsp(new AstCStmt{nodep->fileline(), stmt}); } funcp->addStmtsp(new AstCStmt{nodep->fileline(), "return out;\n"}); - nodep->addStmtp(funcp); + nodep->addStmtsp(funcp); } //###################################################################### diff --git a/src/V3Config.cpp b/src/V3Config.cpp index f716bba2b..5c6d77acf 100644 --- a/src/V3Config.cpp +++ b/src/V3Config.cpp @@ -193,11 +193,11 @@ public: const VPragmaType type = m_inlineValue ? VPragmaType::INLINE_MODULE : VPragmaType::NO_INLINE_MODULE; AstNode* const nodep = new AstPragma(modp->fileline(), type); - modp->addStmtp(nodep); + modp->addStmtsp(nodep); } for (const auto& itr : m_modPragmas) { AstNode* const nodep = new AstPragma{modp->fileline(), itr}; - modp->addStmtp(nodep); + modp->addStmtsp(nodep); } } diff --git a/src/V3Const.cpp b/src/V3Const.cpp index e5dcb93a4..74e4a0b59 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -1042,20 +1042,19 @@ private: // as high as possible, which is usally the right choice, except for this. AstNodeCond* const condp = VN_CAST(nodep->rhsp(), NodeCond); if (!condp) return false; - if (!VN_IS(condp->expr1p(), Const) && !VN_IS(condp->expr2p(), Const)) return false; + if (!VN_IS(condp->thenp(), Const) && !VN_IS(condp->elsep(), Const)) return false; AstConst* const maskp = VN_CAST(nodep->lhsp(), Const); if (!maskp) return false; UINFO(4, "AND(CONSTm, CONDcond(c, i, e))->CONDcond(c, AND(m,i), AND(m, e)) " << nodep << endl); - AstNodeCond* const newp = static_cast( - condp->cloneType(condp->condp()->unlinkFrBack(), - new AstAnd(nodep->fileline(), maskp->cloneTree(false), - condp->expr1p()->unlinkFrBack()), - new AstAnd(nodep->fileline(), maskp->cloneTree(false), - condp->expr2p()->unlinkFrBack()))); + AstNodeCond* const newp = static_cast(condp->cloneType( + condp->condp()->unlinkFrBack(), + new AstAnd(nodep->fileline(), maskp->cloneTree(false), condp->thenp()->unlinkFrBack()), + new AstAnd(nodep->fileline(), maskp->cloneTree(false), + condp->elsep()->unlinkFrBack()))); newp->dtypeFrom(nodep); - newp->expr1p()->dtypeFrom(nodep); // As And might have been to change widths - newp->expr2p()->dtypeFrom(nodep); + newp->thenp()->dtypeFrom(nodep); // As And might have been to change widths + newp->elsep()->dtypeFrom(nodep); nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); return true; @@ -1443,19 +1442,19 @@ private: } } bool ifSameAssign(const AstNodeIf* nodep) { - const AstNodeAssign* const ifp = VN_CAST(nodep->ifsp(), NodeAssign); - const AstNodeAssign* const elsep = VN_CAST(nodep->elsesp(), NodeAssign); - if (!ifp || ifp->nextp()) return false; // Must be SINGLE statement - if (!elsep || elsep->nextp()) return false; - if (ifp->type() != elsep->type()) return false; // Can't mix an assigndly and an assign - if (!ifp->lhsp()->sameGateTree(elsep->lhsp())) return false; - if (!ifp->rhsp()->gateTree()) return false; - if (!elsep->rhsp()->gateTree()) return false; + const AstNodeAssign* const thensp = VN_CAST(nodep->thensp(), NodeAssign); + const AstNodeAssign* const elsesp = VN_CAST(nodep->elsesp(), NodeAssign); + if (!thensp || thensp->nextp()) return false; // Must be SINGLE statement + if (!elsesp || elsesp->nextp()) return false; + if (thensp->type() != elsesp->type()) return false; // Can't mix an assigndly with assign + if (!thensp->lhsp()->sameGateTree(elsesp->lhsp())) return false; + if (!thensp->rhsp()->gateTree()) return false; + if (!elsesp->rhsp()->gateTree()) return false; return true; } bool operandIfIf(const AstNodeIf* nodep) { if (nodep->elsesp()) return false; - const AstNodeIf* const lowerIfp = VN_CAST(nodep->ifsp(), NodeIf); + const AstNodeIf* const lowerIfp = VN_CAST(nodep->thensp(), NodeIf); if (!lowerIfp || lowerIfp->nextp()) return false; if (nodep->type() != lowerIfp->type()) return false; if (afterComment(lowerIfp->elsesp())) return false; @@ -2097,8 +2096,8 @@ private: AstVar* const temp2p = new AstVar(sel2p->fileline(), VVarType::BLOCKTEMP, m_concswapNames.get(sel2p), VFlagLogicPacked(), msb2 - lsb2 + 1); - m_modp->addStmtp(temp1p); - m_modp->addStmtp(temp2p); + m_modp->addStmtsp(temp1p); + m_modp->addStmtsp(temp2p); AstNodeAssign* const asn1ap = VN_AS(nodep->cloneType( new AstVarRef(sel1p->fileline(), temp1p, VAccess::WRITE), sel1p), @@ -2881,7 +2880,7 @@ private: varrefp->unlinkFrBack(); AstInitial* const newinitp = new AstInitial( nodep->fileline(), new AstAssign(nodep->fileline(), varrefp, exprp)); - m_modp->addStmtp(newinitp); + m_modp->addStmtsp(newinitp); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); // Set the initial value right in the variable so we can constant propagate AstNode* const initvaluep = exprp->cloneTree(false); @@ -2911,7 +2910,7 @@ private: keepp = nodep->elsesp(); } else if (!m_doV || constp->isNeqZero()) { // Might be X in Verilog UINFO(4, "IF(!0,{x},{any}) => {x}: " << nodep << endl); - keepp = nodep->ifsp(); + keepp = nodep->thensp(); } else { UINFO(4, "IF condition is X, retaining: " << nodep << endl); return; @@ -2923,7 +2922,7 @@ private: nodep->unlinkFrBack(); } VL_DO_DANGLING(nodep->deleteTree(), nodep); - } else if (!afterComment(nodep->ifsp()) && !afterComment(nodep->elsesp())) { + } else if (!afterComment(nodep->thensp()) && !afterComment(nodep->elsesp())) { if (!isTreePureRecurse(nodep->condp())) { // Condition has side effect - leave - perhaps in // future simplify to remove all but side effect terms @@ -2931,27 +2930,27 @@ private: // Empty block, remove it VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } - } else if (!afterComment(nodep->ifsp())) { + } else if (!afterComment(nodep->thensp())) { UINFO(4, "IF({x}) nullptr {...} => IF(NOT{x}}: " << nodep << endl); AstNode* const condp = nodep->condp(); AstNode* const elsesp = nodep->elsesp(); condp->unlinkFrBackWithNext(); elsesp->unlinkFrBackWithNext(); - if (nodep->ifsp()) { // Must have been comment - nodep->ifsp()->unlinkFrBackWithNext()->deleteTree(); + if (nodep->thensp()) { // Must have been comment + nodep->thensp()->unlinkFrBackWithNext()->deleteTree(); } nodep->condp(new AstLogNot(condp->fileline(), condp)); // LogNot, as C++ optimization also possible - nodep->addIfsp(elsesp); + nodep->addThensp(elsesp); } else if (((VN_IS(nodep->condp(), Not) && nodep->condp()->width() == 1) || VN_IS(nodep->condp(), LogNot)) - && nodep->ifsp() && nodep->elsesp()) { + && nodep->thensp() && nodep->elsesp()) { UINFO(4, "IF(NOT {x}) => IF(x) swapped if/else" << nodep << endl); AstNode* const condp = VN_AS(nodep->condp(), NodeUniop)->lhsp()->unlinkFrBackWithNext(); - AstNode* const ifsp = nodep->ifsp()->unlinkFrBackWithNext(); + AstNode* const thensp = nodep->thensp()->unlinkFrBackWithNext(); AstNode* const elsesp = nodep->elsesp()->unlinkFrBackWithNext(); - AstIf* const ifp = new AstIf(nodep->fileline(), condp, elsesp, ifsp); + AstIf* const ifp = new AstIf(nodep->fileline(), condp, elsesp, thensp); ifp->branchPred(nodep->branchPred().invert()); nodep->replaceWith(ifp); VL_DO_DANGLING(nodep->deleteTree(), nodep); @@ -2959,25 +2958,25 @@ private: UINFO( 4, "IF({a}) ASSIGN({b},{c}) else ASSIGN({b},{d}) => ASSIGN({b}, {a}?{c}:{d})\n"); - AstNodeAssign* const ifp = VN_AS(nodep->ifsp(), NodeAssign); - AstNodeAssign* const elsep = VN_AS(nodep->elsesp(), NodeAssign); - ifp->unlinkFrBack(); + AstNodeAssign* const thensp = VN_AS(nodep->thensp(), NodeAssign); + AstNodeAssign* const elsesp = VN_AS(nodep->elsesp(), NodeAssign); + thensp->unlinkFrBack(); AstNode* const condp = nodep->condp()->unlinkFrBack(); - AstNode* const truep = ifp->rhsp()->unlinkFrBack(); - AstNode* const falsep = elsep->rhsp()->unlinkFrBack(); - ifp->rhsp(new AstCond(truep->fileline(), condp, truep, falsep)); - nodep->replaceWith(ifp); + AstNode* const truep = thensp->rhsp()->unlinkFrBack(); + AstNode* const falsep = elsesp->rhsp()->unlinkFrBack(); + thensp->rhsp(new AstCond(truep->fileline(), condp, truep, falsep)); + nodep->replaceWith(thensp); VL_DO_DANGLING(nodep->deleteTree(), nodep); } else if (false // Disabled, as vpm assertions are faster // without due to short-circuiting && operandIfIf(nodep)) { UINFO(9, "IF({a}) IF({b}) => IF({a} && {b})" << endl); - AstNodeIf* const lowerIfp = VN_AS(nodep->ifsp(), NodeIf); + AstNodeIf* const lowerIfp = VN_AS(nodep->thensp(), NodeIf); AstNode* const condp = nodep->condp()->unlinkFrBack(); - AstNode* const lowerIfsp = lowerIfp->ifsp()->unlinkFrBackWithNext(); + AstNode* const lowerThensp = lowerIfp->thensp()->unlinkFrBackWithNext(); AstNode* const lowerCondp = lowerIfp->condp()->unlinkFrBackWithNext(); nodep->condp(new AstLogAnd(lowerIfp->fileline(), condp, lowerCondp)); - lowerIfp->replaceWith(lowerIfsp); + lowerIfp->replaceWith(lowerThensp); VL_DO_DANGLING(lowerIfp->deleteTree(), lowerIfp); } else if (operandBoolShift(nodep->condp())) { replaceBoolShift(nodep->condp()); @@ -3034,8 +3033,10 @@ private: pformatp->text(pformatp->text() + nformatp->text()); if (!prevp->addNewline() && nodep->addNewline()) pformatp->text(pformatp->text() + "\n"); if (nformatp->exprsp()) pformatp->addExprsp(nformatp->exprsp()->unlinkFrBackWithNext()); - if (nformatp->scopeNamep()) - pformatp->scopeNamep(nformatp->scopeNamep()->unlinkFrBackWithNext()); + if (AstScopeName* const scopeNamep = nformatp->scopeNamep()) { + scopeNamep->unlinkFrBackWithNext(); + pformatp->scopeNamep(scopeNamep); + } VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); return true; } @@ -3315,19 +3316,19 @@ private: TREEOPC("AstAnd {$lhsp.isOne, matchRedundantClean(nodep)}", "DONE") // 1 & (a == b) -> (IData)(a == b) // Trinary ops // Note V3Case::Sel requires Cond to always be conditionally executed in C to prevent core dump! - TREEOP ("AstNodeCond{$condp.isZero, $expr1p, $expr2p}", "replaceWChild(nodep,$expr2p)"); - TREEOP ("AstNodeCond{$condp.isNeqZero, $expr1p, $expr2p}", "replaceWChild(nodep,$expr1p)"); - TREEOPA("AstNodeCond{$condp.isZero, $expr1p.castConst, $expr2p.castConst}", "replaceWChild(nodep,$expr2p)"); - TREEOPA("AstNodeCond{$condp.isNeqZero, $expr1p.castConst, $expr2p.castConst}", "replaceWChild(nodep,$expr1p)"); - TREEOP ("AstNodeCond{$condp, operandsSame($expr1p,,$expr2p)}","replaceWChild(nodep,$expr1p)"); + TREEOP ("AstNodeCond{$condp.isZero, $thenp, $elsep}", "replaceWChild(nodep,$elsep)"); + TREEOP ("AstNodeCond{$condp.isNeqZero, $thenp, $elsep}", "replaceWChild(nodep,$thenp)"); + TREEOPA("AstNodeCond{$condp.isZero, $thenp.castConst, $elsep.castConst}", "replaceWChild(nodep,$elsep)"); + TREEOPA("AstNodeCond{$condp.isNeqZero, $thenp.castConst, $elsep.castConst}", "replaceWChild(nodep,$thenp)"); + TREEOP ("AstNodeCond{$condp, operandsSame($thenp,,$elsep)}","replaceWChild(nodep,$thenp)"); // This visit function here must allow for short-circuiting. TREEOPS("AstCond {$lhsp.isZero}", "replaceWIteratedThs(nodep)"); TREEOPS("AstCond {$lhsp.isNeqZero}", "replaceWIteratedRhs(nodep)"); - TREEOP ("AstCond{$condp.castNot, $expr1p, $expr2p}", "AstCond{$condp->op1p(), $expr2p, $expr1p}"); - TREEOP ("AstNodeCond{$condp.width1, $expr1p.width1, $expr1p.isAllOnes, $expr2p}", "AstLogOr {$condp, $expr2p}"); // a?1:b == a||b - TREEOP ("AstNodeCond{$condp.width1, $expr1p.width1, $expr1p, $expr2p.isZero}", "AstLogAnd{$condp, $expr1p}"); // a?b:0 == a&&b - TREEOP ("AstNodeCond{$condp.width1, $expr1p.width1, $expr1p, $expr2p.isAllOnes}", "AstLogOr {AstNot{$condp}, $expr1p}"); // a?b:1 == ~a||b - TREEOP ("AstNodeCond{$condp.width1, $expr1p.width1, $expr1p.isZero, $expr2p}", "AstLogAnd{AstNot{$condp}, $expr2p}"); // a?0:b == ~a&&b + TREEOP ("AstCond{$condp.castNot, $thenp, $elsep}", "AstCond{$condp->op1p(), $elsep, $thenp}"); + TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp.isAllOnes, $elsep}", "AstLogOr {$condp, $elsep}"); // a?1:b == a||b + TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp, $elsep.isZero}", "AstLogAnd{$condp, $thenp}"); // a?b:0 == a&&b + TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp, $elsep.isAllOnes}", "AstLogOr {AstNot{$condp}, $thenp}"); // a?b:1 == ~a||b + TREEOP ("AstNodeCond{$condp.width1, $thenp.width1, $thenp.isZero, $elsep}", "AstLogAnd{AstNot{$condp}, $elsep}"); // a?0:b == ~a&&b TREEOP ("AstNodeCond{!$condp.width1, operandBoolShift(nodep->condp())}", "replaceBoolShift(nodep->condp())"); // Prefer constants on left, since that often needs a shift, it lets // constant red remove the shift diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index 04266c492..1a87bb02f 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -119,7 +119,7 @@ private: AstCoverDecl* const declp = new AstCoverDecl(fl, page, comment, linescov, offset); declp->hier(hier); - m_modp->addStmtp(declp); + m_modp->addStmtsp(declp); UINFO(9, "new " << declp << endl); AstCoverInc* const incp = new AstCoverInc(fl, declp); @@ -128,7 +128,7 @@ private: incp->findUInt32DType()); varp->trace(true); varp->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true); - m_modp->addStmtp(varp); + m_modp->addStmtsp(varp); UINFO(5, "New coverage trace: " << varp << endl); AstAssign* const assp = new AstAssign( incp->fileline(), new AstVarRef(incp->fileline(), varp, VAccess::WRITE), @@ -245,11 +245,11 @@ private: = newCoverInc(nodep->fileline(), "", "v_line", "block", linesCov(m_state, nodep), 0, traceNameForLine(nodep, "block")); if (AstNodeProcedure* const itemp = VN_CAST(nodep, NodeProcedure)) { - itemp->addStmtp(newp); + itemp->addStmtsp(newp); } else if (AstNodeFTask* const itemp = VN_CAST(nodep, NodeFTask)) { itemp->addStmtsp(newp); } else if (AstWhile* const itemp = VN_CAST(nodep, While)) { - itemp->addBodysp(newp); + itemp->addStmtsp(newp); } else { nodep->v3fatalSrc("Bad node type"); } @@ -283,7 +283,7 @@ private: AstVar* const chgVarp = new AstVar(nodep->fileline(), VVarType::MODULETEMP, newvarname, nodep); chgVarp->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true); - m_modp->addStmtp(chgVarp); + m_modp->addStmtsp(chgVarp); // Create bucket for each dimension * bit. // This is necessarily an O(n^2) expansion, which is why @@ -304,7 +304,7 @@ private: newCoverInc(varp->fileline(), "", "v_toggle", varp->name() + above.m_comment, "", 0, ""), above.m_varRefp->cloneTree(true), above.m_chgRefp->cloneTree(true)); - m_modp->addStmtp(newp); + m_modp->addStmtsp(newp); } void toggleVarRecurse(AstNodeDType* dtypep, int depth, // per-iteration @@ -388,7 +388,7 @@ private: if (m_state.m_on) { // An else-if. When we iterate the if, use "elsif" marking const bool elsif - = nodep->ifsp() && VN_IS(nodep->elsesp(), If) && !nodep->elsesp()->nextp(); + = nodep->thensp() && VN_IS(nodep->elsesp(), If) && !nodep->elsesp()->nextp(); if (elsif) VN_AS(nodep->elsesp(), If)->user1(true); const bool first_elsif = !nodep->user1() && elsif; const bool cont_elsif = nodep->user1() && elsif; @@ -403,7 +403,7 @@ private: CheckState elseState; { createHandle(nodep); - iterateAndNextNull(nodep->ifsp()); + iterateAndNextNull(nodep->thensp()); lineTrack(nodep); ifState = m_state; } @@ -422,9 +422,9 @@ private: // Normal if. Linecov shows what's inside the if (not condition that is // always executed) UINFO(4, " COVER-branch: " << nodep << endl); - nodep->addIfsp(newCoverInc(nodep->fileline(), "", "v_branch", "if", - linesCov(ifState, nodep), 0, - traceNameForLine(nodep, "if"))); + nodep->addThensp(newCoverInc(nodep->fileline(), "", "v_branch", "if", + linesCov(ifState, nodep), 0, + traceNameForLine(nodep, "if"))); // The else has a column offset of 1 to uniquify it relative to the if // As "if" and "else" are more than one character wide, this won't overlap // another token @@ -436,18 +436,18 @@ private: else if (first_elsif || cont_elsif) { UINFO(4, " COVER-elsif: " << nodep << endl); if (ifState.lineCoverageOn(nodep)) { - nodep->addIfsp(newCoverInc(nodep->fileline(), "", "v_line", "elsif", - linesCov(ifState, nodep), 0, - traceNameForLine(nodep, "elsif"))); + nodep->addThensp(newCoverInc(nodep->fileline(), "", "v_line", "elsif", + linesCov(ifState, nodep), 0, + traceNameForLine(nodep, "elsif"))); } // and we don't insert the else as the child if-else will do so } else { // Cover as separate blocks (not a branch as is not two-legged) if (ifState.lineCoverageOn(nodep)) { UINFO(4, " COVER-half-if: " << nodep << endl); - nodep->addIfsp(newCoverInc(nodep->fileline(), "", "v_line", "if", - linesCov(ifState, nodep), 0, - traceNameForLine(nodep, "if"))); + nodep->addThensp(newCoverInc(nodep->fileline(), "", "v_line", "if", + linesCov(ifState, nodep), 0, + traceNameForLine(nodep, "if"))); } if (elseState.lineCoverageOn(nodep)) { UINFO(4, " COVER-half-el: " << nodep << endl); @@ -468,11 +468,11 @@ private: VL_RESTORER(m_state); { createHandle(nodep); - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); if (m_state.lineCoverageOn(nodep)) { // if the case body didn't disable it lineTrack(nodep); UINFO(4, " COVER: " << nodep << endl); - nodep->addBodysp(newCoverInc(nodep->fileline(), "", "v_line", "case", + nodep->addStmtsp(newCoverInc(nodep->fileline(), "", "v_line", "case", linesCov(m_state, nodep), 0, traceNameForLine(nodep, "case"))); } @@ -486,12 +486,12 @@ private: m_state.m_on = true; // Always do cover blocks, even if there's a $stop createHandle(nodep); iterateChildren(nodep); - if (!nodep->coverincp() && v3Global.opt.coverageUser()) { + if (!nodep->coverincsp() && v3Global.opt.coverageUser()) { // Note the name may be overridden by V3Assert processing lineTrack(nodep); - nodep->coverincp(newCoverInc(nodep->fileline(), m_beginHier, "v_user", "cover", - linesCov(m_state, nodep), 0, - m_beginHier + "_vlCoverageUserTrace")); + nodep->addCoverincsp(newCoverInc(nodep->fileline(), m_beginHier, "v_user", "cover", + linesCov(m_state, nodep), 0, + m_beginHier + "_vlCoverageUserTrace")); } } } diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index 4e75e0788..a060c216a 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -157,13 +157,13 @@ private: varp = new AstVar(oldvarscp->fileline(), VVarType::BLOCKTEMP, name, VFlagBitPacked(), width); } - addmodp->addStmtp(varp); + addmodp->addStmtsp(varp); m_modVarMap.emplace(std::make_pair(addmodp, name), varp); } AstVarScope* const varscp = new AstVarScope(oldvarscp->fileline(), oldvarscp->scopep(), varp); - oldvarscp->scopep()->addVarp(varscp); + oldvarscp->scopep()->addVarsp(varscp); return varscp; } @@ -387,15 +387,15 @@ private: postLogicp = new AstIf(nodep->fileline(), new AstVarRef(nodep->fileline(), setvscp, VAccess::READ)); UINFO(9, " Created " << postLogicp << endl); - finalp->addStmtp(postLogicp); + finalp->addStmtsp(postLogicp); finalp->user3p(setvscp); // Remember IF's vset variable finalp->user4p(postLogicp); // and the associated IF, as we may be able to reuse it } - postLogicp->addIfsp(new AstAssign(nodep->fileline(), selectsp, valreadp)); + postLogicp->addThensp(new AstAssign(nodep->fileline(), selectsp, valreadp)); if (m_procp->isSuspendable()) { FileLine* const flp = nodep->fileline(); - postLogicp->addIfsp(new AstAssign{flp, new AstVarRef{flp, setvscp, VAccess::WRITE}, - new AstConst{flp, 0}}); + postLogicp->addThensp(new AstAssign{flp, new AstVarRef{flp, setvscp, VAccess::WRITE}, + new AstConst{flp, 0}}); } return newlhsp; } @@ -475,11 +475,11 @@ private: AstAlwaysPost* const postp = new AstAlwaysPost{flp, nullptr, nullptr}; { AstIf* const ifp = new AstIf{flp, dlyRef(VAccess::READ)}; - postp->addStmtp(ifp); + postp->addStmtsp(ifp); AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "fire"}; callp->statement(true); callp->dtypeSetVoid(); - ifp->addIfsp(callp); + ifp->addThensp(callp); } AstActive* const activep = createActive(nodep); diff --git a/src/V3DepthBlock.cpp b/src/V3DepthBlock.cpp index c4cde8287..89bd99669 100644 --- a/src/V3DepthBlock.cpp +++ b/src/V3DepthBlock.cpp @@ -57,7 +57,7 @@ private: funcp->isStatic(m_cfuncp->isStatic()); funcp->isLoose(m_cfuncp->isLoose()); funcp->addStmtsp(nodep); - scopep->addActivep(funcp); + scopep->addBlocksp(funcp); // Call sub function at the point where the body was removed from AstCCall* const callp = new AstCCall(nodep->fileline(), funcp); if (VN_IS(m_modp, Class)) { diff --git a/src/V3Descope.cpp b/src/V3Descope.cpp index 2a89374ef..e1ad31fdc 100644 --- a/src/V3Descope.cpp +++ b/src/V3Descope.cpp @@ -253,7 +253,7 @@ private: // If it's under a scope, move it up to the top if (m_scopep) { nodep->unlinkFrBack(); - m_modp->addStmtp(nodep); + m_modp->addStmtsp(nodep); if (nodep->funcPublic()) { // There may be multiple public functions by the same name; diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 0bd404cb3..26d502c8e 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -825,7 +825,7 @@ public: puts("while ("); iterateAndNextNull(nodep->condp()); puts(") {\n"); - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); iterateAndNextNull(nodep->incsp()); iterateAndNextNull(nodep->precondsp()); // Need to recompute before next loop puts("}\n"); @@ -839,7 +839,7 @@ public: iterateAndNextNull(nodep->condp()); if (!nodep->branchPred().unknown()) puts(")"); puts(") {\n"); - iterateAndNextNull(nodep->ifsp()); + iterateAndNextNull(nodep->thensp()); puts("}"); if (!nodep->elsesp()) { puts("\n"); @@ -932,17 +932,17 @@ public: } void visit(AstCStmt* nodep) override { putbs(""); - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->exprsp()); } void visit(AstCMath* nodep) override { putbs(""); - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->exprsp()); } void visit(AstUCStmt* nodep) override { VL_RESTORER(m_inUC); m_inUC = true; putsDecoration(ifNoProtect("// $c statement at " + nodep->fileline()->ascii() + "\n")); - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->exprsp()); puts("\n"); } void visit(AstUCFunc* nodep) override { @@ -950,7 +950,7 @@ public: m_inUC = true; puts("\n"); putsDecoration(ifNoProtect("// $c function at " + nodep->fileline()->ascii() + "\n")); - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->exprsp()); puts("\n"); } @@ -1030,15 +1030,15 @@ public: } void visit(AstNodeCond* nodep) override { // Widths match up already, so we'll just use C++'s operator w/o any temps. - if (nodep->expr1p()->isWide()) { - emitOpName(nodep, nodep->emitC(), nodep->condp(), nodep->expr1p(), nodep->expr2p()); + if (nodep->thenp()->isWide()) { + emitOpName(nodep, nodep->emitC(), nodep->condp(), nodep->thenp(), nodep->elsep()); } else { putbs("("); iterateAndNextNull(nodep->condp()); putbs(" ? "); - iterateAndNextNull(nodep->expr1p()); + iterateAndNextNull(nodep->thenp()); putbs(" : "); - iterateAndNextNull(nodep->expr2p()); + iterateAndNextNull(nodep->elsep()); puts(")"); } } diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 0260135b7..fe4316d1f 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -110,7 +110,7 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor { iterateAndNextConstNull(nodep->sensesp()); } putbs(" begin\n"); - iterateAndNextConstNull(nodep->bodysp()); + iterateAndNextConstNull(nodep->stmtsp()); putqs(nodep, "end\n"); } void visit(AstAlwaysPublic* nodep) override { @@ -122,7 +122,7 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor { iterateAndNextConstNull(nodep->sensesp()); } putqs(nodep, " "); - iterateAndNextConstNull(nodep->bodysp()); + iterateAndNextConstNull(nodep->stmtsp()); putqs(nodep, "*/\n"); } void visit(AstNodeAssign* nodep) override { @@ -204,7 +204,7 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor { putbs("default"); } putfs(nodep, ": begin "); - iterateAndNextConstNull(nodep->bodysp()); + iterateAndNextConstNull(nodep->stmtsp()); putqs(nodep, "end\n"); } void visit(AstComment* nodep) override { @@ -326,14 +326,14 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor { iterateAndNextConstNull(nodep->incsp()); } puts(") begin\n"); - iterateAndNextConstNull(nodep->bodysp()); + iterateAndNextConstNull(nodep->stmtsp()); putqs(nodep, "end\n"); } void visit(AstRepeat* nodep) override { putfs(nodep, "repeat ("); iterateAndNextConstNull(nodep->countp()); puts(") begin\n"); - iterateAndNextConstNull(nodep->bodysp()); + iterateAndNextConstNull(nodep->stmtsp()); putfs(nodep, "end\n"); } void visit(AstWhile* nodep) override { @@ -341,7 +341,7 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor { putfs(nodep, "while ("); iterateAndNextConstNull(nodep->condp()); puts(") begin\n"); - iterateAndNextConstNull(nodep->bodysp()); + iterateAndNextConstNull(nodep->stmtsp()); iterateAndNextConstNull(nodep->incsp()); iterateAndNextConstNull(nodep->precondsp()); // Need to recompute before next loop putfs(nodep, "end\n"); @@ -356,7 +356,7 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor { puts("if ("); iterateAndNextConstNull(nodep->condp()); puts(") begin\n"); - iterateAndNextConstNull(nodep->ifsp()); + iterateAndNextConstNull(nodep->thensp()); if (nodep->elsesp()) { putqs(nodep, "end\n"); putqs(nodep, "else begin\n"); @@ -401,22 +401,22 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor { void visit(AstScopeName* nodep) override {} void visit(AstCStmt* nodep) override { putfs(nodep, "$_CSTMT("); - iterateAndNextConstNull(nodep->bodysp()); + iterateAndNextConstNull(nodep->exprsp()); puts(");\n"); } void visit(AstCMath* nodep) override { putfs(nodep, "$_CMATH("); - iterateAndNextConstNull(nodep->bodysp()); + iterateAndNextConstNull(nodep->exprsp()); puts(");\n"); } void visit(AstUCStmt* nodep) override { putfs(nodep, "$c("); - iterateAndNextConstNull(nodep->bodysp()); + iterateAndNextConstNull(nodep->exprsp()); puts(");\n"); } void visit(AstUCFunc* nodep) override { putfs(nodep, "$c("); - iterateAndNextConstNull(nodep->bodysp()); + iterateAndNextConstNull(nodep->exprsp()); puts(")"); } @@ -531,9 +531,9 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor { putbs("("); iterateAndNextConstNull(nodep->condp()); putfs(nodep, " ? "); - iterateAndNextConstNull(nodep->expr1p()); + iterateAndNextConstNull(nodep->thenp()); putbs(" : "); - iterateAndNextConstNull(nodep->expr2p()); + iterateAndNextConstNull(nodep->elsep()); puts(")"); } void visit(AstRange* nodep) override { diff --git a/src/V3Expand.cpp b/src/V3Expand.cpp index 85635e31a..9eea581ae 100644 --- a/src/V3Expand.cpp +++ b/src/V3Expand.cpp @@ -306,8 +306,8 @@ private: for (int w = 0; w < nodep->widthWords(); ++w) { addWordAssign(nodep, w, new AstCond{fl, rhsp->condp()->cloneTree(true), - newAstWordSelClone(rhsp->expr1p(), w), - newAstWordSelClone(rhsp->expr2p(), w)}); + newAstWordSelClone(rhsp->thenp(), w), + newAstWordSelClone(rhsp->elsep(), w)}); } return true; } diff --git a/src/V3Force.cpp b/src/V3Force.cpp index ea4c61a02..446869208 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -109,7 +109,7 @@ class ForceConvertVisitor final : public VNVisitor { new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Initial{}}}}; activep->sensesStorep(activep->sensesp()); activep->addStmtsp(new AstInitial{flp, assignp}); - vscp->scopep()->addActivep(activep); + vscp->scopep()->addBlocksp(activep); } { // Add the combinational override @@ -127,7 +127,7 @@ class ForceConvertVisitor final : public VNVisitor { new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Combo{}}}}; activep->sensesStorep(activep->sensesp()); activep->addStmtsp(new AstAssignW{flp, lhsp, rhsp}); - vscp->scopep()->addActivep(activep); + vscp->scopep()->addBlocksp(activep); } } }; diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp index c49eb56a1..d46d9874e 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -932,7 +932,7 @@ private: if (m_dedupable) { if (!m_always) { m_always = true; - iterateAndNextNull(alwaysp->bodysp()); + iterateAndNextNull(alwaysp->stmtsp()); } else { m_dedupable = false; } @@ -947,7 +947,7 @@ private: if (m_always && !m_ifCondp && !ifp->elsesp()) { // we're under an always, this is the first IF, and there's no else m_ifCondp = ifp->condp(); - iterateAndNextNull(ifp->ifsp()); + iterateAndNextNull(ifp->thensp()); } else { m_dedupable = false; } diff --git a/src/V3GenClk.cpp b/src/V3GenClk.cpp index 40327384b..f8c1514ec 100644 --- a/src/V3GenClk.cpp +++ b/src/V3GenClk.cpp @@ -76,9 +76,9 @@ private: // ASSIGN(VARREF(inpclk), VARREF(var)) AstVar* const newvarp = new AstVar(varp->fileline(), VVarType::MODULETEMP, newvarname, varp); - m_topModp->addStmtp(newvarp); + m_topModp->addStmtsp(newvarp); AstVarScope* const newvscp = new AstVarScope(vscp->fileline(), m_scopetopp, newvarp); - m_scopetopp->addVarp(newvscp); + m_scopetopp->addVarsp(newvscp); AstAssign* const asninitp = new AstAssign( vscp->fileline(), new AstVarRef(vscp->fileline(), newvscp, VAccess::WRITE), new AstVarRef(vscp->fileline(), vscp, VAccess::READ)); diff --git a/src/V3Inline.cpp b/src/V3Inline.cpp index 277531647..cacca2901 100644 --- a/src/V3Inline.cpp +++ b/src/V3Inline.cpp @@ -294,15 +294,15 @@ private: UASSERT_OBJ(exprconstp || exprvarrefp, nodep, "Unknown interconnect type; pinReconnectSimple should have cleared up"); if (exprconstp) { - m_modp->addStmtp(new AstAssignW(flp, new AstVarRef(flp, nodep, VAccess::WRITE), - exprconstp->cloneTree(false))); + m_modp->addStmtsp(new AstAssignW(flp, new AstVarRef(flp, nodep, VAccess::WRITE), + exprconstp->cloneTree(false))); } else if (nodep->user3()) { // Public variable at the lower module end - we need to make sure we propagate // the logic changes up and down; if we aliased, we might // remove the change detection on the output variable. UINFO(9, "public pin assign: " << exprvarrefp << endl); UASSERT_OBJ(!nodep->isNonOutput(), nodep, "Outputs only - inputs use AssignAlias"); - m_modp->addStmtp( + m_modp->addStmtsp( new AstAssignW(flp, new AstVarRef(flp, exprvarrefp->varp(), VAccess::WRITE), new AstVarRef(flp, nodep, VAccess::READ))); } else if (nodep->isSigPublic() && VN_IS(nodep->dtypep(), UnpackArrayDType)) { @@ -310,11 +310,11 @@ private: // instead of aliased, because otherwise it will pass V3Slice and invalid // code will be emitted. UINFO(9, "assign to public and unpacked: " << nodep << endl); - m_modp->addStmtp( + m_modp->addStmtsp( new AstAssignW{flp, new AstVarRef{flp, nodep, VAccess::WRITE}, new AstVarRef{flp, exprvarrefp->varp(), VAccess::READ}}); } else if (nodep->isIfaceRef()) { - m_modp->addStmtp( + m_modp->addStmtsp( new AstAssignVarScope(flp, new AstVarRef(flp, nodep, VAccess::WRITE), new AstVarRef(flp, exprvarrefp->varp(), VAccess::READ))); FileLine* const flbp = exprvarrefp->varp()->fileline(); @@ -323,7 +323,7 @@ private: } else { // Do to inlining child's variable now within the same // module, so a AstVarRef not AstVarXRef below - m_modp->addStmtp( + m_modp->addStmtsp( new AstAssignAlias(flp, new AstVarRef(flp, nodep, VAccess::WRITE), new AstVarRef(flp, exprvarrefp->varp(), VAccess::READ))); FileLine* const flbp = exprvarrefp->varp()->fileline(); @@ -423,16 +423,16 @@ private: // If there's a %m in the display text, we add a special node that will contain the name() // Similar code in V3Begin // To keep correct visual order, must add before other Text's - AstNode* afterp = nodep->scopeAttrp(); + AstText* afterp = nodep->scopeAttrp(); if (afterp) afterp->unlinkFrBackWithNext(); - nodep->scopeAttrp( + nodep->addScopeAttrp( new AstText{nodep->fileline(), std::string{"__DOT__"} + m_cellp->name()}); - if (afterp) nodep->scopeAttrp(afterp); + if (afterp) nodep->addScopeAttrp(afterp); afterp = nodep->scopeEntrp(); if (afterp) afterp->unlinkFrBackWithNext(); - nodep->scopeEntrp( + nodep->addScopeEntrp( new AstText{nodep->fileline(), std::string{"__DOT__"} + m_cellp->name()}); - if (afterp) nodep->scopeEntrp(afterp); + if (afterp) nodep->addScopeEntrp(afterp); iterateChildren(nodep); } void visit(AstCoverDecl* nodep) override { @@ -566,7 +566,7 @@ private: // Move statements under the module we are inlining into if (AstNode* const stmtsp = newmodp->stmtsp()) { stmtsp->unlinkFrBackWithNext(); - m_modp->addStmtp(stmtsp); + m_modp->addStmtsp(stmtsp); } // Clear any leftover ports, etc VL_DO_DANGLING(newmodp->deleteTree(), newmodp); @@ -649,7 +649,7 @@ private: } if (VN_IS(nodep->modp(), Iface)) { - nodep->addIntfRefp(new AstIntfRef{nodep->fileline(), m_scope}); + nodep->addIntfRefsp(new AstIntfRef{nodep->fileline(), m_scope}); } { AstNodeModule* const modp = nodep->modp(); @@ -666,7 +666,7 @@ private: if ((cellp = VN_CAST(fromVarp->user1p(), Cell)) || (cellp = irdtp->cellp())) { varp->user1p(cellp); const string alias = m_scope + "__DOT__" + pinp->name(); - cellp->addIntfRefp(new AstIntfRef(pinp->fileline(), alias)); + cellp->addIntfRefsp(new AstIntfRef(pinp->fileline(), alias)); } } @@ -695,7 +695,7 @@ private: string alias; if (!m_scope.empty()) alias = m_scope + "__DOT__"; alias += varlp->name(); - cellp->addIntfRefp(new AstIntfRef(varlp->fileline(), alias)); + cellp->addIntfRefsp(new AstIntfRef(varlp->fileline(), alias)); } //-------------------- void visit(AstNodeMath*) override {} // Accelerate diff --git a/src/V3InstrCount.cpp b/src/V3InstrCount.cpp index 5514a6e1c..534ce403d 100644 --- a/src/V3InstrCount.cpp +++ b/src/V3InstrCount.cpp @@ -178,9 +178,9 @@ private: iterateAndNextNull(nodep->condp()); const uint32_t savedCount = m_instrCount; - UINFO(8, "ifsp:\n"); + UINFO(8, "thensp:\n"); reset(); - iterateAndNextNull(nodep->ifsp()); + iterateAndNextNull(nodep->thensp()); uint32_t ifCount = m_instrCount; if (nodep->branchPred().unlikely()) ifCount = 0; @@ -196,7 +196,7 @@ private: if (nodep->elsesp()) nodep->elsesp()->user4(0); // Don't dump it } else { m_instrCount = savedCount + elseCount; - if (nodep->ifsp()) nodep->ifsp()->user4(0); // Don't dump it + if (nodep->thensp()) nodep->thensp()->user4(0); // Don't dump it } } void visit(AstNodeCond* nodep) override { @@ -209,21 +209,21 @@ private: UINFO(8, "?\n"); reset(); - iterateAndNextNull(nodep->expr1p()); + iterateAndNextNull(nodep->thenp()); const uint32_t ifCount = m_instrCount; UINFO(8, ":\n"); reset(); - iterateAndNextNull(nodep->expr2p()); + iterateAndNextNull(nodep->elsep()); const uint32_t elseCount = m_instrCount; reset(); if (ifCount < elseCount) { m_instrCount = savedCount + elseCount; - if (nodep->expr1p()) nodep->expr1p()->user4(0); // Don't dump it + if (nodep->thenp()) nodep->thenp()->user4(0); // Don't dump it } else { m_instrCount = savedCount + ifCount; - if (nodep->expr2p()) nodep->expr2p()->user4(0); // Don't dump it + if (nodep->elsep()) nodep->elsep()->user4(0); // Don't dump it } } void visit(AstCAwait* nodep) override { diff --git a/src/V3Life.cpp b/src/V3Life.cpp index a0b436f64..edd9b8d5f 100644 --- a/src/V3Life.cpp +++ b/src/V3Life.cpp @@ -351,7 +351,7 @@ private: LifeBlock* const elseLifep = new LifeBlock(prevLifep, m_statep); { m_lifep = ifLifep; - iterateAndNextNull(nodep->ifsp()); + iterateAndNextNull(nodep->thensp()); } { m_lifep = elseLifep; @@ -386,7 +386,7 @@ private: } { m_lifep = bodyLifep; - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); iterateAndNextNull(nodep->incsp()); } m_lifep = prevLifep; diff --git a/src/V3LinkCells.cpp b/src/V3LinkCells.cpp index 393013026..5057a4090 100644 --- a/src/V3LinkCells.cpp +++ b/src/V3LinkCells.cpp @@ -268,7 +268,7 @@ private: { m_modp = modp; // Important that this adds to end, as next iterate assumes does all cells - modp->addStmtp(cellsp); + modp->addStmtsp(cellsp); iterateAndNextNull(cellsp); } } @@ -321,7 +321,7 @@ private: otherModp->recursiveClone(true); // user1 etc will retain its pre-clone value cellmodp->user2p(otherModp); - v3Global.rootp()->addModulep(otherModp); + v3Global.rootp()->addModulesp(otherModp); new V3GraphEdge(&m_graph, vertex(cellmodp), vertex(otherModp), 1, false); } diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 2825daaf7..57f9fca83 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -994,7 +994,7 @@ class LinkDotFindVisitor final : public VNVisitor { if (!nodep->isExternDef()) { // Move it to proper spot under the target class nodep->unlinkFrBack(); - classp->addStmtp(nodep); + classp->addStmtsp(nodep); nodep->isExternDef(true); // So we check there's a matching extern nodep->classOrPackagep()->unlinkFrBack()->deleteTree(); } @@ -1030,7 +1030,7 @@ class LinkDotFindVisitor final : public VNVisitor { newvarp->funcReturn(true); newvarp->trace(false); // Not user visible newvarp->attrIsolateAssign(nodep->attrIsolateAssign()); - nodep->addFvarp(newvarp); + nodep->fvarp(newvarp); // Explicit insert required, as the var name shadows the upper level's task name m_statep->insertSym(m_curSymp, newvarp->name(), newvarp, nullptr /*classOrPackagep*/); @@ -1938,7 +1938,7 @@ private: VFlagLogicPacked{}, 1); newp->trace(modp->modTrace()); nodep->varp(newp); - modp->addStmtp(newp); + modp->addStmtsp(newp); // Link it to signal list, must add the variable under the module; // current scope might be lower now m_statep->insertSym(moduleSymp, newp->name(), newp, nullptr /*classOrPackagep*/); @@ -3097,7 +3097,7 @@ private: nodep->classOrPackagep(foundp->classOrPackagep()); } } else if (AstClass* const defp = foundp ? VN_AS(foundp->nodep(), Class) : nullptr) { - AstNode* const paramsp = nodep->paramsp(); + AstPin* const paramsp = nodep->paramsp(); if (paramsp) paramsp->unlinkFrBackWithNext(); AstClassRefDType* const newp = new AstClassRefDType{nodep->fileline(), defp, paramsp}; diff --git a/src/V3LinkInc.cpp b/src/V3LinkInc.cpp index 8af3f4152..6726b8668 100644 --- a/src/V3LinkInc.cpp +++ b/src/V3LinkInc.cpp @@ -106,7 +106,7 @@ private: iterateAndNextNull(nodep->condp()); // Body insert just before themselves m_insStmtp = nullptr; // First thing should be new statement - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); iterateAndNextNull(nodep->incsp()); // Done the loop m_insStmtp = nullptr; // Next thing should be new statement @@ -131,7 +131,7 @@ private: m_insStmtp = nodep; iterateAndNextNull(nodep->condp()); m_insStmtp = nullptr; - iterateAndNextNull(nodep->ifsp()); + iterateAndNextNull(nodep->thensp()); iterateAndNextNull(nodep->elsesp()); m_insStmtp = nullptr; } @@ -143,7 +143,7 @@ private: iterateAndNextNull(nodep->condsp()); } m_insStmtp = nullptr; // Next thing should be new statement - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); } void visit(AstNodeFor* nodep) override { // LCOV_EXCL_LINE nodep->v3fatalSrc( @@ -165,7 +165,7 @@ private: m_insStmtp = nodep; iterateAndNextNull(nodep->condp()); m_insStmtp = nullptr; - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); m_insStmtp = nullptr; } void visit(AstNodeStmt* nodep) override { diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 405bcbaec..4bf20ebdc 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -69,7 +69,7 @@ private: underp = VN_AS(nodep, NodeFTask)->stmtsp(); } else if (VN_IS(nodep, Foreach)) { if (endOfIter) { - underp = VN_AS(nodep, Foreach)->bodysp(); + underp = VN_AS(nodep, Foreach)->stmtsp(); } else { underp = nodep; under_and_next = false; // IE we skip the entire foreach @@ -78,7 +78,7 @@ private: if (endOfIter) { // Note we jump to end of bodysp; a FOR loop has its // increment under incsp() which we don't skip - underp = VN_AS(nodep, While)->bodysp(); + underp = VN_AS(nodep, While)->stmtsp(); } else { underp = nodep; under_and_next = false; // IE we skip the entire while @@ -157,7 +157,7 @@ private: AstVar* const varp = new AstVar(nodep->fileline(), VVarType::BLOCKTEMP, name, nodep->findSigned32DType()); varp->usedLoopIdx(true); - m_modp->addStmtp(varp); + m_modp->addStmtsp(varp); AstNode* initsp = new AstAssign( nodep->fileline(), new AstVarRef(nodep->fileline(), varp, VAccess::WRITE), countp); AstNode* const decp = new AstAssign( @@ -167,7 +167,7 @@ private: AstNode* const zerosp = new AstConst(nodep->fileline(), AstConst::Signed32(), 0); AstNode* const condp = new AstGtS( nodep->fileline(), new AstVarRef(nodep->fileline(), varp, VAccess::READ), zerosp); - AstNode* const bodysp = nodep->bodysp(); + AstNode* const bodysp = nodep->stmtsp(); if (bodysp) bodysp->unlinkFrBackWithNext(); AstNode* newp = new AstWhile(nodep->fileline(), condp, bodysp, decp); initsp = initsp->addNext(newp); @@ -184,7 +184,7 @@ private: m_loopInc = false; iterateAndNextNull(nodep->precondsp()); iterateAndNextNull(nodep->condp()); - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); m_loopInc = true; iterateAndNextNull(nodep->incsp()); } @@ -193,7 +193,7 @@ private: VL_RESTORER(m_loopp); { m_loopp = nodep; - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); } } void visit(AstReturn* nodep) override { diff --git a/src/V3LinkLevel.cpp b/src/V3LinkLevel.cpp index 3e9130ed3..9c88973f6 100644 --- a/src/V3LinkLevel.cpp +++ b/src/V3LinkLevel.cpp @@ -76,7 +76,7 @@ void V3LinkLevel::modSortByLevel() { UINFO(9, "modSortByLevel() sorted\n"); // Comment required for gcc4.6.3 / bug666 for (AstNodeModule* nodep : mods) nodep->unlinkFrBack(); UASSERT_OBJ(!v3Global.rootp()->modulesp(), v3Global.rootp(), "Unlink didn't work"); - for (AstNodeModule* nodep : mods) v3Global.rootp()->addModulep(nodep); + for (AstNodeModule* nodep : mods) v3Global.rootp()->addModulesp(nodep); UINFO(9, "modSortByLevel() done\n"); // Comment required for gcc4.6.3 / bug666 V3Global::dumpCheckGlobalTree("cells", false, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); } @@ -154,7 +154,7 @@ void V3LinkLevel::wrapTop(AstNetlist* rootp) { newmodp->modPublic(true); newmodp->protect(false); newmodp->timeunit(oldmodp->timeunit()); - rootp->addModulep(newmodp); + rootp->addModulesp(newmodp); // TODO the module creation above could be done after linkcells, but // the rest must be done after data type resolution @@ -170,7 +170,7 @@ void V3LinkLevel::wrapTop(AstNetlist* rootp) { // with module names/"v" modp->name(), modp->name(), nullptr, nullptr, nullptr); cellp->modp(modp); - newmodp->addStmtp(cellp); + newmodp->addStmtsp(cellp); } } @@ -213,7 +213,7 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { (!v3Global.opt.l2Name().empty() ? v3Global.opt.l2Name() : oldmodp->name()), oldmodp->name(), nullptr, nullptr, nullptr); cellp->modp(oldmodp); - newmodp->addStmtp(cellp); + newmodp->addStmtsp(cellp); // Add pins for (AstNode* subnodep = oldmodp->stmtsp(); subnodep; subnodep = subnodep->nextp()) { @@ -229,7 +229,7 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { AstVar* const varp = oldvarp->cloneTree(false); varp->name(name); varp->protect(false); - newmodp->addStmtp(varp); + newmodp->addStmtsp(varp); varp->sigPublic(true); // User needs to be able to get to it... if (oldvarp->isIO()) { oldvarp->primaryIO(false); diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index e96a67c7b..8dd4d8863 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -372,7 +372,7 @@ private: // lvalue is true, because we know we have a verilator public_flat_rw // but someday we may be more general const bool lvalue = m_varp->isSigUserRWPublic(); - nodep->addStmtp( + nodep->addStmtsp( new AstVarRef(nodep->fileline(), m_varp, lvalue ? VAccess::WRITE : VAccess::READ)); } } @@ -594,7 +594,7 @@ private: sensesp->unlinkFrBackWithNext(); alwaysp->sensesp(sensesp); } - if (nodep->stmtsp()) alwaysp->addStmtp(nodep->stmtsp()->unlinkFrBackWithNext()); + if (nodep->stmtsp()) alwaysp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext()); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } } diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp index ed418f025..38da0ea3f 100644 --- a/src/V3LinkResolve.cpp +++ b/src/V3LinkResolve.cpp @@ -86,7 +86,7 @@ private: // Initial assignments under function/tasks can just be simple // assignments without the initial if (m_ftaskp) { - VL_DO_DANGLING(nodep->replaceWith(nodep->bodysp()->unlinkFrBackWithNext()), nodep); + VL_DO_DANGLING(nodep->replaceWith(nodep->stmtsp()->unlinkFrBackWithNext()), nodep); } } void visit(AstNodeCoverOrAssert* nodep) override { @@ -414,7 +414,7 @@ private: } varoutp = varp; // Tie off - m_modp->addStmtp( + m_modp->addStmtsp( new AstAssignW(varp->fileline(), new AstVarRef(varp->fileline(), varp, VAccess::WRITE), new AstConst(varp->fileline(), AstConst::BitFalse()))); diff --git a/src/V3MergeCond.cpp b/src/V3MergeCond.cpp index 3dd8fe02e..1f99e4570 100644 --- a/src/V3MergeCond.cpp +++ b/src/V3MergeCond.cpp @@ -565,7 +565,7 @@ private: return false; } if (const AstNodeCond* const condp = VN_CAST(nodep, NodeCond)) { - return yieldsOneOrZero(condp->expr1p()) && yieldsOneOrZero(condp->expr2p()); + return yieldsOneOrZero(condp->thenp()) && yieldsOneOrZero(condp->elsep()); } if (const AstCCast* const castp = VN_CAST(nodep, CCast)) { // Cast never sign extends @@ -593,7 +593,7 @@ private: return new AstConst{rhsp->fileline(), AstConst::BitTrue{}, condTrue}; } else if (const AstNodeCond* const condp = extractCondFromRhs(rhsp)) { AstNode* const resp - = condTrue ? condp->expr1p()->unlinkFrBack() : condp->expr2p()->unlinkFrBack(); + = condTrue ? condp->thenp()->unlinkFrBack() : condp->elsep()->unlinkFrBack(); if (condp == rhsp) return resp; if (const AstAnd* const andp = VN_CAST(rhsp, And)) { UASSERT_OBJ(andp->rhsp() == condp, rhsp, "Should not try to fold this"); @@ -676,7 +676,7 @@ private: // Construct the new RHSs and add to branches thenp->rhsp(foldAndUnlink(rhsp, true)); elsep->rhsp(foldAndUnlink(rhsp, false)); - resultp->addIfsp(thenp); + resultp->addThensp(thenp); resultp->addElsesp(elsep); // Cleanup VL_DO_DANGLING(rhsp->deleteTree(), rhsp); @@ -684,8 +684,8 @@ private: AstNodeIf* const ifp = VN_AS(currp, NodeIf); UASSERT_OBJ(ifp, currp, "Must be AstNodeIf"); // Move branch contents under new if - if (AstNode* const listp = ifp->ifsp()) { - resultp->addIfsp(listp->unlinkFrBackWithNext()); + if (AstNode* const listp = ifp->thensp()) { + resultp->addThensp(listp->unlinkFrBackWithNext()); } if (AstNode* const listp = ifp->elsesp()) { resultp->addElsesp(listp->unlinkFrBackWithNext()); @@ -695,7 +695,7 @@ private: } } while (nextp); // Merge the branches of the resulting AstIf after re-analysis - if (resultp->ifsp()) m_workQueuep->push(resultp->ifsp()); + if (resultp->thensp()) m_workQueuep->push(resultp->thensp()); if (resultp->elsesp()) m_workQueuep->push(resultp->elsesp()); } else if (AstNodeIf* const ifp = VN_CAST(m_mgFirstp, NodeIf)) { // There was nothing to merge this AstNodeIf with, so try to merge its branches. @@ -711,7 +711,7 @@ private: AstNode::user2ClearTree(); // Merge recursively within the branches of an un-merged AstNodeIF if (recursivep) { - iterateAndNextNull(recursivep->ifsp()); + iterateAndNextNull(recursivep->thensp()); iterateAndNextNull(recursivep->elsesp()); // Close a pending merge to ensure merge state is // reset as expected at the end of this function @@ -840,7 +840,7 @@ private: // Check if mergeable if (!checkOrMakeMergeable(nodep)) { // If not mergeable, try to merge the branches - iterateAndNextNull(nodep->ifsp()); + iterateAndNextNull(nodep->thensp()); iterateAndNextNull(nodep->elsesp()); return; } diff --git a/src/V3Order.cpp b/src/V3Order.cpp index fd2a4a25e..0c8be135e 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -1235,7 +1235,7 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, if (AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure)) { suspendable = procp->isSuspendable(); if (suspendable) slow = slow && !VN_IS(procp, Always); - nodep = procp->bodysp(); + nodep = procp->stmtsp(); pushDeletep(procp); } @@ -1262,7 +1262,7 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp, newFuncpr->isLoose(true); newFuncpr->slow(slow); newStmtsr = 0; - scopep->addActivep(newFuncpr); + scopep->addBlocksp(newFuncpr); // Create top call to it AstCCall* const callp = new AstCCall{nodep->fileline(), newFuncpr}; // Where will we be adding the call? @@ -1423,7 +1423,7 @@ void OrderProcess::processMTasks() { const MTaskState& fromState = mtaskStates[fromp->id()]; new V3GraphEdge(depGraphp, fromState.m_execMTaskp, state.m_execMTaskp, 1); } - execGraphp->addMTaskBodyp(bodyp); + execGraphp->addMTaskBodiesp(bodyp); } } @@ -1501,7 +1501,7 @@ AstCFunc* order(AstNetlist* netlistp, // funcp->slow(slow); funcp->isConst(false); funcp->declPrivate(true); - scopeTopp->addActivep(funcp); + scopeTopp->addBlocksp(funcp); // Add ordered statements to the result function for (AstNode* const nodep : nodeps) funcp->addStmtsp(nodep); diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 3a21266fc..4c372895e 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1132,7 +1132,7 @@ class ParamVisitor final : public VNVisitor { // NOT recurse the body. V3Const::constifyGenerateParamsEdit(nodep->condp()); // condp may change if (const AstConst* const constp = VN_CAST(nodep->condp(), Const)) { - if (AstNode* const keepp = (constp->isZero() ? nodep->elsesp() : nodep->ifsp())) { + if (AstNode* const keepp = (constp->isZero() ? nodep->elsesp() : nodep->thensp())) { keepp->unlinkFrBackWithNext(); nodep->replaceWith(keepp); } else { @@ -1150,9 +1150,7 @@ class ParamVisitor final : public VNVisitor { //! expression, since this is currently restricted to simple comparisons. If we ever do //! move to more generic constant expressions, such code will be needed here. void visit(AstBegin* nodep) override { - if (nodep->genforp()) { - AstGenFor* const forp = VN_AS(nodep->genforp(), GenFor); - UASSERT_OBJ(forp, nodep, "Non-GENFOR under generate-for BEGIN"); + if (AstGenFor* const forp = VN_AS(nodep->genforp(), GenFor)) { // We should have a GENFOR under here. We will be replacing the begin, // so process here rather than at the generate to avoid iteration problems UINFO(9, " BEGIN " << nodep << endl); @@ -1211,7 +1209,7 @@ class ParamVisitor final : public VNVisitor { if (const AstConst* const ccondp = VN_CAST(ep, Const)) { V3Number match(nodep, 1); match.opEq(ccondp->num(), exprp->num()); - if (!keepp && match.isNeqZero()) keepp = itemp->bodysp(); + if (!keepp && match.isNeqZero()) keepp = itemp->stmtsp(); } else { itemp->v3error("Generate Case item does not evaluate to constant"); } @@ -1222,7 +1220,7 @@ class ParamVisitor final : public VNVisitor { for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { if (itemp->isDefault()) { - if (!keepp) keepp = itemp->bodysp(); + if (!keepp) keepp = itemp->stmtsp(); } } // Replace @@ -1269,7 +1267,7 @@ public: }); // Re-insert modules - for (AstNodeModule* const modp : modps) netlistp->addModulep(modp); + for (AstNodeModule* const modp : modps) netlistp->addModulesp(modp); } } ~ParamVisitor() override = default; diff --git a/src/V3ParseImp.cpp b/src/V3ParseImp.cpp index 37da5a1fc..db0a0f422 100644 --- a/src/V3ParseImp.cpp +++ b/src/V3ParseImp.cpp @@ -283,7 +283,7 @@ void V3ParseImp::parseFile(FileLine* fileline, const string& modfilename, bool i if (errmsg != "") return; // Threw error already // Create fake node for later error reporting AstNodeModule* const nodep = new AstNotFoundModule(fileline, modname); - v3Global.rootp()->addModulep(nodep); + v3Global.rootp()->addModulesp(nodep); return; } diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index baefa2ea3..4eb861a51 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -3072,7 +3072,7 @@ static void addMTaskToFunction(const ThreadSchedule& schedule, const uint32_t th AstVar* const varp = new AstVar(fl, VVarType::MODULETEMP, name, mtaskStateDtypep); varp->valuep(new AstConst(fl, nDependencies)); varp->protect(false); // Do not protect as we still have references in AstText - modp->addStmtp(varp); + modp->addStmtsp(varp); // For now, reference is still via text bashing addStrStmt("vlSelf->" + name + +".waitUntilUpstreamDone(even_cycle);\n"); } @@ -3133,7 +3133,7 @@ static const std::vector createThreadFunctions(const ThreadSchedule& const uint32_t threadId = schedule.threadId(thread.front()); const string name{"__Vthread__" + tag + "__" + cvtToStr(threadId)}; AstCFunc* const funcp = new AstCFunc(fl, name, nullptr, "void"); - modp->addStmtp(funcp); + modp->addStmtsp(funcp); funcps.push_back(funcp); funcp->isStatic(true); // Uses void self pointer, so static and hand rolled funcp->isLoose(true); @@ -3161,7 +3161,7 @@ static const std::vector createThreadFunctions(const ThreadSchedule& = new AstVar{fl, VVarType::MODULETEMP, "__Vm_mtaskstate_final__" + tag, mtaskStateDtypep}; varp->valuep(new AstConst(fl, funcps.size())); varp->protect(false); // Do not protect as we still have references in AstText - modp->addStmtp(varp); + modp->addStmtsp(varp); return funcps; } diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 842b8d720..54b1324c4 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -176,7 +176,7 @@ private: iterateAndNextNull(nodep->condp()); m_inWhilep = nullptr; startStatement(nodep); - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); iterateAndNextNull(nodep->incsp()); m_stmtp = nullptr; } @@ -329,7 +329,7 @@ private: } void visit(AstNodeCond* nodep) override { iterateChildren(nodep); - if (nodep->expr1p()->isWide() && !VN_IS(nodep->condp(), Const) + if (nodep->thenp()->isWide() && !VN_IS(nodep->condp(), Const) && !VN_IS(nodep->condp(), VarRef)) { // We're going to need the expression several times in the expanded code, // so might as well make it a common expression diff --git a/src/V3ProtectLib.cpp b/src/V3ProtectLib.cpp index 21abed5a7..734695c2f 100644 --- a/src/V3ProtectLib.cpp +++ b/src/V3ProtectLib.cpp @@ -95,7 +95,7 @@ private: } void addComment(AstTextBlock* txtp, FileLine* fl, const string& comment) { - txtp->addNodep(new AstComment{fl, comment}); + txtp->addNodesp(new AstComment{fl, comment}); } void hashComment(AstTextBlock* txtp, FileLine* fl) { @@ -143,7 +143,7 @@ private: // Module declaration m_modPortsp = new AstTextBlock{fl, "module " + m_libName + " (\n", false, true}; - txtp->addNodep(m_modPortsp); + txtp->addNodesp(m_modPortsp); txtp->addText(fl, ");\n\n"); // Timescale @@ -177,7 +177,7 @@ private: "(\n", false, true}; m_comboPortsp->addText(fl, "chandle handle__V\n"); - txtp->addNodep(m_comboPortsp); + txtp->addNodesp(m_comboPortsp); txtp->addText(fl, ");\n\n"); seqComment(txtp, fl); if (m_hasClk) { @@ -187,7 +187,7 @@ private: "(\n", false, true}; m_seqPortsp->addText(fl, "chandle handle__V\n"); - txtp->addNodep(m_seqPortsp); + txtp->addNodesp(m_seqPortsp); txtp->addText(fl, ");\n\n"); } comboIgnoreComment(txtp, fl); @@ -197,7 +197,7 @@ private: "(\n", false, true}; m_comboIgnorePortsp->addText(fl, "chandle handle__V\n"); - txtp->addNodep(m_comboIgnorePortsp); + txtp->addNodesp(m_comboIgnorePortsp); txtp->addText(fl, ");\n\n"); finalComment(txtp, fl); @@ -227,17 +227,17 @@ private: txtp->addText(fl, "\n"); m_comboDeclsp = new AstTextBlock{fl}; - txtp->addNodep(m_comboDeclsp); + txtp->addNodesp(m_comboDeclsp); m_seqDeclsp = new AstTextBlock{fl}; - txtp->addNodep(m_seqDeclsp); + txtp->addNodesp(m_seqDeclsp); m_tmpDeclsp = new AstTextBlock{fl}; - txtp->addNodep(m_tmpDeclsp); + txtp->addNodesp(m_tmpDeclsp); // CPP hash value addComment(txtp, fl, "Hash value to make sure this file and the corresponding"); addComment(txtp, fl, "library agree"); m_hashValuep = new AstTextBlock{fl, "localparam int protectlib_hash__V = 32'd"}; - txtp->addNodep(m_hashValuep); + txtp->addNodesp(m_hashValuep); txtp->addText(fl, "\n"); // Initial @@ -256,7 +256,7 @@ private: + m_libName + "_protectlib_combo_update(\n", false, true}; m_comboParamsp->addText(fl, "handle__V\n"); - txtp->addNodep(m_comboParamsp); + txtp->addNodesp(m_comboParamsp); txtp->addText(fl, ");\n"); txtp->addText(fl, "end\n\n"); @@ -264,21 +264,21 @@ private: if (m_hasClk) { addComment(txtp, fl, "Evaluate clock edges"); m_clkSensp = new AstTextBlock{fl, "always @(", false, true}; - txtp->addNodep(m_clkSensp); + txtp->addNodesp(m_clkSensp); txtp->addText(fl, ") begin\n"); m_comboIgnoreParamsp = new AstTextBlock{fl, m_libName + "_protectlib_combo_ignore(\n", false, true}; m_comboIgnoreParamsp->addText(fl, "handle__V\n"); - txtp->addNodep(m_comboIgnoreParamsp); + txtp->addNodesp(m_comboIgnoreParamsp); txtp->addText(fl, ");\n"); m_seqParamsp = new AstTextBlock{ fl, "last_seq_seqnum__V <= " + m_libName + "_protectlib_seq_update(\n", false, true}; m_seqParamsp->addText(fl, "handle__V\n"); - txtp->addNodep(m_seqParamsp); + txtp->addNodesp(m_seqParamsp); txtp->addText(fl, ");\n"); m_nbAssignsp = new AstTextBlock{fl}; - txtp->addNodep(m_nbAssignsp); + txtp->addNodesp(m_nbAssignsp); txtp->addText(fl, "end\n\n"); } @@ -288,13 +288,13 @@ private: if (m_hasClk) { m_seqAssignsp = new AstTextBlock{fl, "if (last_seq_seqnum__V > " "last_combo_seqnum__V) begin\n"}; - txtp->addNodep(m_seqAssignsp); + txtp->addNodesp(m_seqAssignsp); m_comboAssignsp = new AstTextBlock{fl, "end\nelse begin\n"}; - txtp->addNodep(m_comboAssignsp); + txtp->addNodesp(m_comboAssignsp); txtp->addText(fl, "end\n"); } else { m_comboAssignsp = new AstTextBlock{fl, ""}; - txtp->addNodep(m_comboAssignsp); + txtp->addNodesp(m_comboAssignsp); } txtp->addText(fl, "end\n\n"); @@ -341,7 +341,7 @@ private: + "_protectlib_check_hash" "(int protectlib_hash__V) {\n"); m_cHashValuep = new AstTextBlock{fl, "const int expected_hash__V = "}; - txtp->addNodep(m_cHashValuep); + txtp->addNodesp(m_cHashValuep); txtp->addText(fl, /**/ "if (protectlib_hash__V != expected_hash__V) {\n"); txtp->addText(fl, /****/ "fprintf(stderr, \"%%Error: cannot use " + m_libName + " library, " @@ -364,13 +364,13 @@ private: m_cComboParamsp = new AstTextBlock{ fl, "long long " + m_libName + "_protectlib_combo_update(\n", false, true}; m_cComboParamsp->addText(fl, "void* vhandlep__V\n"); - txtp->addNodep(m_cComboParamsp); + txtp->addNodesp(m_cComboParamsp); txtp->addText(fl, ")\n"); m_cComboInsp = new AstTextBlock{fl, "{\n"}; castPtr(fl, m_cComboInsp); - txtp->addNodep(m_cComboInsp); + txtp->addNodesp(m_cComboInsp); m_cComboOutsp = new AstTextBlock{fl, "handlep__V->eval();\n"}; - txtp->addNodep(m_cComboOutsp); + txtp->addNodesp(m_cComboOutsp); txtp->addText(fl, "return handlep__V->m_seqnum++;\n"); txtp->addText(fl, "}\n\n"); @@ -379,13 +379,13 @@ private: m_cSeqParamsp = new AstTextBlock{ fl, "long long " + m_libName + "_protectlib_seq_update(\n", false, true}; m_cSeqParamsp->addText(fl, "void* vhandlep__V\n"); - txtp->addNodep(m_cSeqParamsp); + txtp->addNodesp(m_cSeqParamsp); txtp->addText(fl, ")\n"); m_cSeqClksp = new AstTextBlock{fl, "{\n"}; castPtr(fl, m_cSeqClksp); - txtp->addNodep(m_cSeqClksp); + txtp->addNodesp(m_cSeqClksp); m_cSeqOutsp = new AstTextBlock{fl, "handlep__V->eval();\n"}; - txtp->addNodep(m_cSeqOutsp); + txtp->addNodesp(m_cSeqOutsp); txtp->addText(fl, "return handlep__V->m_seqnum++;\n"); txtp->addText(fl, "}\n\n"); } @@ -394,7 +394,7 @@ private: m_cIgnoreParamsp = new AstTextBlock{ fl, "void " + m_libName + "_protectlib_combo_ignore(\n", false, true}; m_cIgnoreParamsp->addText(fl, "void* vhandlep__V\n"); - txtp->addNodep(m_cIgnoreParamsp); + txtp->addNodesp(m_cIgnoreParamsp); txtp->addText(fl, ")\n"); txtp->addText(fl, "{ }\n\n"); @@ -448,7 +448,7 @@ private: void handleClock(AstVar* varp) { FileLine* const fl = varp->fileline(); handleInput(varp); - m_seqPortsp->addNodep(varp->cloneTree(false)); + m_seqPortsp->addNodesp(varp->cloneTree(false)); if (m_hasClk) { m_seqParamsp->addText(fl, varp->name() + "\n"); m_clkSensp->addText(fl, "posedge " + varp->name() + " or negedge " + varp->name()); @@ -460,30 +460,30 @@ private: void handleDataInput(AstVar* varp) { FileLine* const fl = varp->fileline(); handleInput(varp); - m_comboPortsp->addNodep(varp->cloneTree(false)); + m_comboPortsp->addNodesp(varp->cloneTree(false)); m_comboParamsp->addText(fl, varp->name() + "\n"); - m_comboIgnorePortsp->addNodep(varp->cloneTree(false)); + m_comboIgnorePortsp->addNodesp(varp->cloneTree(false)); if (m_hasClk) m_comboIgnoreParamsp->addText(fl, varp->name() + "\n"); m_cComboParamsp->addText(fl, varp->dpiArgType(true, false) + "\n"); m_cComboInsp->addText(fl, cInputConnection(varp)); m_cIgnoreParamsp->addText(fl, varp->dpiArgType(true, false) + "\n"); } - void handleInput(AstVar* varp) { m_modPortsp->addNodep(varp->cloneTree(false)); } + void handleInput(AstVar* varp) { m_modPortsp->addNodesp(varp->cloneTree(false)); } static void addLocalVariable(AstTextBlock* textp, AstVar* varp, const char* suffix) { AstVar* const newVarp = new AstVar{varp->fileline(), VVarType::VAR, varp->name() + suffix, varp->dtypep()}; - textp->addNodep(newVarp); + textp->addNodesp(newVarp); } void handleOutput(AstVar* varp) { FileLine* const fl = varp->fileline(); - m_modPortsp->addNodep(varp->cloneTree(false)); - m_comboPortsp->addNodep(varp->cloneTree(false)); + m_modPortsp->addNodesp(varp->cloneTree(false)); + m_comboPortsp->addNodesp(varp->cloneTree(false)); m_comboParamsp->addText(fl, varp->name() + "_combo__V\n"); if (m_hasClk) { - m_seqPortsp->addNodep(varp->cloneTree(false)); + m_seqPortsp->addNodesp(varp->cloneTree(false)); m_seqParamsp->addText(fl, varp->name() + "_tmp__V\n"); } diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 448623a08..7efc30c0f 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -144,7 +144,7 @@ private: varp->isStatic(true); varp->valuep(initp); // Add to root, as don't know module we are in, and aids later structure sharing - v3Global.rootp()->dollarUnitPkgAddp()->addStmtp(varp); + v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(varp); UASSERT_OBJ(nodep->itemsp(), nodep, "Enum without items"); for (AstEnumItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), EnumItem)) { diff --git a/src/V3Reloop.cpp b/src/V3Reloop.cpp index e163bdf8c..4e8690b3e 100644 --- a/src/V3Reloop.cpp +++ b/src/V3Reloop.cpp @@ -114,7 +114,7 @@ private: AstWhile* const whilep = new AstWhile(fl, condp, nullptr, incp); initp->addNext(whilep); bodyp->replaceWith(initp); - whilep->addBodysp(bodyp); + whilep->addStmtsp(bodyp); // Replace constant index with new loop index AstNode* const offsetp diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index c6b08a569..df3e5f0a4 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -65,7 +65,7 @@ AstCFunc* makeSubFunction(AstNetlist* netlistp, const string& name, bool slow) { funcp->slow(slow); funcp->isConst(false); funcp->declPrivate(true); - scopeTopp->addActivep(funcp); + scopeTopp->addBlocksp(funcp); return funcp; } @@ -143,7 +143,7 @@ void splitCheck(AstCFunc* ofuncp) { funcp->isStatic(false); funcp->isLoose(true); funcp->slow(ofuncp->slow()); - ofuncp->scopep()->addActivep(funcp); + ofuncp->scopep()->addBlocksp(funcp); // AstCCall* const callp = new AstCCall{funcp->fileline(), funcp}; ofuncp->addStmtsp(callp); @@ -209,7 +209,7 @@ void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) { subFuncp->isConst(false); subFuncp->declPrivate(true); subFuncp->slow(funcp->slow()); - scopep->addActivep(subFuncp); + scopep->addBlocksp(subFuncp); // Call it from the top function funcp->addStmtsp(new AstCCall{scopep->fileline(), subFuncp}); return subFuncp; @@ -226,7 +226,7 @@ void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) { auto* subFuncp = VN_AS(scopep->user1p(), CFunc); nextp = logicp->nextp(); if (AstNodeProcedure* const procp = VN_CAST(logicp, NodeProcedure)) { - if (AstNode* bodyp = procp->bodysp()) { + if (AstNode* bodyp = procp->stmtsp()) { bodyp->unlinkFrBackWithNext(); // If the process is suspendable, we need a separate function (a coroutine) if (procp->isSuspendable()) { @@ -326,7 +326,7 @@ AstSenTree* createTriggerSenTree(AstNetlist* netlistp, AstVarScope* const vscp, callp->pure(true); AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, callp}; AstSenTree* const resultp = new AstSenTree{flp, senItemp}; - topScopep->addSenTreep(resultp); + topScopep->addSenTreesp(resultp); return resultp; } @@ -409,7 +409,7 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui AstIf* const ifp = new AstIf{flp, getTrigRef(index, VAccess::READ)}; dumpp->addStmtsp(ifp); - ifp->addIfsp(new AstText{flp, message, true}); + ifp->addThensp(new AstText{flp, message, true}); }; // Add a print for each of the extra triggers @@ -428,7 +428,7 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui AstCMethodHard* const senp = getTrigRef(triggerNumber, VAccess::READ); AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_TRUE, senp}; AstSenTree* const trigpSenp = new AstSenTree{flp, senItemp}; - topScopep->addSenTreep(trigpSenp); + topScopep->addSenTreesp(trigpSenp); map[senTreep] = trigpSenp; // Add the trigger computation @@ -479,8 +479,8 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui AstIf* const ifp = new AstIf{flp, new AstNot{flp, condp}}; funcp->addStmtsp(ifp); ifp->branchPred(VBranchPred::BP_UNLIKELY); - ifp->addIfsp(setVar(vscp, 1)); - ifp->addIfsp(initialTrigsp); + ifp->addThensp(setVar(vscp, 1)); + ifp->addThensp(initialTrigsp); } // Add a call to the dumping function if debug is enabled @@ -490,7 +490,7 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui const auto add = [&](const string& text) { blockp->addText(flp, text, true); }; add("#ifdef VL_DEBUG\n"); add("if (VL_UNLIKELY(vlSymsp->_vm_contextp__->debug())) {\n"); - blockp->addNodep(new AstCCall{flp, dumpp}); + blockp->addNodesp(new AstCCall{flp, dumpp}); add("}\n"); add("#endif\n"); } @@ -515,7 +515,7 @@ AstNode* buildLoop(AstNetlist* netlistp, const string& name, AstWhile* const loopp = new AstWhile{flp, new AstVarRef{flp, condp, VAccess::READ}}; resp->addNext(loopp); // Clear the loop condition variable in the loop - loopp->addBodysp(setVar(condp, 0)); + loopp->addStmtsp(setVar(condp, 0)); // Build the body build(condp, loopp); // Done @@ -537,15 +537,15 @@ std::pair makeEvalLoop(AstNetlist* netlistp, const strin AstNode* nodep = setVar(counterp, 0); nodep->addNext(buildLoop(netlistp, tag, [&](AstVarScope* continuep, AstWhile* loopp) { // Compute triggers - loopp->addBodysp(computeTriggers()); + loopp->addStmtsp(computeTriggers()); // Invoke body if triggered { AstVarRef* const refp = new AstVarRef{flp, trigVscp, VAccess::READ}; AstCMethodHard* const callp = new AstCMethodHard{flp, refp, "any"}; callp->dtypeSetBit(); AstIf* const ifp = new AstIf{flp, callp}; - loopp->addBodysp(ifp); - ifp->addIfsp(setVar(continuep, 1)); + loopp->addStmtsp(ifp); + ifp->addThensp(setVar(continuep, 1)); // If we exceeded the iteration limit, die { @@ -555,15 +555,15 @@ std::pair makeEvalLoop(AstNetlist* netlistp, const strin constp->num().setLong(limit); AstNodeMath* const condp = new AstGt{flp, refp, constp}; AstIf* const failp = new AstIf{flp, condp}; - ifp->addIfsp(failp); + ifp->addThensp(failp); AstTextBlock* const blockp = new AstTextBlock{flp}; - failp->addIfsp(blockp); + failp->addThensp(blockp); FileLine* const locp = netlistp->topModulep()->fileline(); const string& file = EmitCBaseVisitor::protect(locp->filename()); const string& line = cvtToStr(locp->lineno()); const auto add = [&](const string& text) { blockp->addText(flp, text, true); }; add("#ifdef VL_DEBUG\n"); - blockp->addNodep(new AstCCall{flp, trigDumpp}); + blockp->addNodesp(new AstCCall{flp, trigDumpp}); add("#endif\n"); add("VL_FATAL_MT(\"" + file + "\", " + line + ", \"\", "); add("\"" + name + " region did not converge.\");\n"); @@ -575,11 +575,11 @@ std::pair makeEvalLoop(AstNetlist* netlistp, const strin AstVarRef* const rrefp = new AstVarRef{flp, counterp, VAccess::READ}; AstConst* const onep = new AstConst{flp, AstConst::DTyped{}, counterp->dtypep()}; onep->num().setLong(1); - ifp->addIfsp(new AstAssign{flp, wrefp, new AstAdd{flp, rrefp, onep}}); + ifp->addThensp(new AstAssign{flp, wrefp, new AstAdd{flp, rrefp, onep}}); } // Add body - ifp->addIfsp(makeBody()); + ifp->addThensp(makeBody()); } })); @@ -957,7 +957,7 @@ void schedule(AstNetlist* netlistp) { refp->replaceWith(new AstVarRef{refp->fileline(), vscp, VAccess::READ}); deleter.pushDeletep(refp); }); - topScopep->addSenTreep(pair.second); + topScopep->addSenTreesp(pair.second); } return newMap; }; diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index d091718e3..93f27062d 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -65,7 +65,7 @@ AstCCall* TimingKit::createResume(AstNetlist* const netlistp) { m_resumeFuncp->isLoose(true); m_resumeFuncp->isConst(false); m_resumeFuncp->declPrivate(true); - scopeTopp->addActivep(m_resumeFuncp); + scopeTopp->addBlocksp(m_resumeFuncp); for (auto& p : m_lbs) { // Put all the timing actives in the resume function AstActive* const activep = p.second; @@ -98,7 +98,7 @@ AstCCall* TimingKit::createCommit(AstNetlist* const netlistp) { m_commitFuncp->isLoose(true); m_commitFuncp->isConst(false); m_commitFuncp->declPrivate(true); - scopeTopp->addActivep(m_commitFuncp); + scopeTopp->addBlocksp(m_commitFuncp); } AstSenTree* const sensesp = activep->sensesp(); FileLine* const flp = sensesp->fileline(); @@ -289,7 +289,7 @@ void transformForks(AstNetlist* const netlistp) { funcp->addArgsp(newvarp); AstVarScope* const newvscp = new AstVarScope{newvarp->fileline(), funcp->scopep(), newvarp}; - funcp->scopep()->addVarp(newvscp); + funcp->scopep()->addVarsp(newvscp); vscp->user2p(newvscp); callp->addArgsp(new AstVarRef{refp->fileline(), vscp, VAccess::READ}); } diff --git a/src/V3Scope.cpp b/src/V3Scope.cpp index 0c4bad466..8077d2959 100644 --- a/src/V3Scope.cpp +++ b/src/V3Scope.cpp @@ -135,7 +135,7 @@ private: if (m_modp->isTop()) { v3Global.rootp()->createTopScope(m_scopep); } else { - m_modp->addStmtp(m_scopep); + m_modp->addStmtsp(m_scopep); } // Copy blocks into this scope @@ -188,7 +188,7 @@ private: UINFO(4, " Move " << nodep << endl); AstNode* const clonep = nodep->cloneTree(false); nodep->user2p(clonep); - m_scopep->addActivep(clonep); + m_scopep->addBlocksp(clonep); iterateChildren(clonep); // We iterate under the *clone* } void visit(AstAssignAlias* nodep) override { @@ -196,7 +196,7 @@ private: UINFO(4, " Move " << nodep << endl); AstNode* const clonep = nodep->cloneTree(false); nodep->user2p(clonep); - m_scopep->addActivep(clonep); + m_scopep->addBlocksp(clonep); iterateChildren(clonep); // We iterate under the *clone* } void visit(AstAssignVarScope* nodep) override { @@ -204,7 +204,7 @@ private: UINFO(4, " Move " << nodep << endl); AstNode* const clonep = nodep->cloneTree(false); nodep->user2p(clonep); - m_scopep->addActivep(clonep); + m_scopep->addBlocksp(clonep); iterateChildren(clonep); // We iterate under the *clone* } void visit(AstAssignW* nodep) override { @@ -212,7 +212,7 @@ private: UINFO(4, " Move " << nodep << endl); AstNode* const clonep = nodep->cloneTree(false); nodep->user2p(clonep); - m_scopep->addActivep(clonep); + m_scopep->addBlocksp(clonep); iterateChildren(clonep); // We iterate under the *clone* } void visit(AstAlwaysPublic* nodep) override { @@ -220,7 +220,7 @@ private: UINFO(4, " Move " << nodep << endl); AstNode* const clonep = nodep->cloneTree(false); nodep->user2p(clonep); - m_scopep->addActivep(clonep); + m_scopep->addBlocksp(clonep); iterateChildren(clonep); // We iterate under the *clone* } void visit(AstCoverToggle* nodep) override { @@ -228,7 +228,7 @@ private: UINFO(4, " Move " << nodep << endl); AstNode* const clonep = nodep->cloneTree(false); nodep->user2p(clonep); - m_scopep->addActivep(clonep); + m_scopep->addBlocksp(clonep); iterateChildren(clonep); // We iterate under the *clone* } void visit(AstCFunc* nodep) override { @@ -236,7 +236,7 @@ private: UINFO(4, " CFUNC " << nodep << endl); AstCFunc* const clonep = nodep->cloneTree(false); nodep->user2p(clonep); - m_scopep->addActivep(clonep); + m_scopep->addBlocksp(clonep); clonep->scopep(m_scopep); // We iterate under the *clone* iterateChildren(clonep); @@ -253,7 +253,7 @@ private: clonep = nodep->cloneTree(false); } nodep->user2p(clonep); - m_scopep->addActivep(clonep); + m_scopep->addBlocksp(clonep); // We iterate under the *clone* iterateChildren(clonep); } @@ -272,7 +272,7 @@ private: } UASSERT_OBJ(m_scopep, nodep, "No scope for var"); m_varScopes.emplace(std::make_pair(nodep, m_scopep), varscp); - m_scopep->addVarp(varscp); + m_scopep->addVarsp(varscp); } } void visit(AstVarRef* nodep) override { @@ -295,14 +295,14 @@ private: // TOP and above will be the user's name(). // Note 'TOP.' is stripped by scopePrettyName // To keep correct visual order, must add before other Text's - AstNode* afterp = nodep->scopeAttrp(); + AstText* afterp = nodep->scopeAttrp(); if (afterp) afterp->unlinkFrBackWithNext(); - nodep->scopeAttrp(new AstText(nodep->fileline(), prefix)); - if (afterp) nodep->scopeAttrp(afterp); + nodep->addScopeAttrp(new AstText(nodep->fileline(), prefix)); + if (afterp) nodep->addScopeAttrp(afterp); afterp = nodep->scopeEntrp(); if (afterp) afterp->unlinkFrBackWithNext(); - nodep->scopeEntrp(new AstText(nodep->fileline(), prefix)); - if (afterp) nodep->scopeEntrp(afterp); + nodep->addScopeEntrp(new AstText(nodep->fileline(), prefix)); + if (afterp) nodep->addScopeEntrp(afterp); iterateChildren(nodep); } void visit(AstScope* nodep) override { diff --git a/src/V3SenExprBuilder.h b/src/V3SenExprBuilder.h index 59592a29f..b5341a157 100644 --- a/src/V3SenExprBuilder.h +++ b/src/V3SenExprBuilder.h @@ -77,7 +77,7 @@ class SenExprBuilder final { varp->funcLocal(true); m_locals.push_back(varp); AstVarScope* vscp = new AstVarScope{flp, m_scopeTopp, varp}; - m_scopeTopp->addVarp(vscp); + m_scopeTopp->addVarsp(vscp); result.first->second = vscp; } AstVarScope* const currp = result.first->second; @@ -178,16 +178,16 @@ class SenExprBuilder final { // Clear 'fired' state when done AstCMethodHard* const clearp = new AstCMethodHard{flp, currp(), "clearFired"}; - ifp->addIfsp(clearp); + ifp->addThensp(clearp); clearp->dtypeSetVoid(); clearp->statement(true); // Enqueue for clearing 'triggered' state on next eval AstTextBlock* const blockp = new AstTextBlock{flp}; - ifp->addIfsp(blockp); + ifp->addThensp(blockp); const auto add = [&](const string& text) { blockp->addText(flp, text, true); }; add("vlSymsp->enqueueTriggeredEventForClearing("); - blockp->addNodep(currp()); + blockp->addNodesp(currp()); add(");\n"); } diff --git a/src/V3SenTree.h b/src/V3SenTree.h index 864cea0a4..698406c74 100644 --- a/src/V3SenTree.h +++ b/src/V3SenTree.h @@ -77,7 +77,7 @@ public: // Not found, create a new one AstSenTree* const newSenTreep = senTreep->cloneTree(false); - m_topScopep->addSenTreep(newSenTreep); + m_topScopep->addSenTreesp(newSenTreep); m_trees.emplace(*newSenTreep); return newSenTreep; } diff --git a/src/V3Simulate.h b/src/V3Simulate.h index 98d44a3c8..ee4b23caf 100644 --- a/src/V3Simulate.h +++ b/src/V3Simulate.h @@ -513,7 +513,7 @@ private: iterateAndNextNull(nodep->condp()); if (optimizable()) { if (fetchConst(nodep->condp())->num().isNeqZero()) { - iterateAndNextNull(nodep->ifsp()); + iterateAndNextNull(nodep->thensp()); } else { iterateAndNextNull(nodep->elsesp()); } @@ -647,11 +647,11 @@ private: iterate(nodep->condp()); if (optimizable()) { if (fetchConst(nodep->condp())->num().isNeqZero()) { - iterate(nodep->expr1p()); - newValue(nodep, fetchValue(nodep->expr1p())); + iterate(nodep->thenp()); + newValue(nodep, fetchValue(nodep->thenp())); } else { - iterate(nodep->expr2p()); - newValue(nodep, fetchValue(nodep->expr2p())); + iterate(nodep->elsep()); + newValue(nodep, fetchValue(nodep->elsep())); } } } @@ -835,7 +835,7 @@ private: V3Number match{nodep, 1}; match.opEq(fetchConst(nodep->exprp())->num(), fetchConst(ep)->num()); if (match.isNeqZero()) { - iterateAndNextNull(itemp->bodysp()); + iterateAndNextNull(itemp->stmtsp()); hit = true; } } @@ -847,7 +847,7 @@ private: itemp = VN_AS(itemp->nextp(), CaseItem)) { if (hit) break; if (!hit && itemp->isDefault()) { - iterateAndNextNull(itemp->bodysp()); + iterateAndNextNull(itemp->stmtsp()); hit = true; } } @@ -917,7 +917,7 @@ private: if (!fetchConst(nodep->condp())->num().isNeqZero()) { // break; } - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); iterateAndNextNull(nodep->incsp()); if (loops++ > unrollCount() * 16) { clearOptimizable(nodep, "Loop unrolling took too long; probably this is an" @@ -952,7 +952,7 @@ private: if (!fetchConst(nodep->condp())->num().isNeqZero()) { // break; } - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); if (jumpingOver(nodep)) break; iterateAndNextNull(nodep->incsp()); if (jumpingOver(nodep)) break; diff --git a/src/V3Slice.cpp b/src/V3Slice.cpp index f362c3cb7..cd977626f 100644 --- a/src/V3Slice.cpp +++ b/src/V3Slice.cpp @@ -101,8 +101,8 @@ class SliceVisitor final : public VNVisitor { } else if (AstNodeCond* const snodep = VN_CAST(nodep, NodeCond)) { UINFO(9, " cloneCond(" << elements << "," << offset << ") " << nodep << endl); return snodep->cloneType(snodep->condp()->cloneTree(false), - cloneAndSel(snodep->expr1p(), elements, offset), - cloneAndSel(snodep->expr2p(), elements, offset)); + cloneAndSel(snodep->thenp(), elements, offset), + cloneAndSel(snodep->elsep(), elements, offset)); } else if (const AstSliceSel* const snodep = VN_CAST(nodep, SliceSel)) { UINFO(9, " cloneSliceSel(" << elements << "," << offset << ") " << nodep << endl); const int leOffset = (snodep->declRange().lo() diff --git a/src/V3Split.cpp b/src/V3Split.cpp index 8b9c2f64a..f2f2fe03a 100644 --- a/src/V3Split.cpp +++ b/src/V3Split.cpp @@ -605,14 +605,14 @@ protected: UINFO(4, " ALW " << nodep << endl); if (debug() >= 9) nodep->dumpTree(cout, " alwIn:: "); scoreboardClear(); - processBlock(nodep->bodysp()); + processBlock(nodep->stmtsp()); if (debug() >= 9) nodep->dumpTree(cout, " alwOut: "); } void visit(AstNodeIf* nodep) override { UINFO(4, " IF " << nodep << endl); iterateAndNextNull(nodep->condp()); - processBlock(nodep->ifsp()); + processBlock(nodep->thensp()); processBlock(nodep->elsesp()); } @@ -717,14 +717,14 @@ public: // Put a placeholder node into stmtp to track our position. // We'll strip these out after the blocks are fully cloned. AstSplitPlaceholder* const placeholderp = makePlaceholderp(); - alwaysp->addStmtp(placeholderp); + alwaysp->addStmtsp(placeholderp); m_addAfter[color] = placeholderp; m_newBlocksp->push_back(alwaysp); } // Scan the body of the always. We'll handle if/else // specially, everything else is a leaf node that we can // just clone into one of the split always blocks. - iterateAndNextNull(m_origAlwaysp->bodysp()); + iterateAndNextNull(m_origAlwaysp->stmtsp()); } protected: @@ -780,7 +780,7 @@ protected: m_addAfter[color] = if_placeholderp; } - iterateAndNextNull(nodep->ifsp()); + iterateAndNextNull(nodep->thensp()); for (const auto& color : colors) m_addAfter[color] = clones[color]->elsesp(); @@ -808,7 +808,7 @@ class RemovePlaceholdersVisitor final : public VNVisitor { VL_RESTORER(m_isPure); m_isPure = true; iterateChildren(nodep); - if (!nodep->ifsp() && !nodep->elsesp() && m_isPure) pushDeletep(nodep->unlinkFrBack()); + if (!nodep->thensp() && !nodep->elsesp() && m_isPure) pushDeletep(nodep->unlinkFrBack()); } void visit(AstAlways* nodep) override { VL_RESTORER(m_isPure); @@ -816,7 +816,7 @@ class RemovePlaceholdersVisitor final : public VNVisitor { iterateChildren(nodep); if (m_isPure) { bool emptyOrCommentOnly = true; - for (AstNode* bodysp = nodep->bodysp(); bodysp; bodysp = bodysp->nextp()) { + for (AstNode* bodysp = nodep->stmtsp(); bodysp; bodysp = bodysp->nextp()) { // If this always block contains only AstComment, remove here. // V3Gate will remove anyway. if (!VN_IS(bodysp, Comment)) { @@ -959,7 +959,7 @@ protected: void visit(AstAlways* nodep) override { // build the scoreboard scoreboardClear(); - scanBlock(nodep->bodysp()); + scanBlock(nodep->stmtsp()); if (m_noReorderWhy != "") { // We saw a jump or something else rare that we don't handle. @@ -993,7 +993,7 @@ protected: m_curIfConditional = nodep; iterateAndNextNull(nodep->condp()); m_curIfConditional = nullptr; - scanBlock(nodep->ifsp()); + scanBlock(nodep->thensp()); scanBlock(nodep->elsesp()); } diff --git a/src/V3SplitVar.cpp b/src/V3SplitVar.cpp index 47dc0e006..502058e37 100644 --- a/src/V3SplitVar.cpp +++ b/src/V3SplitVar.cpp @@ -191,16 +191,16 @@ struct SplitVarImpl { template void insertBeginCore(T_ALWAYSLIKE* ap, AstNodeStmt* stmtp, AstNodeModule* modp) { - if (ap->isJustOneBodyStmt() && ap->bodysp() == stmtp) { + if (ap->isJustOneBodyStmt() && ap->stmtsp() == stmtp) { stmtp->unlinkFrBack(); // Insert begin-end because temp value may be inserted to this block later. const std::string name = "__VsplitVarBlk" + cvtToStr(modp->user1Inc(1)); - ap->addStmtp(new AstBegin{ap->fileline(), name, stmtp}); + ap->addStmtsp(new AstBegin{ap->fileline(), name, stmtp}); } } void insertBeginCore(AstInitial* initp, AstNodeStmt* stmtp, AstNodeModule* modp) { - if (initp->isJustOneBodyStmt() && initp->bodysp() == stmtp) { + if (initp->isJustOneBodyStmt() && initp->stmtsp() == stmtp) { stmtp->unlinkFrBack(); // Insert begin-end because temp value may be inserted to this block later. FileLine* const fl = initp->fileline(); @@ -477,7 +477,7 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl { if (nodep->sensesp()) { // When visiting sensitivity list, always is the context setContextAndIterate(nodep, nodep->sensesp()); } - for (AstNode* bodysp = nodep->bodysp(); bodysp; bodysp = bodysp->nextp()) { + for (AstNode* bodysp = nodep->stmtsp(); bodysp; bodysp = bodysp->nextp()) { iterate(bodysp); } }; @@ -485,7 +485,7 @@ class SplitUnpackedVarVisitor final : public VNVisitor, public SplitVarImpl { if (nodep->sensesp()) { // When visiting sensitivity list, always is the context setContextAndIterate(nodep, nodep->sensesp()); } - for (AstNode* bodysp = nodep->bodysp(); bodysp; bodysp = bodysp->nextp()) { + for (AstNode* bodysp = nodep->stmtsp(); bodysp; bodysp = bodysp->nextp()) { iterate(bodysp); } } diff --git a/src/V3Stats.cpp b/src/V3Stats.cpp index 01d3ec2f2..5ec58dfdc 100644 --- a/src/V3Stats.cpp +++ b/src/V3Stats.cpp @@ -141,7 +141,7 @@ private: { m_counting = false; m_instrs = 0.0; - iterateAndNextConstNull(nodep->ifsp()); + iterateAndNextConstNull(nodep->thensp()); ifInstrs = m_instrs; } } @@ -158,7 +158,7 @@ private: // Now collect the stats if (m_counting) { if (ifInstrs >= elseInstrs) { - iterateAndNextConstNull(nodep->ifsp()); + iterateAndNextConstNull(nodep->thensp()); } else { iterateAndNextConstNull(nodep->elsesp()); } diff --git a/src/V3Table.cpp b/src/V3Table.cpp index 76fcb3711..1e829f32e 100644 --- a/src/V3Table.cpp +++ b/src/V3Table.cpp @@ -253,9 +253,9 @@ private: AstVar* const indexVarp = new AstVar{fl, VVarType::BLOCKTEMP, "__Vtableidx" + cvtToStr(m_modTables), VFlagBitPacked{}, static_cast(m_inWidthBits)}; - m_modp->addStmtp(indexVarp); + m_modp->addStmtsp(indexVarp); AstVarScope* const indexVscp = new AstVarScope{indexVarp->fileline(), m_scopep, indexVarp}; - m_scopep->addVarp(indexVscp); + m_scopep->addVarsp(indexVscp); // The 'output assigned' table builder TableBuilder outputAssignedTableBuilder{fl}; @@ -274,8 +274,8 @@ private: // Link it in. // Keep sensitivity list, but delete all else - nodep->bodysp()->unlinkFrBackWithNext()->deleteTree(); - nodep->addStmtp(stmtsp); + nodep->stmtsp()->unlinkFrBackWithNext()->deleteTree(); + nodep->addStmtsp(stmtsp); if (debug() >= 6) nodep->dumpTree(cout, " table_new: "); } diff --git a/src/V3Task.cpp b/src/V3Task.cpp index b952d40aa..91ab55ce2 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -250,7 +250,7 @@ private: } UASSERT_OBJ(m_ctorp, nodep, "class constructor missing"); // LinkDot always makes it for (AstInitialAutomatic* initialp : m_initialps) { - if (AstNode* const newp = initialp->bodysp()) { + if (AstNode* const newp = initialp->stmtsp()) { newp->unlinkFrBackWithNext(); if (!m_ctorp->stmtsp()) { m_ctorp->addStmtsp(newp); @@ -372,7 +372,7 @@ private: newvarp->funcLocal(true); funcp->addInitsp(newvarp); AstVarScope* const newvscp = new AstVarScope(funcp->fileline(), m_scopep, newvarp); - m_scopep->addVarp(newvscp); + m_scopep->addVarsp(newvscp); return newvscp; } AstVarScope* createInputVar(AstCFunc* funcp, const string& name, VBasicDTypeKwd kwd) { @@ -382,7 +382,7 @@ private: newvarp->direction(VDirection::INPUT); funcp->addArgsp(newvarp); AstVarScope* const newvscp = new AstVarScope(funcp->fileline(), m_scopep, newvarp); - m_scopep->addVarp(newvscp); + m_scopep->addVarsp(newvscp); return newvscp; } AstVarScope* createVarScope(AstVar* invarp, const string& name) { @@ -398,9 +398,9 @@ private: = new AstVar{invarp->fileline(), VVarType::BLOCKTEMP, name, invarp}; newvarp->funcLocal(false); newvarp->propagateAttrFrom(invarp); - m_modp->addStmtp(newvarp); + m_modp->addStmtsp(newvarp); AstVarScope* const newvscp = new AstVarScope{newvarp->fileline(), m_scopep, newvarp}; - m_scopep->addVarp(newvscp); + m_scopep->addVarsp(newvscp); return newvscp; } } @@ -767,7 +767,7 @@ private: funcp->protect(false); funcp->cname(nodep->cname()); // Add DPI Export to top, since it's a global function - m_topScopep->scopep()->addActivep(funcp); + m_topScopep->scopep()->addBlocksp(funcp); { // Create dispatch wrapper // Note this function may dispatch to myfunc on a different class. @@ -856,9 +856,9 @@ private: // doesn't rip up the variables on us args += ");\n"; AstCStmt* const newp = new AstCStmt(nodep->fileline(), "(*__Vcb)("); - newp->addBodysp(argnodesp); + newp->addExprsp(argnodesp); VL_DANGLING(argnodesp); - newp->addBodysp(new AstText(nodep->fileline(), args, true)); + newp->addExprsp(new AstText(nodep->fileline(), args, true)); funcp->addStmtsp(newp); } @@ -900,7 +900,7 @@ private: funcp->protect(false); funcp->pure(nodep->pure()); // Add DPI Import to top, since it's a global function - m_topScopep->scopep()->addActivep(funcp); + m_topScopep->scopep()->addBlocksp(funcp); makePortList(nodep, funcp); return funcp; } @@ -1066,9 +1066,9 @@ private: FileLine* const fl = m_topScopep->fileline(); const string name{"__Vdpi_export_trigger"}; AstVar* const varp = new AstVar{fl, VVarType::VAR, name, VFlagBitPacked{}, 1}; - m_topScopep->scopep()->modp()->addStmtp(varp); + m_topScopep->scopep()->modp()->addStmtsp(varp); dpiExportTriggerp = new AstVarScope{fl, m_topScopep->scopep(), varp}; - m_topScopep->scopep()->addVarp(dpiExportTriggerp); + m_topScopep->scopep()->addVarsp(dpiExportTriggerp); netlistp->dpiExportTriggerp(dpiExportTriggerp); } return dpiExportTriggerp; @@ -1136,7 +1136,7 @@ private: AstVarScope* rtnvscp = nullptr; if (rtnvarp) { rtnvscp = new AstVarScope(rtnvarp->fileline(), m_scopep, rtnvarp); - m_scopep->addVarp(rtnvscp); + m_scopep->addVarsp(rtnvscp); rtnvarp->user2p(rtnvscp); } @@ -1230,7 +1230,7 @@ private: } AstVarScope* const newvscp = new AstVarScope{portp->fileline(), m_scopep, portp}; - m_scopep->addVarp(newvscp); + m_scopep->addVarsp(newvscp); portp->user2p(newvscp); } } @@ -1508,7 +1508,7 @@ private: iterateAndNextNull(nodep->condp()); // Body insert just before themselves m_insStmtp = nullptr; // First thing should be new statement - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); iterateAndNextNull(nodep->incsp()); // Done the loop m_insStmtp = nullptr; // Next thing should be new statement diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 7af7a3203..9a54c91b9 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -161,13 +161,13 @@ private: if (delayp) { auto* const delayStmtp = new AstDelay{flp, delayp, nullptr}; stmtp->replaceWith(delayStmtp); - delayStmtp->stmtsp(stmtp); + delayStmtp->addStmtsp(stmtp); stmtp = delayStmtp; } if (auto* const sensesp = VN_CAST(controlp, SenTree)) { auto* const eventControlp = new AstEventControl{flp, sensesp, nullptr}; stmtp->replaceWith(eventControlp); - eventControlp->stmtsp(stmtp); + eventControlp->addStmtsp(stmtp); stmtp = eventControlp; } return stmtp == nodep ? nullptr : stmtp; @@ -216,7 +216,7 @@ private: awaitingCurrentTimep->dtypeSetBit(); m_delaySensesp = new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE, awaitingCurrentTimep}}; - m_netlistp->topScopep()->addSenTreep(m_delaySensesp); + m_netlistp->topScopep()->addSenTreesp(m_delaySensesp); return m_delaySensesp; } // Creates a trigger scheduler variable @@ -265,10 +265,10 @@ private: insertBeforep->addHereThisAsNext(varp); } else { varp = new AstVar{flp, VVarType::MODULETEMP, name, dtypep}; - m_scopep->modp()->addStmtp(varp); + m_scopep->modp()->addStmtsp(varp); } AstVarScope* vscp = new AstVarScope{flp, m_scopep, varp}; - m_scopep->addVarp(vscp); + m_scopep->addVarsp(vscp); return vscp; } // Add a done() call on the fork sync @@ -347,9 +347,9 @@ private: FileLine* const flp = nodep->fileline(); AstSenTree* const sensesp = m_activep->sensesp(); if (sensesp->hasClocked()) { - AstNode* bodysp = nodep->bodysp()->unlinkFrBackWithNext(); + AstNode* bodysp = nodep->stmtsp()->unlinkFrBackWithNext(); auto* const controlp = new AstEventControl{flp, sensesp->cloneTree(false), bodysp}; - nodep->addStmtp(controlp); + nodep->addStmtsp(controlp); iterate(controlp); } // Note: The 'while (true)' outer loop will be added in V3Sched @@ -412,9 +412,9 @@ private: } void visit(AstNodeCCall* nodep) override { if (nodep->funcp()->user2()) { // If suspendable - auto* const awaitp = new AstCAwait{nodep->fileline(), nullptr}; - nodep->replaceWith(awaitp); - awaitp->exprp(nodep); + VNRelinker relinker; + nodep->unlinkFrBack(&relinker); + relinker.relink(new AstCAwait{nodep->fileline(), nodep}); } else { // Add our process/func as the CFunc's dependency as we might have to put an await here DependencyVertex* const procVxp = getDependencyVertex(m_procp); @@ -571,15 +571,15 @@ private: // Wait on changed events related to the vars in the wait statement AstSenItem* const senItemsp = varRefpsToSenItemsp(nodep->condp()); AstNode* const condp = nodep->condp()->unlinkFrBack(); - AstNode* const bodysp = nodep->bodysp(); - if (bodysp) bodysp->unlinkFrBackWithNext(); + AstNode* const stmtsp = nodep->stmtsp(); + if (stmtsp) stmtsp->unlinkFrBackWithNext(); FileLine* const flp = nodep->fileline(); if (senItemsp) { // Put the event control in a while loop with the wait expression as condition AstNode* const loopp = new AstWhile{flp, new AstLogNot{flp, condp}, new AstEventControl{flp, new AstSenTree{flp, senItemsp}, nullptr}}; - if (bodysp) loopp->addNext(bodysp); + if (stmtsp) loopp->addNext(stmtsp); nodep->replaceWith(loopp); } else { condp->v3warn(WAITCONST, "Wait statement condition is constant"); @@ -590,10 +590,10 @@ private: auto* const awaitp = new AstCAwait{flp, new AstCStmt{flp, "VlForever{}"}}; awaitp->statement(true); nodep->replaceWith(awaitp); - if (bodysp) VL_DO_DANGLING(bodysp->deleteTree(), bodysp); - } else if (bodysp) { + if (stmtsp) VL_DO_DANGLING(stmtsp->deleteTree(), stmtsp); + } else if (stmtsp) { // Just put the body there - nodep->replaceWith(bodysp); + nodep->replaceWith(stmtsp); } VL_DO_DANGLING(constCondp->deleteTree(), condp); } diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index 4153178f3..faa409b1d 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -454,9 +454,9 @@ private: v3Global.rootp()->typeTablep()->addTypesp(newArrDtp); AstVar* const newvarp = new AstVar(flp, VVarType::MODULETEMP, "__Vm_traceActivity", newArrDtp); - m_topModp->addStmtp(newvarp); + m_topModp->addStmtsp(newvarp); AstVarScope* const newvscp = new AstVarScope(flp, m_topScopep, newvarp); - m_topScopep->addVarp(newvscp); + m_topScopep->addVarsp(newvscp); m_activityVscp = newvscp; // Insert activity setters @@ -493,7 +493,7 @@ private: funcp->slow(full); funcp->isStatic(isTopFunc); // Add it to top scope - m_topScopep->addActivep(funcp); + m_topScopep->addBlocksp(funcp); const auto addInitStr = [funcp, flp](const string& str) -> void { funcp->addInitsp(new AstCStmt(flp, str)); }; @@ -676,7 +676,7 @@ private: // Add TraceInc node AstTraceInc* const incp = new AstTraceInc(declp->fileline(), declp, /* full: */ false, baseCode); - ifp->addIfsp(incp); + ifp->addThensp(incp); subStmts += incp->nodeCount(); // Track partitioning @@ -698,7 +698,7 @@ private: cleanupFuncp->slow(false); cleanupFuncp->isStatic(true); cleanupFuncp->isLoose(true); - m_topScopep->addActivep(cleanupFuncp); + m_topScopep->addBlocksp(cleanupFuncp); cleanupFuncp->addInitsp(new AstCStmt(fl, voidSelfAssign(m_topModp))); cleanupFuncp->addInitsp(new AstCStmt(fl, symClassAssign())); @@ -755,7 +755,7 @@ private: m_regFuncp->slow(true); m_regFuncp->isStatic(false); m_regFuncp->isLoose(true); - m_topScopep->addActivep(m_regFuncp); + m_topScopep->addBlocksp(m_regFuncp); // Create the full dump functions, also allocates signal numbers createFullTraceFunction(traces, nFullCodes, m_parallelism); diff --git a/src/V3TraceDecl.cpp b/src/V3TraceDecl.cpp index 4c8cae3a8..9df50a158 100644 --- a/src/V3TraceDecl.cpp +++ b/src/V3TraceDecl.cpp @@ -162,7 +162,7 @@ private: funcp->isStatic(false); funcp->isLoose(true); funcp->slow(true); - topScopep->addActivep(funcp); + topScopep->addBlocksp(funcp); return funcp; } @@ -303,8 +303,8 @@ private: scopeName = scopeName.substr(0, lastDot + 1); const size_t scopeLen = scopeName.length(); - UASSERT_OBJ(cellp->intfRefp(), cellp, "Interface without tracing reference"); - for (AstIntfRef *irp = cellp->intfRefp(), *nextIrp; irp; irp = nextIrp) { + UASSERT_OBJ(cellp->intfRefsp(), cellp, "Interface without tracing reference"); + for (AstIntfRef *irp = cellp->intfRefsp(), *nextIrp; irp; irp = nextIrp) { nextIrp = VN_AS(irp->nextp(), IntfRef); const string irpName = irp->prettyName(); diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index f88908f62..613983a02 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -422,7 +422,7 @@ class TristateVisitor final : public TristateBaseVisitor { "Unsupported: Creating tristate signal not underneath a module: " << nodep->prettyNameQ()); } else { - m_modp->addStmtp(newp); + m_modp->addStmtsp(newp); } } void associateLogic(AstNode* fromp, AstNode* top) { @@ -554,7 +554,7 @@ class TristateVisitor final : public TristateBaseVisitor { AstNode* const newp = new AstAssignW(varp->fileline(), varrefp, constp); UINFO(9, " newoev " << newp << endl); varrefp->user1p(newAllZerosOrOnes(varp, false)); - nodep->addStmtp(newp); + nodep->addStmtsp(newp); mapInsertLhsVarRef(varrefp); // insertTristates will convert // // to a varref to the __out# variable } @@ -627,7 +627,7 @@ class TristateVisitor final : public TristateBaseVisitor { lhsp->name() + "__out" + cvtToStr(m_unique), VFlagBitPacked(), w); // 2-state ok; sep enable UINFO(9, " newout " << newlhsp << endl); - nodep->addStmtp(newlhsp); + nodep->addStmtsp(newlhsp); refp->varp(newlhsp); // assign the new var to the varref refp->name(newlhsp->name()); @@ -636,13 +636,13 @@ class TristateVisitor final : public TristateBaseVisitor { lhsp->name() + "__en" + cvtToStr(m_unique++), VFlagBitPacked(), w); // 2-state ok UINFO(9, " newenp " << newenp << endl); - nodep->addStmtp(newenp); + nodep->addStmtsp(newenp); AstNode* const enassp = new AstAssignW( refp->fileline(), new AstVarRef(refp->fileline(), newenp, VAccess::WRITE), getEnp(refp)); UINFO(9, " newass " << enassp << endl); - nodep->addStmtp(enassp); + nodep->addStmtsp(enassp); // now append this driver to the driver logic. AstNode* const ref1p = new AstVarRef(refp->fileline(), newlhsp, VAccess::READ); @@ -675,7 +675,7 @@ class TristateVisitor final : public TristateBaseVisitor { VL_DO_DANGLING(undrivenp->deleteTree(), undrivenp); } if (envarp) { - nodep->addStmtp(new AstAssignW( + nodep->addStmtsp(new AstAssignW( enp->fileline(), new AstVarRef(envarp->fileline(), envarp, VAccess::WRITE), enp)); } // __out (child) or (parent) = drive-value expression @@ -683,7 +683,7 @@ class TristateVisitor final : public TristateBaseVisitor { lhsp->fileline(), new AstVarRef(lhsp->fileline(), lhsp, VAccess::WRITE), orp); assp->user2(U2_BOTH); // Don't process further; already resolved if (debug() >= 9) assp->dumpTree(cout, "-lhsp-eqn: "); - nodep->addStmtp(assp); + nodep->addStmtsp(assp); } void addToAssignmentList(AstAssignW* nodep) { @@ -864,11 +864,11 @@ class TristateVisitor final : public TristateBaseVisitor { if (m_graphing) { iterateChildren(nodep); if (m_alhs) { - associateLogic(nodep, nodep->expr1p()); - associateLogic(nodep, nodep->expr2p()); + associateLogic(nodep, nodep->thenp()); + associateLogic(nodep, nodep->elsep()); } else { - associateLogic(nodep->expr1p(), nodep); - associateLogic(nodep->expr2p(), nodep); + associateLogic(nodep->thenp(), nodep); + associateLogic(nodep->elsep(), nodep); } } else { if (m_alhs && nodep->user1p()) { @@ -887,20 +887,20 @@ class TristateVisitor final : public TristateBaseVisitor { condp->v3warn(E_UNSUPPORTED, "Unsupported: don't know how to deal with " "tristate logic in the conditional expression"); } - AstNode* const expr1p = nodep->expr1p(); - AstNode* const expr2p = nodep->expr2p(); - if (expr1p->user1p() || expr2p->user1p()) { // else no tristates + AstNode* const thenp = nodep->thenp(); + AstNode* const elsep = nodep->elsep(); + if (thenp->user1p() || elsep->user1p()) { // else no tristates m_tgraph.didProcess(nodep); - AstNode* const en1p = getEnp(expr1p); - AstNode* const en2p = getEnp(expr2p); + AstNode* const en1p = getEnp(thenp); + AstNode* const en2p = getEnp(elsep); // The output enable of a cond is a cond of the output enable of the // two expressions with the same conditional. AstNode* const enp = new AstCond(nodep->fileline(), condp->cloneTree(false), en1p, en2p); UINFO(9, " newcond " << enp << endl); nodep->user1p(enp); // propagate up COND(lhsp->enable, rhsp->enable) - expr1p->user1p(nullptr); - expr2p->user1p(nullptr); + thenp->user1p(nullptr); + elsep->user1p(nullptr); } } } @@ -1391,7 +1391,7 @@ class TristateVisitor final : public TristateBaseVisitor { UINFO(9, " newpin " << enpinp << endl); enpinp->user2(U2_BOTH); // don't iterate the pin later nodep->addNextHere(enpinp); - m_modp->addStmtp(enVarp); + m_modp->addStmtsp(enVarp); enrefp = new AstVarRef(nodep->fileline(), enVarp, VAccess::READ); UINFO(9, " newvrf " << enrefp << endl); if (debug() >= 9) enpinp->dumpTree(cout, "-pin-ena: "); @@ -1608,7 +1608,7 @@ class TristateVisitor final : public TristateBaseVisitor { void visit(AstCaseItem* nodep) override { // don't deal with casez compare '???? values - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); } void visit(AstCell* nodep) override { diff --git a/src/V3Unknown.cpp b/src/V3Unknown.cpp index 61d336ba3..708c1c1d7 100644 --- a/src/V3Unknown.cpp +++ b/src/V3Unknown.cpp @@ -121,7 +121,7 @@ private: } else { AstVar* const varp = new AstVar(fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), prep->dtypep()); - m_modp->addStmtp(varp); + m_modp->addStmtsp(varp); AstNode* const abovep = prep->backp(); // Grab above point before we replace 'prep' prep->replaceWith(new AstVarRef(fl, varp, VAccess::WRITE)); if (m_timingControlp) m_timingControlp->unlinkFrBack(); @@ -187,7 +187,7 @@ private: m_constXCvt = false; // Avoid losing the X's in casex iterateAndNextNull(nodep->condsp()); m_constXCvt = true; - iterateAndNextNull(nodep->bodysp()); + iterateAndNextNull(nodep->stmtsp()); } } void visit(AstNodeDType* nodep) override { @@ -367,9 +367,9 @@ private: // Add inits in front of other statement. // In the future, we should stuff the initp into the module's constructor. AstNode* const afterp = m_modp->stmtsp()->unlinkFrBackWithNext(); - m_modp->addStmtp(newvarp); - m_modp->addStmtp(newinitp); - m_modp->addStmtp(afterp); + m_modp->addStmtsp(newvarp); + m_modp->addStmtsp(newinitp); + m_modp->addStmtsp(afterp); if (debug() >= 9) newref1p->dumpTree(cout, " _new: "); if (debug() >= 9) newvarp->dumpTree(cout, " _new: "); if (debug() >= 9) newinitp->dumpTree(cout, " _new: "); diff --git a/src/V3Unroll.cpp b/src/V3Unroll.cpp index 1efbef18b..2ffef6176 100644 --- a/src/V3Unroll.cpp +++ b/src/V3Unroll.cpp @@ -387,21 +387,21 @@ private: if (nodep->backp()->nextp() == nodep) initp = nodep->backp(); // Grab assignment AstNode* incp = nullptr; // Should be last statement - AstNode* bodysp = nodep->bodysp(); + AstNode* stmtsp = nodep->stmtsp(); if (nodep->incsp()) V3Const::constifyEdit(nodep->incsp()); // cppcheck-suppress duplicateCondition if (nodep->incsp()) { incp = nodep->incsp(); } else { - for (incp = nodep->bodysp(); incp && incp->nextp(); incp = incp->nextp()) {} + for (incp = nodep->stmtsp(); incp && incp->nextp(); incp = incp->nextp()) {} if (incp) VL_DO_DANGLING(V3Const::constifyEdit(incp), incp); // Again, as may have changed - bodysp = nodep->bodysp(); - for (incp = nodep->bodysp(); incp && incp->nextp(); incp = incp->nextp()) {} - if (incp == bodysp) bodysp = nullptr; + stmtsp = nodep->stmtsp(); + for (incp = nodep->stmtsp(); incp && incp->nextp(); incp = incp->nextp()) {} + if (incp == stmtsp) stmtsp = nullptr; } // And check it - if (forUnrollCheck(nodep, initp, nodep->precondsp(), nodep->condp(), incp, bodysp)) { + if (forUnrollCheck(nodep, initp, nodep->precondsp(), nodep->condp(), incp, stmtsp)) { VL_DO_DANGLING(pushDeletep(nodep), nodep); // Did replacement } } @@ -426,7 +426,7 @@ private: // deleted by V3Const. VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } else if (forUnrollCheck(nodep, nodep->initsp(), nullptr, nodep->condp(), - nodep->incsp(), nodep->bodysp())) { + nodep->incsp(), nodep->stmtsp())) { VL_DO_DANGLING(pushDeletep(nodep), nodep); // Did replacement } else { nodep->v3error("For loop doesn't have genvar index, or is malformed"); diff --git a/src/V3VariableOrder.cpp b/src/V3VariableOrder.cpp index 53e53788e..ad98ec081 100644 --- a/src/V3VariableOrder.cpp +++ b/src/V3VariableOrder.cpp @@ -188,7 +188,7 @@ class VariableOrder final { stmtsp->unlinkFrBackWithNext(); AstNode::addNext(firstp, stmtsp); } - modp->addStmtp(firstp); + modp->addStmtsp(firstp); } } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 48673f857..7087b9bc2 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -462,22 +462,22 @@ private: iterateCheckBool(nodep, "Conditional Test", nodep->condp(), BOTH); // Determine sub expression widths only relying on what's in the subops // CONTEXT determined, but need data type for pattern assignments - userIterateAndNext(nodep->expr1p(), WidthVP(m_vup->dtypeNullp(), PRELIM).p()); - userIterateAndNext(nodep->expr2p(), WidthVP(m_vup->dtypeNullp(), PRELIM).p()); + userIterateAndNext(nodep->thenp(), WidthVP(m_vup->dtypeNullp(), PRELIM).p()); + userIterateAndNext(nodep->elsep(), WidthVP(m_vup->dtypeNullp(), PRELIM).p()); // Calculate width of this expression. // First call (prelim()) m_vup->width() is probably zero, so we'll return // the size of this subexpression only. // Second call (final()) m_vup->width() is probably the expression size, so // the expression includes the size of the output too. - if (nodep->expr1p()->isDouble() || nodep->expr2p()->isDouble()) { + if (nodep->thenp()->isDouble() || nodep->elsep()->isDouble()) { nodep->dtypeSetDouble(); - } else if (nodep->expr1p()->isString() || nodep->expr2p()->isString()) { + } else if (nodep->thenp()->isString() || nodep->elsep()->isString()) { nodep->dtypeSetString(); } else { - const int width = std::max(nodep->expr1p()->width(), nodep->expr2p()->width()); + const int width = std::max(nodep->thenp()->width(), nodep->elsep()->width()); const int mwidth - = std::max(nodep->expr1p()->widthMin(), nodep->expr2p()->widthMin()); - const bool issigned = nodep->expr1p()->isSigned() && nodep->expr2p()->isSigned(); + = std::max(nodep->thenp()->widthMin(), nodep->elsep()->widthMin()); + const bool issigned = nodep->thenp()->isSigned() && nodep->elsep()->isSigned(); nodep->dtypeSetLogicUnsized(width, mwidth, VSigning::fromBool(issigned)); } } @@ -486,9 +486,9 @@ private: AstNodeDType* const subDTypep = expDTypep; nodep->dtypeFrom(expDTypep); // Error report and change sizes for suboperands of this node. - iterateCheck(nodep, "Conditional True", nodep->expr1p(), CONTEXT, FINAL, subDTypep, + iterateCheck(nodep, "Conditional True", nodep->thenp(), CONTEXT, FINAL, subDTypep, EXTEND_EXP); - iterateCheck(nodep, "Conditional False", nodep->expr2p(), CONTEXT, FINAL, subDTypep, + iterateCheck(nodep, "Conditional False", nodep->elsep(), CONTEXT, FINAL, subDTypep, EXTEND_EXP); } } @@ -1872,7 +1872,7 @@ private: castSized(nodep, nodep->fromp(), width); // Note castSized might modify nodep->fromp() } else { - iterateCheck(nodep, "value", nodep->lhsp(), SELF, FINAL, fromDtp, EXTEND_EXP, + iterateCheck(nodep, "value", nodep->fromp(), SELF, FINAL, fromDtp, EXTEND_EXP, false); } if (basicp->isDouble() && !nodep->fromp()->isDouble()) { @@ -1904,14 +1904,14 @@ private: << toDtp->prettyDTypeNameQ()); } if (!newp) newp = nodep->fromp()->unlinkFrBack(); - nodep->lhsp(newp); + nodep->fromp(newp); // if (debug()) nodep->dumpTree(cout, " CastOut: "); // if (debug()) nodep->backp()->dumpTree(cout, " CastOutUpUp: "); } if (m_vup->final()) { - iterateCheck(nodep, "value", nodep->lhsp(), SELF, FINAL, nodep->lhsp()->dtypep(), + iterateCheck(nodep, "value", nodep->fromp(), SELF, FINAL, nodep->fromp()->dtypep(), EXTEND_EXP, false); - AstNode* const underp = nodep->lhsp()->unlinkFrBack(); + AstNode* const underp = nodep->fromp()->unlinkFrBack(); // if (debug()) underp->dumpTree(cout, " CastRep: "); nodep->replaceWith(underp); VL_DO_DANGLING(pushDeletep(nodep), nodep); @@ -4012,7 +4012,7 @@ private: userIterateAndNext(nodep->exprp(), WidthVP(CONTEXT, PRELIM).p()); for (AstCaseItem *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) { nextip = VN_AS(itemp->nextp(), CaseItem); // Prelim may cause the node to get replaced - if (!VN_IS(nodep, GenCase)) userIterateAndNext(itemp->bodysp(), nullptr); + if (!VN_IS(nodep, GenCase)) userIterateAndNext(itemp->stmtsp(), nullptr); for (AstNode *nextcp, *condp = itemp->condsp(); condp; condp = nextcp) { nextcp = condp->nextp(); // Prelim may cause the node to get replaced VL_DO_DANGLING(userIterate(condp, WidthVP(CONTEXT, PRELIM).p()), condp); @@ -4055,27 +4055,27 @@ private: userIterateAndNext(nodep->initsp(), nullptr); iterateCheckBool(nodep, "For Test Condition", nodep->condp(), BOTH); // it's like an if() condition. - if (!VN_IS(nodep, GenFor)) userIterateAndNext(nodep->bodysp(), nullptr); + if (!VN_IS(nodep, GenFor)) userIterateAndNext(nodep->stmtsp(), nullptr); userIterateAndNext(nodep->incsp(), nullptr); } void visit(AstRepeat* nodep) override { assertAtStatement(nodep); userIterateAndNext(nodep->countp(), WidthVP(SELF, BOTH).p()); - userIterateAndNext(nodep->bodysp(), nullptr); + userIterateAndNext(nodep->stmtsp(), nullptr); } void visit(AstWhile* nodep) override { assertAtStatement(nodep); userIterateAndNext(nodep->precondsp(), nullptr); iterateCheckBool(nodep, "For Test Condition", nodep->condp(), BOTH); // it's like an if() condition. - userIterateAndNext(nodep->bodysp(), nullptr); + userIterateAndNext(nodep->stmtsp(), nullptr); userIterateAndNext(nodep->incsp(), nullptr); } void visit(AstNodeIf* nodep) override { assertAtStatement(nodep); // if (debug()) nodep->dumpTree(cout, " IfPre: "); if (!VN_IS(nodep, GenIf)) { // for m_paramsOnly - userIterateAndNext(nodep->ifsp(), nullptr); + userIterateAndNext(nodep->thensp(), nullptr); userIterateAndNext(nodep->elsesp(), nullptr); } iterateCheckBool(nodep, "If", nodep->condp(), BOTH); // it's like an if() condition. @@ -4096,7 +4096,7 @@ private: UASSERT_OBJ(fromp->dtypep(), fromp, "Missing data type"); AstNodeDType* fromDtp = fromp->dtypep()->skipRefp(); // Split into for loop - AstNode* bodyp = nodep->bodysp(); // Might be null + AstNode* bodyp = nodep->stmtsp(); // Might be null if (bodyp) bodyp->unlinkFrBackWithNext(); // We record where the body needs to eventually go with bodyPointp // (Can't use bodyp as might be null) @@ -4404,8 +4404,8 @@ private: argp->unlinkFrBack(&handle); AstCMath* const newp = new AstCMath(nodep->fileline(), "VL_TO_STRING(", 0, true); - newp->addBodysp(argp); - newp->addBodysp(new AstText(nodep->fileline(), ")", true)); + newp->addExprsp(argp); + newp->addExprsp(new AstText(nodep->fileline(), ")", true)); newp->dtypeSetString(); newp->pure(true); newp->protect(false); @@ -5139,7 +5139,7 @@ private: if (nodep->fileline()->timingOn()) { if (v3Global.opt.timing().isSetTrue()) { userIterate(nodep->condp(), WidthVP(SELF, PRELIM).p()); - iterateNull(nodep->bodysp()); + iterateNull(nodep->stmtsp()); return; } else if (v3Global.opt.timing().isSetFalse()) { nodep->v3warn(E_NOTIMING, "Wait statements require --timing"); @@ -5150,9 +5150,9 @@ private: } // If we ignore timing: // Statements we'll just execute immediately; equivalent to if they followed this - if (AstNode* const bodysp = nodep->bodysp()) { - bodysp->unlinkFrBackWithNext(); - nodep->replaceWith(bodysp); + if (AstNode* const stmtsp = nodep->stmtsp()) { + stmtsp->unlinkFrBackWithNext(); + nodep->replaceWith(stmtsp); } else { nodep->unlinkFrBack(); } @@ -6459,7 +6459,7 @@ private: varp->isStatic(true); varp->valuep(initp); // Add to root, as don't know module we are in, and aids later structure sharing - v3Global.rootp()->dollarUnitPkgAddp()->addStmtp(varp); + v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(varp); // Element 0 is a non-index and has speced values initp->addValuep(dimensionValue(nodep->fileline(), nodep, attrType, 0)); for (unsigned i = 1; i < msbdim + 1; ++i) { @@ -6524,7 +6524,7 @@ private: varp->isStatic(true); varp->valuep(initp); // Add to root, as don't know module we are in, and aids later structure sharing - v3Global.rootp()->dollarUnitPkgAddp()->addStmtp(varp); + v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(varp); // Default for all unspecified values if (attrType == VAttrType::ENUM_NAME) { diff --git a/src/astgen b/src/astgen index 19a6d0b19..56956768d 100755 --- a/src/astgen +++ b/src/astgen @@ -24,6 +24,8 @@ class Node: self._file = file # File this class is defined in self._lineno = lineno # Line this class is defined on self._ordIdx = None # Ordering index of this class + self._arity = -1 # Arity of node + self._ops = {} # Operands of node @property def name(self): @@ -33,6 +35,10 @@ class Node: def superClass(self): return self._superClass + @property + def isRoot(self): + return self.superClass is None + @property def isCompleted(self): return isinstance(self._subClasses, tuple) @@ -50,6 +56,20 @@ class Node: assert not self.isCompleted self._subClasses.append(subClass) + def addOp(self, n, name, monad, kind): + assert 1 <= n <= 4 + self._ops[n] = (name, monad, kind) + self._arity = max(self._arity, n) + + def getOp(self, n): + assert 1 <= n <= 4 + op = self._ops.get(n, None) + if op is not None: + return op + if not self.isRoot: + return self.superClass.getOp(n) + return None + # Computes derived properties over entire class hierarchy. # No more changes to the hierarchy are allowed once this was called def complete(self, typeId=0, ordIdx=0): @@ -62,6 +82,11 @@ class Node: self._ordIdx = ordIdx ordIdx = ordIdx + 1 + if self.isRoot: + self._arity = 0 + else: + self._arity = max(self._arity, self._superClass.arity) + # Leaves if self.isLeaf: self._typeId = typeId @@ -78,11 +103,6 @@ class Node: assert self.isCompleted return self._subClasses - @property - def isRoot(self): - assert self.isCompleted - return self.superClass is None - @property def isLeaf(self): assert self.isCompleted @@ -140,6 +160,11 @@ class Node: assert self.isCompleted return self._ordIdx + @property + def arity(self): + assert self.isCompleted + return self._arity + def isSubClassOf(self, other): assert self.isCompleted if self is other: @@ -495,6 +520,25 @@ class Cpt: ###################################################################### +def partitionAndStrip(string, separator): + return map(lambda _: _.strip(), string.partition(separator)) + + +def parseOpType(string): + match = re.match(r'^(\w+)\[(\w+)\]$', string) + if match: + monad, kind = match.groups() + if monad not in ("Optional", "List"): + return None + kind = parseOpType(kind) + if not kind or kind[0]: + return None + return monad, kind[1] + if re.match(r'^Ast(\w+)$', string): + return "", string[3:] + return None + + def read_types(filename): hasErrors = False @@ -539,12 +583,65 @@ def read_types(filename): node = Node(classn, superClass, filename, lineno) superClass.addSubClass(node) Nodes[classn] = node - if not node: continue if re.match(r'^\s*ASTGEN_MEMBERS_' + node.name + ';', line): hasAstgenMembers = True + match = re.match(r'^\s*//\s*@astgen\s+(.*)$', line) + if match: + decl = re.sub(r'//.*$', '', match.group(1)) + what, sep, rest = partitionAndStrip(decl, ":=") + what = re.sub(r'\s+', ' ', what) + if not sep: + error( + lineno, + "Malformed '@astgen' directive (expecting ' := '): " + + decl) + elif what in ("op1", "op2", "op3", "op4"): + n = int(what[-1]) + ident, sep, kind = partitionAndStrip(rest, ":") + ident = ident.strip() + if not sep or not re.match(r'^\w+$', ident): + error( + lineno, "Malformed '@astgen " + what + + "' directive (expecting '" + what + + " := : ': " + decl) + else: + kind = parseOpType(kind) + if not kind: + error( + lineno, "Bad type for '@astgen " + what + + "' (expecting Ast*, Optional[Ast*], or List[Ast*]):" + + decl) + elif node.getOp(n) is not None: + error( + lineno, "Already defined " + what + " for " + + node.name) + else: + node.addOp(n, ident, *kind) + elif what in ("alias op1", "alias op2", "alias op3", + "alias op4"): + n = int(what[-1]) + ident = rest.strip() + if not re.match(r'^\w+$', ident): + error( + lineno, "Malformed '@astgen " + what + + "' directive (expecting '" + what + + " := ': " + decl) + else: + op = node.getOp(n) + if op is None: + error(lineno, + "Alaised op" + str(n) + " is not defined") + else: + node.addOp(n, ident, *op[1:]) + else: + line = re.sub(r'//.*$', '', line) + if re.match(r'.*[Oo]p[1-9].*', line): + error(lineno, + "Use generated accessors to access op operands") + checkFinishedNode(node) if hasErrors: sys.exit("%Error: Stopping due to errors reported above") @@ -614,6 +711,7 @@ def write_report(filename): fh.write("\nClasses:\n") for node in SortedNodes: fh.write(" class Ast%-17s\n" % node.name) + fh.write(" arity: {}\n".format(node.arity)) fh.write(" parent: ") for superClass in node.allSuperClasses: if not superClass.isRoot: @@ -759,6 +857,42 @@ def write_macros(filename): ''', t=node.name) + for n in (1, 2, 3, 4): + op = node.getOp(n) + if not op: + continue + name, monad, kind = op + retrieve = ("VN_AS(op{n}p(), {kind})" if kind != "Node" else + "op{n}p()").format(n=n, kind=kind) + if monad == "List": + emitBlock('''\ + Ast{kind}* {name}() const {{ return {retrieve}; }} + void add{Name}(Ast{kind}* nodep) {{ addNOp{n}p(reinterpret_cast(nodep)); }} + ''', + kind=kind, + name=name, + Name=name[0].upper() + name[1:], + n=n, + retrieve=retrieve) + elif monad == "Optional": + emitBlock('''\ + Ast{kind}* {name}() const {{ return {retrieve}; }} + void {name}(Ast{kind}* nodep) {{ setNOp{n}p(reinterpret_cast(nodep)); }} + ''', + kind=kind, + name=name, + n=n, + retrieve=retrieve) + else: + emitBlock('''\ + Ast{kind}* {name}() const {{ return {retrieve}; }} + void {name}(Ast{kind}* nodep) {{ setOp{n}p(reinterpret_cast(nodep)); }} + ''', + kind=kind, + name=name, + n=n, + retrieve=retrieve) + fh.write( " static_assert(true, \"\")\n") # Swallowing the semicolon diff --git a/src/bisonpre b/src/bisonpre index e03a7e41e..c60e3575d 100755 --- a/src/bisonpre +++ b/src/bisonpre @@ -391,9 +391,8 @@ def clean_input(filename, outname): if not re.match(r'^\s*$', line): sys.exit( "%Error: " + filename + ":" + str(lineno) + ": Need " + - needmore + - " more blank lines to keep line numbers are constant\n" - ) + str(needmore) + + " more blank lines to keep line numbers constant\n") needmore -= 1 else: lines.append(line) diff --git a/src/verilog.y b/src/verilog.y index 79f7dcf61..67344ea29 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -1081,6 +1081,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) // Blank lines for type insertion // Blank lines for type insertion // Blank lines for type insertion +// Blank lines for type insertion %start source_text @@ -1105,8 +1106,8 @@ description: // ==IEEE: description | interface_declaration { } | program_declaration { } | package_declaration { } - | package_item { if ($1) PARSEP->unitPackage($1->fileline())->addStmtp($1); } - | bind_directive { if ($1) PARSEP->unitPackage($1->fileline())->addStmtp($1); } + | package_item { if ($1) PARSEP->unitPackage($1->fileline())->addStmtsp($1); } + | bind_directive { if ($1) PARSEP->unitPackage($1->fileline())->addStmtsp($1); } // unsupported // IEEE: config_declaration // // Verilator only | yaT_RESETALL { } // Else, under design, and illegal based on IEEE 22.3 @@ -1132,7 +1133,7 @@ timeunits_declaration: // ==IEEE: timeunits_declaration package_declaration: // ==IEEE: package_declaration packageFront package_itemListE yENDPACKAGE endLabelE { $1->modTrace(GRAMMARP->allTracingOn($1->fileline())); // Stash for implicit wires, etc - if ($2) $1->addStmtp($2); + if ($2) $1->addStmtsp($2); GRAMMARP->m_modp = nullptr; SYMP->popScope($1); GRAMMARP->endLabel($4,$1,$4); } @@ -1145,7 +1146,7 @@ packageFront: $$->lifetime($2); $$->modTrace(GRAMMARP->allTracingOn($$->fileline())); $$->timeunit(PARSEP->timeLastUnit()); - PARSEP->rootp()->addModulep($$); + PARSEP->rootp()->addModulesp($$); SYMP->pushNew($$); GRAMMARP->m_modp = $$; } ; @@ -1241,18 +1242,18 @@ module_declaration: // ==IEEE: module_declaration modFront importsAndParametersE portsStarE ';' /*cont*/ module_itemListE yENDMODULE endLabelE { $1->modTrace(GRAMMARP->allTracingOn($1->fileline())); // Stash for implicit wires, etc - if ($2) $1->addStmtp($2); - if ($3) $1->addStmtp($3); - if ($5) $1->addStmtp($5); + if ($2) $1->addStmtsp($2); + if ($3) $1->addStmtsp($3); + if ($5) $1->addStmtsp($5); GRAMMARP->m_modp = nullptr; SYMP->popScope($1); GRAMMARP->endLabel($7,$1,$7); } | udpFront parameter_port_listE portsStarE ';' /*cont*/ module_itemListE yENDPRIMITIVE endLabelE { $1->modTrace(false); // Stash for implicit wires, etc - if ($2) $1->addStmtp($2); - if ($3) $1->addStmtp($3); - if ($5) $1->addStmtp($5); + if ($2) $1->addStmtsp($2); + if ($3) $1->addStmtsp($3); + if ($5) $1->addStmtsp($5); GRAMMARP->m_tracingParse = true; GRAMMARP->m_modp = nullptr; SYMP->popScope($1); @@ -1272,7 +1273,7 @@ modFront: $$->modTrace(GRAMMARP->allTracingOn($$->fileline())); $$->timeunit(PARSEP->timeLastUnit()); $$->unconnectedDrive(PARSEP->unconnectedDrive()); - PARSEP->rootp()->addModulep($$); + PARSEP->rootp()->addModulesp($$); SYMP->pushNew($$); GRAMMARP->m_modp = $$; } ; @@ -1288,9 +1289,9 @@ udpFront: { $$ = new AstPrimitive($3, *$3); $$->inLibrary(true); $$->lifetime($2); $$->modTrace(false); - $$->addStmtp(new AstPragma($3, VPragmaType::INLINE_MODULE)); + $$->addStmtsp(new AstPragma($3, VPragmaType::INLINE_MODULE)); GRAMMARP->m_tracingParse = false; - PARSEP->rootp()->addModulep($$); + PARSEP->rootp()->addModulesp($$); SYMP->pushNew($$); } ; @@ -1502,9 +1503,9 @@ interface_declaration: // IEEE: interface_declaration + interface_nonan // // timeunits_delcarationE is instead in interface_item intFront importsAndParametersE portsStarE ';' interface_itemListE yENDINTERFACE endLabelE - { if ($2) $1->addStmtp($2); - if ($3) $1->addStmtp($3); - if ($5) $1->addStmtp($5); + { if ($2) $1->addStmtsp($2); + if ($3) $1->addStmtsp($3); + if ($5) $1->addStmtsp($5); SYMP->popScope($1); } | yEXTERN intFront parameter_port_listE portsStarE ';' { BBUNSUP($1, "Unsupported: extern interface"); } @@ -1515,7 +1516,7 @@ intFront: { $$ = new AstIface($3, *$3); $$->inLibrary(true); $$->lifetime($2); - PARSEP->rootp()->addModulep($$); + PARSEP->rootp()->addModulesp($$); SYMP->pushNew($$); } ; @@ -1591,9 +1592,9 @@ program_declaration: // IEEE: program_declaration + program_nonansi_h pgmFront parameter_port_listE portsStarE ';' /*cont*/ program_itemListE yENDPROGRAM endLabelE { $1->modTrace(GRAMMARP->allTracingOn($1->fileline())); // Stash for implicit wires, etc - if ($2) $1->addStmtp($2); - if ($3) $1->addStmtp($3); - if ($5) $1->addStmtp($5); + if ($2) $1->addStmtsp($2); + if ($3) $1->addStmtsp($3); + if ($5) $1->addStmtsp($5); GRAMMARP->m_modp = nullptr; SYMP->popScope($1); GRAMMARP->endLabel($7,$1,$7); } @@ -1609,7 +1610,7 @@ pgmFront: $$->inLibrary(PARSEP->inLibrary() || $$->fileline()->celldefineOn()); $$->modTrace(GRAMMARP->allTracingOn($$->fileline())); $$->timeunit(PARSEP->timeLastUnit()); - PARSEP->rootp()->addModulep($$); + PARSEP->rootp()->addModulesp($$); SYMP->pushNew($$); GRAMMARP->m_modp = $$; } ; @@ -2024,12 +2025,13 @@ struct_unionDecl: // IEEE: part of data_type { $$ = $5; $$->addMembersp($6); SYMP->popScope($$); } ; -struct_union_memberList: // IEEE: { struct_union_member } +struct_union_memberList: // IEEE: { struct_union_member } struct_union_member { $$ = $1; } + | struct_union_memberList struct_union_member { $$ = addNextNull($1, $2); } ; -struct_union_member: // ==IEEE: struct_union_member +struct_union_member: // ==IEEE: struct_union_member // // UNSUP random_qualifer not propagagted until have randomize support random_qualifierE data_type_or_void /*mid*/ { GRAMMARP->m_memDTypep = $2; } // As a list follows, need to attach this dtype to each member. @@ -2037,7 +2039,7 @@ struct_union_member: // ==IEEE: struct_union_member | vlTag { $$ = nullptr; } ; -list_of_member_decl_assignments: // Derived from IEEE: list_of_variable_decl_assignments +list_of_member_decl_assignments: // Derived from IEEE: list_of_variable_decl_assignments member_decl_assignment { $$ = $1; } | list_of_member_decl_assignments ',' member_decl_assignment { $$ = addNextNull($1, $3); } ; @@ -2193,17 +2195,17 @@ enum_base_typeE: // IEEE: enum_base_type $$ = GRAMMARP->createArray(refp, $3, true); } ; -enum_nameList: +enum_nameList: enum_name_declaration { $$ = $1; } | enum_nameList ',' enum_name_declaration { $$ = addNextNull($1, $3); } ; -enum_name_declaration: // ==IEEE: enum_name_declaration +enum_name_declaration: // ==IEEE: enum_name_declaration idAny/*enum_identifier*/ enumNameRangeE enumNameStartE { $$ = new AstEnumItem($1, *$1, $2, $3); } ; -enumNameRangeE: // IEEE: second part of enum_name_declaration +enumNameRangeE: // IEEE: second part of enum_name_declaration /* empty */ { $$ = nullptr; } | '[' intnumAsConst ']' @@ -2646,7 +2648,7 @@ loop_generate_construct: // ==IEEE: loop_generate_construct } // Statements are under 'genforp' as cells under this // for loop won't get an extra layer of hierarchy tacked on - blkp->addGenforp(new AstGenFor($1, initp, $5, $7, lowerNoBegp)); + blkp->genforp(new AstGenFor($1, initp, $5, $7, lowerNoBegp)); $$ = blkp; VL_DO_DANGLING(lowerBegp->deleteTree(), lowerBegp); } @@ -2691,12 +2693,12 @@ genvar_iteration: // ==IEEE: genvar_iteration new AstConst{$2, AstConst::StringToParse{}, "'b1"}}}; } ; -case_generate_itemListE: // IEEE: [{ case_generate_itemList }] +case_generate_itemListE: // IEEE: [{ case_generate_itemList }] /* empty */ { $$ = nullptr; } | case_generate_itemList { $$ = $1; } ; -case_generate_itemList: // IEEE: { case_generate_itemList } +case_generate_itemList: // IEEE: { case_generate_itemList } ~c~case_generate_item { $$ = $1; } | ~c~case_generate_itemList ~c~case_generate_item { $$ = $1; $1->addNext($2); } ; @@ -2705,7 +2707,7 @@ case_generate_itemList: // IEEE: { case_generate_itemList } //UNSUP BISONPRE_COPY(case_generate_itemList,{s/~c~/c_/g}) // {copied} //UNSUP ; -case_generate_item: // ==IEEE: case_generate_item +case_generate_item: // ==IEEE: case_generate_item caseCondList colon generate_block_or_null { $$ = new AstCaseItem{$2, $1, $3}; } | yDEFAULT colon generate_block_or_null { $$ = new AstCaseItem{$1, nullptr, $3}; } | yDEFAULT generate_block_or_null { $$ = new AstCaseItem{$1, nullptr, $2}; } @@ -4107,19 +4109,19 @@ funcId: // IEEE: function_data_type_or_implicit // // function_data_type expanded here to prevent conflicts with implicit_type:empty vs data_type:ID /**/ fIdScoped { $$ = $1; - $$->addFvarp(new AstBasicDType($1, LOGIC_IMPLICIT)); + $$->fvarp(new AstBasicDType($1, LOGIC_IMPLICIT)); SYMP->pushNewUnderNodeOrCurrent($$, $1); } | signingE rangeList fIdScoped { $$ = $3; - $$->addFvarp(GRAMMARP->addRange(new AstBasicDType($3, LOGIC_IMPLICIT, $1), $2,true)); + $$->fvarp(GRAMMARP->addRange(new AstBasicDType($3, LOGIC_IMPLICIT, $1), $2,true)); SYMP->pushNewUnderNodeOrCurrent($$, $3); } | signing fIdScoped { $$ = $2; - $$->addFvarp(new AstBasicDType($2, LOGIC_IMPLICIT, $1)); + $$->fvarp(new AstBasicDType($2, LOGIC_IMPLICIT, $1)); SYMP->pushNewUnderNodeOrCurrent($$, $2); } | data_type fIdScoped { $$ = $2; - $$->addFvarp($1); + $$->fvarp($1); SYMP->pushNewUnderNodeOrCurrent($$, $2); } // // To verilator tasks are the same as void functions (we separately detect time passing) | yVOID taskId @@ -4977,12 +4979,12 @@ combinational_body: // IEEE: combinational_body + sequential_body yTABLE tableEntryList yENDTABLE { $$ = new AstUdpTable($1,$2); } ; -tableEntryList: // IEEE: { combinational_entry | sequential_entry } +tableEntryList: // IEEE: { combinational_entry | sequential_entry } tableEntry { $$ = $1; } | tableEntryList tableEntry { $$ = addNextNull($1, $2); } ; -tableEntry: // IEEE: combinational_entry + sequential_entry +tableEntry: // IEEE: combinational_entry + sequential_entry yaTABLELINE { $$ = new AstUdpTableLine($1,*$1); } | error { $$ = nullptr; } ; @@ -6214,14 +6216,14 @@ classVirtualE: | yVIRTUAL__CLASS { $$ = true; } ; -classExtendsE: // IEEE: part of class_declaration +classExtendsE: // IEEE: part of class_declaration // // The classExtendsE rule relys on classFront having the // // new class scope correct via classFront /* empty */ { $$ = nullptr; $$ = nullptr; } | yEXTENDS classExtendsList { $$ = $2; $$ = $2; } ; -classExtendsList: // IEEE: part of class_declaration +classExtendsList: // IEEE: part of class_declaration classExtendsOne { $$ = $1; $$ = $1; } | classExtendsList ',' classExtendsOne { $$ = $3; $$ = $3; @@ -6229,7 +6231,7 @@ classExtendsList: // IEEE: part of class_declaration "and unsupported for interface classes."); } ; -classExtendsOne: // IEEE: part of class_declaration +classExtendsOne: // IEEE: part of class_declaration class_typeExtImpList { $$ = new AstClassExtends($1->fileline(), $1); $$ = $1; } From 4600932d8c78e6aab3b3b5c21eff9e7ac323a70e Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 21 Sep 2022 14:16:20 +0100 Subject: [PATCH 047/177] Remove unused files --- src/V3Changed.cpp | 256 ---------------------------------------------- src/V3GenClk.cpp | 225 ---------------------------------------- 2 files changed, 481 deletions(-) delete mode 100644 src/V3Changed.cpp delete mode 100644 src/V3GenClk.cpp diff --git a/src/V3Changed.cpp b/src/V3Changed.cpp deleted file mode 100644 index 067d0d0a7..000000000 --- a/src/V3Changed.cpp +++ /dev/null @@ -1,256 +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 "V3Changed.h" - -#include "V3Ast.h" -#include "V3Global.h" - -#include - -//###################################################################### - -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->addBlocksp(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->addBlocksp(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(); - } - - void visit(AstBasicDType*) override { // - newChangeDet(); - } - void visit(AstPackArrayDType*) override { // - newChangeDet(); - } - 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(); - } - } - } - 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()); - } - } - 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->addStmtsp(newvarp); - m_newvscp = new AstVarScope{m_vscp->fileline(), m_state.m_scopetopp, newvarp}; - m_state.m_scopetopp->addVarsp(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(); - } - ~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([&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); -} diff --git a/src/V3GenClk.cpp b/src/V3GenClk.cpp deleted file mode 100644 index f8c1514ec..000000000 --- a/src/V3GenClk.cpp +++ /dev/null @@ -1,225 +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 "V3GenClk.h" - -#include "V3Ast.h" -#include "V3Global.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->addStmtsp(newvarp); - AstVarScope* const newvscp = new AstVarScope(vscp->fileline(), m_scopetopp, newvarp); - m_scopetopp->addVarsp(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 - 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); - } - } - } - void visit(AstActive* nodep) override { - m_activep = nodep; - iterate(nodep->sensesp()); - m_activep = nullptr; - } - - //----- - void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - GenClkRenameVisitor(AstTopScope* nodep, AstNodeModule* topModp) - : m_topModp{topModp} { - iterate(nodep); - } - ~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 - 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}; - } - void visit(AstNodeModule* nodep) override { - // Only track the top scopes, not lower level functions - if (nodep->isTop()) { - m_topModp = nodep; - iterateChildren(nodep); - } - } - void visit(AstNodeCCall* nodep) override { - iterateChildren(nodep); - if (!nodep->funcp()->entryPoint()) { - // Enter the function and trace it - m_tracingCall = true; - iterate(nodep->funcp()); - } - } - 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); - } - //---- - - 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); - } - } - void visit(AstNodeAssign* nodep) override { - // UINFO(8, "ASS " << nodep << endl); - m_assignp = nodep; - iterateChildren(nodep); - m_assignp = nullptr; - } - 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); - } - - //----- - void visit(AstVar*) override {} // Don't want varrefs under it - void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - explicit GenClkReadVisitor(AstNetlist* nodep) { iterate(nodep); } - ~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); -} From 47bce4157dacd3b177a20308fde8f70e3b0ca929 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 23 Sep 2022 16:46:22 +0100 Subject: [PATCH 048/177] Introduce DFG based combinational logic optimizer (#3527) Added a new data-flow graph (DFG) based combinational logic optimizer. The capabilities of this covers a combination of V3Const and V3Gate, but is also more capable of transforming combinational logic into simplified forms and more. This entail adding a new internal representation, `DfgGraph`, and appropriate `astToDfg` and `dfgToAst` conversion functions. The graph represents some of the combinational equations (~continuous assignments) in a module, and for the duration of the DFG passes, it takes over the role of AstModule. A bulk of the Dfg vertices represent expressions. These vertex classes, and the corresponding conversions to/from AST are mostly auto-generated by astgen, together with a DfgVVisitor that can be used for dynamic dispatch based on vertex (operation) types. The resulting combinational logic graph (a `DfgGraph`) is then optimized in various ways. Currently we perform common sub-expression elimination, variable inlining, and some specific peephole optimizations, but there is scope for more optimizations in the future using the same representation. The optimizer is run directly before and after inlining. The pre inline pass can operate on smaller graphs and hence converges faster, but still has a chance of substantially reducing the size of the logic on some designs, making inlining both faster and less memory intensive. The post inline pass can then optimize across the inlined module boundaries. No optimization is performed across a module boundary. For debugging purposes, each peephole optimization can be disabled individually via the -fno-dfg-peepnole- option, where is one of the optimizations listed in V3DfgPeephole.h, for example -fno-dfg-peephole-remove-not-not. The peephole patterns currently implemented were mostly picked based on the design that inspired this work, and on that design the optimizations yields ~30% single threaded speedup, and ~50% speedup on 4 threads. As you can imagine not having to haul around redundant combinational networks in the rest of the compilation pipeline also helps with memory consumption, and up to 30% peak memory usage of Verilator was observed on the same design. Gains on other arbitrary designs are smaller (and can be improved by analyzing those designs). For example OpenTitan gains between 1-15% speedup depending on build type. --- bin/verilator | 4 +- docs/guide/exe_verilator.rst | 31 + docs/internals.rst | 36 + src/Makefile_obj.in | 6 + src/V3Dfg.cpp | 535 +++++++++++ src/V3Dfg.h | 726 ++++++++++++++ src/V3DfgAstToDfg.cpp | 267 ++++++ src/V3DfgDfgToAst.cpp | 314 +++++++ src/V3DfgOptimizer.cpp | 292 ++++++ src/V3DfgOptimizer.h | 35 + src/V3DfgPasses.cpp | 213 +++++ src/V3DfgPasses.h | 113 +++ src/V3DfgPeephole.cpp | 1144 +++++++++++++++++++++++ src/V3DfgPeephole.h | 126 +++ src/V3Error.h | 1 + src/V3Hash.h | 5 + src/V3List.h | 3 + src/V3OptionParser.cpp | 4 + src/V3OptionParser.h | 5 +- src/V3Options.cpp | 15 + src/V3Options.h | 10 + src/Verilator.cpp | 16 + src/astgen | 147 +++ test_regress/t/t_altera_lpm.v | 1 + test_regress/t/t_cdc_async_bad.out | 8 +- test_regress/t/t_cellarray.pl | 2 +- test_regress/t/t_const_opt.pl | 3 +- test_regress/t/t_dfg_circular.pl | 18 + test_regress/t/t_dfg_circular.v | 19 + test_regress/t/t_dfg_peephole.cpp | 37 + test_regress/t/t_dfg_peephole.pl | 126 +++ test_regress/t/t_dfg_peephole.v | 227 +++++ test_regress/t/t_dfg_unhandled.pl | 20 + test_regress/t/t_dfg_unhandled.v | 16 + test_regress/t/t_dump_dfg.pl | 21 + test_regress/t/t_flag_expand_limit.pl | 2 +- test_regress/t/t_gate_chained.pl | 2 +- test_regress/t/t_gate_ormux.pl | 2 +- test_regress/t/t_inst_tree_inl1_pub0.pl | 2 +- test_regress/t/t_inst_tree_inl1_pub1.pl | 2 +- test_regress/t/t_math_wide_bad.out | 8 +- test_regress/t/t_order_wireloop.pl | 2 +- test_regress/t/t_var_escape.out | 214 ++--- test_regress/t/t_xml_first.out | 4 +- test_regress/t/t_xml_flat.out | 4 +- 45 files changed, 4653 insertions(+), 135 deletions(-) create mode 100644 src/V3Dfg.cpp create mode 100644 src/V3Dfg.h create mode 100644 src/V3DfgAstToDfg.cpp create mode 100644 src/V3DfgDfgToAst.cpp create mode 100644 src/V3DfgOptimizer.cpp create mode 100644 src/V3DfgOptimizer.h create mode 100644 src/V3DfgPasses.cpp create mode 100644 src/V3DfgPasses.h create mode 100644 src/V3DfgPeephole.cpp create mode 100644 src/V3DfgPeephole.h create mode 100755 test_regress/t/t_dfg_circular.pl create mode 100644 test_regress/t/t_dfg_circular.v create mode 100644 test_regress/t/t_dfg_peephole.cpp create mode 100755 test_regress/t/t_dfg_peephole.pl create mode 100644 test_regress/t/t_dfg_peephole.v create mode 100755 test_regress/t/t_dfg_unhandled.pl create mode 100644 test_regress/t/t_dfg_unhandled.v create mode 100755 test_regress/t/t_dump_dfg.pl diff --git a/bin/verilator b/bin/verilator index 0e2a882a9..247b4aae9 100755 --- a/bin/verilator +++ b/bin/verilator @@ -309,10 +309,12 @@ detailed descriptions of these arguments. +define+= Set preprocessor define --dpi-hdr-only Only produce the DPI header file --dump-defines Show preprocessor defines with -E - --dump-graph Enable dumping V3Graphs to .dot + --dump-dfg Enable dumping DfgGraphs to .dot files + --dump-graph Enable dumping V3Graphs to .dot files --dump-tree Enable dumping Ast .tree files --dump-tree-addrids Use short identifiers instead of addresses --dump- Enable dumping everything in source file + --dumpi-dfg Enable dumping DfgGraphs to .dot files at level --dumpi-graph Enable dumping V3Graphs to .dot files at level --dumpi-tree Enable dumping Ast .tree files at level --dumpi- Enable dumping everything in source file at level diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 14878f869..aff59dd5b 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -355,6 +355,11 @@ Summary: touch foo.v ; verilator -E --dump-defines foo.v +.. option:: --dump-dfg + + Rarely needed. Enable dumping DfgGraph .dot debug files with dumping + level 3. + .. option:: --dump-graph Rarely needed. Enable dumping V3Graph .dot debug files with dumping @@ -384,6 +389,11 @@ Summary: Rarely needed - for developer use. Enable all dumping in the given source file at level 3. +.. option:: --dumpi-dfg + + Rarely needed - for developer use. Set internal DfgGraph dumping level + globally to the specified value. + .. option:: --dumpi-graph Rarely needed - for developer use. Set internal V3Graph dumping level @@ -482,6 +492,27 @@ Summary: .. option:: -fno-dedup +.. option:: -fno-dfg + + Disable all use of the DFG based combinational logic optimizer. + Alias for :vlopt:`-fno-dfg-pre-inline` and :vlopt:`-fno-dfg-post-inline`. + +.. option:: -fno-dfg-peephole + + Disable the DFG peephole optimizer. + +.. option:: -fno-dfg-peephole- + + Disable individula DFG peephole optimizer pattern. + +.. option:: -fno-dfg-pre-inline + + Do not apply the DFG optimizer before inlining. + +.. option:: -fno-dfg-post-inline + + Do not apply the DFG optimizer after inlining. + .. option:: -fno-expand .. option:: -fno-gate diff --git a/docs/internals.rst b/docs/internals.rst index d575bccec..842c27d17 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -184,6 +184,34 @@ A number of predefined derived algorithm classes and access methods are provided and documented in ``V3GraphAlg.cpp``. +``DfgGraph`` +^^^^^^^^^^^^^ + +The data-flow graph based combinational logic optimizer (DFG optimizer) +converts an ``AstModule`` into a ``DfgGraph``. The graph represents the +combinational equations (~continuous assignments) in the module, and for the +duration of the DFG passes, it takes over the role of the represented +``AstModule``. The ``DfgGraph`` keeps holds of the represented ``AstModule``, +and the ``AstModule`` retains all other logic that is not representable as a +data-flow graph. At the end of optimization, the combinational logic +represented by the ``DfgGraph`` is converted back into AST form and is +re-inserted into the corresponding ``AstModule``. The ``DfgGraph`` is distinct +from ``V3Graph`` for efficiency and other desirable properties which make +writing DFG passes easier. + + +``DfgVertex`` +^^^^^^^^^^^^^ + +The ``DfgGraph`` represents combinational logic equations as a graph of +``DfgVertex`` vertices. Each sub-class of ``DfgVertex`` corresponds to an +expression (a sub-class of ``AstNodeMath``), a constanat, or a variable +reference. LValues and RValues referencing the same storage location are +represented by the same ``DfgVertex``. Consumers of such vertices read as the +LValue, writers of such vertices write the RValue. The bulk of the final +``DfgVertex`` sub-classes are generated by ``astgen`` from the corresponding +``AstNode`` definitions. + Scheduling ---------- @@ -1067,6 +1095,7 @@ given ````. For list type children, the getter is ````, and instead of the setter, there an ``add`` method is generated that appends new nodes (or lists of nodes) to the child list. + ``alias op`` operand alias directives """""""""""""""""""""""""""""""""""""""" @@ -1080,6 +1109,13 @@ super-class of the current node. Example: ``@astgen alias op1 := condp`` +Generating ``DfgVertex`` sub-classes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Most of the ``DfgVertex`` sub-classes are generated by ``astgen``, from the +definitions of the corresponding ``AstNode`` vertices. + + Additional features of ``astgen`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index e435abf36..a6430e10c 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -180,6 +180,12 @@ RAW_OBJS = \ V3Depth.o \ V3DepthBlock.o \ V3Descope.o \ + V3Dfg.o \ + V3DfgAstToDfg.o \ + V3DfgDfgToAst.o \ + V3DfgOptimizer.o \ + V3DfgPasses.o \ + V3DfgPeephole.o \ V3DupFinder.o \ V3Timing.o \ V3EmitCBase.o \ diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp new file mode 100644 index 000000000..24658be08 --- /dev/null +++ b/src/V3Dfg.cpp @@ -0,0 +1,535 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Data flow graph (DFG) representation of 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 +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Dfg.h" + +#include "V3File.h" + +#include +#include + +//------------------------------------------------------------------------------ +// DfgGraph +//------------------------------------------------------------------------------ + +DfgGraph::DfgGraph(AstModule& module, const string& name) + : m_modulep{&module} + , m_name{name} {} + +DfgGraph::~DfgGraph() { + forEachVertex([](DfgVertex& vtxp) { delete &vtxp; }); +} + +void DfgGraph::addGraph(DfgGraph& other) { + other.forEachVertex([&](DfgVertex& vtx) { + other.removeVertex(vtx); + this->addVertex(vtx); + }); +} + +bool DfgGraph::sortTopologically(bool reverse) { + // Vertices in reverse topological order + std::vector order; + + // Markings for algorithm + enum class Mark : uint8_t { Scheduled, OnPath, Finished }; + std::unordered_map marks; + + // Stack of nodes in depth first search. The second element of the pair is true if the vertex + // is on the current DFS path, and false if it's only scheduled for visitation. + std::vector> stack; + + // Schedule vertex for visitation + const auto scheudle = [&](DfgVertex& vtx) { + // Nothing to do if already finished + if (marks.emplace(&vtx, Mark::Scheduled).first->second == Mark::Finished) return; + // Otherwise scheule for visitation + stack.emplace_back(&vtx, false); + }; + + // For each vertex (direct loop, so we can return early) + for (DfgVertex* vtxp = m_vertices.begin(); vtxp; vtxp = vtxp->m_verticesEnt.nextp()) { + // Initiate DFS from this vertex + scheudle(*vtxp); + while (!stack.empty()) { + // Pick up stack top + const auto pair = stack.back(); + stack.pop_back(); + DfgVertex* const currp = pair.first; + const bool onPath = pair.second; + Mark& mark = marks.at(currp); + + if (onPath) { // Popped node on path + // Mark it as done + UASSERT_OBJ(mark == Mark::OnPath, currp, "DFS got lost"); + mark = Mark::Finished; + // Add to order + order.push_back(currp); + } else { // Otherwise node was scheduled for visitation, so visit it + // If already finished, then nothing to do + if (mark == Mark::Finished) continue; + // If already on path, then not a DAG + if (mark == Mark::OnPath) return false; + // Push to path and mark as such + mark = Mark::OnPath; + stack.emplace_back(currp, true); + // Schedule children + currp->forEachSink(scheudle); + } + } + } + + // Move given vertex to end of vertex list + const auto reinsert = [this](DfgVertex& vtx) { + // Remove from current location + removeVertex(vtx); + // 'addVertex' appends to the end of the vertex list, so can do this in one loop + addVertex(vtx); + }; + + // Remember 'order' is in reverse topological order + if (!reverse) { + for (DfgVertex* vtxp : vlstd::reverse_view(order)) reinsert(*vtxp); + } else { + for (DfgVertex* vtxp : order) reinsert(*vtxp); + } + + // Done + return true; +} + +std::vector> DfgGraph::splitIntoComponents() { + size_t componentNumber = 0; + std::unordered_map vertex2component; + + forEachVertex([&](const DfgVertex& vtx) { + // If already assigned this vertex to a component, then continue + if (vertex2component.count(&vtx)) return; + + // Work queue for depth first traversal starting from this vertex + std::vector queue{&vtx}; + + // Depth first traversal + while (!queue.empty()) { + // Pop next work item + const DfgVertex& item = *queue.back(); + queue.pop_back(); + + // Mark vertex as belonging to current component (if it's not marked yet) + const bool isFirstEncounter = vertex2component.emplace(&item, componentNumber).second; + + // If we have already visited this vertex during the traversal, then move on. + if (!isFirstEncounter) continue; + + // Enqueue all sources and sinks of this vertex. + item.forEachSource([&](const DfgVertex& src) { queue.push_back(&src); }); + item.forEachSink([&](const DfgVertex& dst) { queue.push_back(&dst); }); + } + + // Done with this component + ++componentNumber; + }); + + // Create the component graphs + std::vector> results{componentNumber}; + + for (size_t i = 0; i < componentNumber; ++i) { + results[i].reset(new DfgGraph{*m_modulep, name() + "-component-" + cvtToStr(i)}); + } + + // Move all vertices under the corresponding component graphs + forEachVertex([&](DfgVertex& vtx) { + this->removeVertex(vtx); + results[vertex2component[&vtx]]->addVertex(vtx); + }); + + UASSERT(size() == 0, "'this' DfgGraph should have been emptied"); + + return results; +} + +void DfgGraph::runToFixedPoint(std::function f) { + bool changed; + const auto apply = [&](DfgVertex& vtx) -> void { + if (f(vtx)) changed = true; + }; + while (true) { + // Do one pass over the graph. + changed = false; + forEachVertex(apply); + if (!changed) break; + // Do another pass in the opposite direction. Alternating directions reduces + // the pathological complexity with left/right leaning trees. + changed = false; + forEachVertexInReverse(apply); + if (!changed) break; + } +} + +static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; } + +// Dump one DfgVertex in Graphviz format +static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { + os << toDotId(vtx); + if (const DfgVar* const varVtxp = vtx.cast()) { + AstVar* const varp = varVtxp->varp(); + os << " [label=\"" << varp->name() << "\nW" << varVtxp->width() << " / F" + << varVtxp->fanout() << '"'; + if (varp->isIO()) { + if (varp->direction() == VDirection::INPUT) { + os << ", shape=house, orientation=270"; + } else if (varp->direction() == VDirection::OUTPUT) { + os << ", shape=house, orientation=90"; + } else { + os << ", shape=star"; + } + } else if (varVtxp->hasExtRefs()) { + os << ", shape=box, style=diagonals,filled, fillcolor=red"; + } else if (varVtxp->hasModRefs()) { + os << ", shape=box, style=diagonals"; + } else { + os << ", shape=box"; + } + os << "]"; + } else if (const DfgConst* const constVtxp = vtx.cast()) { + const V3Number& num = constVtxp->constp()->num(); + os << " [label=\""; + if (num.width() <= 32 && !num.isSigned()) { + const bool feedsSel = !constVtxp->findSink([](const DfgVertex& vtx) { // + return !vtx.is(); + }); + if (feedsSel) { + os << num.toUInt(); + } else { + os << constVtxp->width() << "'d" << num.toUInt() << "\n"; + os << constVtxp->width() << "'h" << std::hex << num.toUInt() << std::dec; + } + } else { + os << num.ascii(); + } + os << '"'; + os << ", shape=plain"; + os << "]"; + } else { + os << " [label=\"" << vtx.typeName() << "\nW" << vtx.width() << " / F" << vtx.fanout() + << '"'; + if (vtx.hasMultipleSinks()) + os << ", shape=doublecircle"; + else + os << ", shape=circle"; + os << "]"; + } + os << endl; +} + +// Dump one DfgEdge in Graphviz format +static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, const string& headlabel) { + os << toDotId(*edge.sourcep()) << " -> " << toDotId(*edge.sinkp()); + if (!headlabel.empty()) os << " [headlabel=\"" << headlabel << "\"]"; + os << endl; +} + +// Dump one DfgVertex and all of its source DfgEdges in Graphviz format +static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) { + dumpDotVertex(os, vtx); + vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { // + if (edge.sourcep()) { + string headLabel; + if (vtx.arity() > 1) headLabel = std::toupper(vtx.srcName(idx)[0]); + dumpDotEdge(os, edge, headLabel); + } + }); +} + +void DfgGraph::dumpDot(std::ostream& os, const string& label) const { + // Header + os << "digraph dfg {" << endl; + os << "graph [label=\"" << name(); + if (!label.empty()) os << "-" << label; + os << "\", labelloc=t, labeljust=l]" << endl; + os << "graph [rankdir=LR]" << endl; + + // Emit all vertices + forEachVertex([&](const DfgVertex& vtx) { dumpDotVertexAndSourceEdges(os, vtx); }); + + // Footer + os << "}" << endl; +} + +void DfgGraph::dumpDotFile(const string& fileName, const string& label) const { + // This generates a file used by graphviz, https://www.graphviz.org + // "hardcoded" parameters: + const std::unique_ptr os{V3File::new_ofstream(fileName)}; + if (os->fail()) v3fatal("Cannot write to file: " << fileName); + dumpDot(*os.get(), label); + os->close(); +} + +void DfgGraph::dumpDotFilePrefixed(const string& label) const { + string fileName = name(); + if (!label.empty()) fileName += "-" + label; + dumpDotFile(v3Global.debugFilename(fileName) + ".dot", label); +} + +// Dump upstream logic cone starting from given vertex +static void dumpDotUpstreamConeFromVertex(std::ostream& os, const DfgVertex& vtx) { + // Work queue for depth first traversal starting from this vertex + std::vector queue{&vtx}; + + // Set of already visited vertices + std::unordered_set visited; + + // Depth first traversal + while (!queue.empty()) { + // Pop next work item + const DfgVertex* const itemp = queue.back(); + queue.pop_back(); + + // Mark vertex as visited + const bool isFirstEncounter = visited.insert(itemp).second; + + // If we have already visited this vertex during the traversal, then move on. + if (!isFirstEncounter) continue; + + // Enqueue all sources of this vertex. + itemp->forEachSource([&](const DfgVertex& src) { queue.push_back(&src); }); + + // Emit this vertex and all of its source edges + dumpDotVertexAndSourceEdges(os, *itemp); + } + + // Emit all DfgVar vertices that have external references driven by this vertex + vtx.forEachSink([&](const DfgVertex& dst) { + if (const DfgVar* const varVtxp = dst.cast()) { + if (varVtxp->hasRefs()) dumpDotVertexAndSourceEdges(os, dst); + } + }); +} + +// LCOV_EXCL_START // Debug function for developer use only +void DfgGraph::dumpDotUpstreamCone(const string& fileName, const DfgVertex& vtx, + const string& name) const { + // Open output file + const std::unique_ptr os{V3File::new_ofstream(fileName)}; + if (os->fail()) v3fatal("Cannot write to file: " << fileName); + + // Header + *os << "digraph dfg {" << endl; + if (!name.empty()) *os << "graph [label=\"" << name << "\", labelloc=t, labeljust=l]" << endl; + *os << "graph [rankdir=LR]" << endl; + + // Dump the cone + dumpDotUpstreamConeFromVertex(*os, vtx); + + // Footer + *os << "}" << endl; + + // Done + os->close(); +} +// LCOV_EXCL_STOP + +void DfgGraph::dumpDotAllVarConesPrefixed(const string& label) const { + const string prefix = label.empty() ? name() + "-cone-" : name() + "-" + label + "-cone-"; + forEachVertex([&](const DfgVertex& vtx) { + // Check if this vertex drives a variable referenced outside the DFG. + const DfgVar* const sinkp = vtx.findSink([](const DfgVar& sink) { // + return sink.hasRefs(); + }); + + // We only dump cones driving an externally referenced variable + if (!sinkp) return; + + // Open output file + const string coneName{prefix + sinkp->varp()->name()}; + const string fileName{v3Global.debugFilename(coneName) + ".dot"}; + const std::unique_ptr os{V3File::new_ofstream(fileName)}; + if (os->fail()) v3fatal("Cannot write to file: " << fileName); + + // Header + *os << "digraph dfg {" << endl; + *os << "graph [label=\"" << coneName << "\", labelloc=t, labeljust=l]" << endl; + *os << "graph [rankdir=LR]" << endl; + + // Dump this cone + dumpDotUpstreamConeFromVertex(*os, vtx); + + // Footer + *os << "}" << endl; + + // Done with this logic cone + os->close(); + }); +} + +//------------------------------------------------------------------------------ +// DfgEdge +//------------------------------------------------------------------------------ + +void DfgEdge::unlinkSource() { + if (!m_sourcep) return; +#ifdef VL_DEBUG + { + DfgEdge* sinkp = m_sourcep->m_sinksp; + while (sinkp) { + if (sinkp == this) break; + sinkp = sinkp->m_nextp; + } + UASSERT(sinkp, "'m_sourcep' does not have this edge as sink"); + } +#endif + // Relink pointers of predecessor and successor + if (m_prevp) m_prevp->m_nextp = m_nextp; + if (m_nextp) m_nextp->m_prevp = m_prevp; + // If head of list in source, update source's head pointer + if (m_sourcep->m_sinksp == this) m_sourcep->m_sinksp = m_nextp; + // Mark source as unconnected + m_sourcep = nullptr; + // Clear links. This is not strictly necessary, but might catch bugs. + m_prevp = nullptr; + m_nextp = nullptr; +} + +void DfgEdge::relinkSource(DfgVertex* newSourcep) { + // Unlink current source, if any + unlinkSource(); + // Link new source + m_sourcep = newSourcep; + // Prepend to sink list in source + m_nextp = newSourcep->m_sinksp; + if (m_nextp) m_nextp->m_prevp = this; + newSourcep->m_sinksp = this; +} + +//------------------------------------------------------------------------------ +// DfgVertex +//------------------------------------------------------------------------------ + +DfgVertex::DfgVertex(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type) + : m_filelinep{flp} + , m_dtypep{dtypep} + , m_type{type} { + dfg.addVertex(*this); +} + +bool DfgVertex::selfEquals(const DfgVertex& that) const { + return this->m_type == that.m_type && this->dtypep() == that.dtypep(); +} + +V3Hash DfgVertex::selfHash() const { return V3Hash{m_type} + width(); } + +bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const { + if (this == &that) return true; + if (!this->selfEquals(that)) return false; + + const auto key = (this < &that) ? EqualsCache::key_type{this, &that} // + : EqualsCache::key_type{&that, this}; + const auto pair = cache.emplace(key, true); + bool& result = pair.first->second; + if (pair.second) { + auto thisPair = this->sourceEdges(); + const DfgEdge* const thisSrcEdgesp = thisPair.first; + const size_t thisArity = thisPair.second; + auto thatPair = that.sourceEdges(); + const DfgEdge* const thatSrcEdgesp = thatPair.first; + const size_t thatArity = thatPair.second; + UASSERT_OBJ(thisArity == thatArity, this, "Same type vertices must have same arity!"); + for (size_t i = 0; i < thisArity; ++i) { + const DfgVertex* const thisSrcVtxp = thisSrcEdgesp[i].m_sourcep; + const DfgVertex* const thatSrcVtxp = thatSrcEdgesp[i].m_sourcep; + if (thisSrcVtxp == thatSrcVtxp) continue; + if (!thisSrcVtxp || !thatSrcVtxp || !thisSrcVtxp->equals(*thatSrcVtxp, cache)) { + result = false; + break; + } + } + } + return result; +} + +V3Hash DfgVertex::hash(HashCache& cache) const { + const auto pair = cache.emplace(this, V3Hash{}); + V3Hash& result = pair.first->second; + if (pair.second) { + result += selfHash(); + forEachSource([&result, &cache](const DfgVertex& src) { result += src.hash(cache); }); + } + return result; +} + +uint32_t DfgVertex::fanout() const { + uint32_t result = 0; + forEachSinkEdge([&](const DfgEdge&) { ++result; }); + return result; +} + +void DfgVertex::unlinkDelete(DfgGraph& dfg) { + // Unlink source edges + forEachSourceEdge([](DfgEdge& edge, size_t) { edge.unlinkSource(); }); + // Unlink sink edges + forEachSinkEdge([](DfgEdge& edge) { edge.unlinkSource(); }); + // Remove from graph + dfg.removeVertex(*this); + // Delete + delete this; +} + +void DfgVertex::replaceWith(DfgVertex* newSorucep) { + while (m_sinksp) m_sinksp->relinkSource(newSorucep); +} + +//------------------------------------------------------------------------------ +// Vertex classes +//------------------------------------------------------------------------------ + +// DfgVar ---------- +void DfgVar::accept(DfgVisitor& visitor) { visitor.visit(this); } + +bool DfgVar::selfEquals(const DfgVertex& that) const { + if (const DfgVar* otherp = that.cast()) return varp() == otherp->varp(); + return false; +} + +V3Hash DfgVar::selfHash() const { return V3Hasher::uncachedHash(m_varp); } + +// DfgConst ---------- +void DfgConst::accept(DfgVisitor& visitor) { visitor.visit(this); } + +bool DfgConst::selfEquals(const DfgVertex& that) const { + if (const DfgConst* otherp = that.cast()) { + return constp()->sameTree(otherp->constp()); + } + return false; +} + +V3Hash DfgConst::selfHash() const { return V3Hasher::uncachedHash(m_constp); } + +//------------------------------------------------------------------------------ +// DfgVisitor +//------------------------------------------------------------------------------ + +void DfgVisitor::visit(DfgVar* vtxp) { visit(static_cast(vtxp)); } + +void DfgVisitor::visit(DfgConst* vtxp) { visit(static_cast(vtxp)); } + +//------------------------------------------------------------------------------ +// 'astgen' generated definitions +//------------------------------------------------------------------------------ + +#include "V3Dfg__gen_definitions.h" diff --git a/src/V3Dfg.h b/src/V3Dfg.h new file mode 100644 index 000000000..94fcac4ed --- /dev/null +++ b/src/V3Dfg.h @@ -0,0 +1,726 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Data flow graph (DFG) representation of 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 +// +//************************************************************************* +// +// This is a data-flow graph based representation of combinational logic, +// the main difference from a V3Graph is that DfgVertex owns the storage +// of it's input edges (operands/sources/arguments), and can access each +// input edge directly by indexing, making modifications more efficient +// than the linked list based structures used by V3Graph. +// +// A bulk of the DfgVertex sub-types are generated by astgen, and are +// analogous to the correspondign AstNode sub-types. +// +// See also the internals documentation docs/internals.rst +// +//************************************************************************* + +#ifndef VERILATOR_V3DFG_H_ +#define VERILATOR_V3DFG_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3Error.h" +#include "V3Hash.h" +#include "V3Hasher.h" +#include "V3List.h" + +#include +#include +#include +#include + +class DfgVertex; +class DfgEdge; +class DfgVisitor; + +//------------------------------------------------------------------------------ + +// Specialization of std::hash for a std::pair for use below +template <> +struct std::hash> final { + size_t operator()(const std::pair& item) const { + const size_t a = reinterpret_cast(item.first); + const size_t b = reinterpret_cast(item.second); + constexpr size_t halfWidth = 8 * sizeof(b) / 2; + return a ^ ((b << halfWidth) | (b >> halfWidth)); + } +}; + +//------------------------------------------------------------------------------ +// Dataflow graph +//------------------------------------------------------------------------------ + +class DfgGraph final { + friend class DfgVertex; + + // MEMBERS + size_t m_size = 0; // Number of vertices in the graph + V3List m_vertices; // The vertices in the graph + // Parent of the graph (i.e.: the module containing the logic represented by this graph). + AstModule* const m_modulep; + const string m_name; // Name of graph (for debugging) + +public: + // CONSTRUCTOR + explicit DfgGraph(AstModule& module, const string& name = ""); + ~DfgGraph(); + VL_UNCOPYABLE(DfgGraph); + + // METHODS +private: + // Add DfgVertex to this graph (assumes not yet contained). + inline void addVertex(DfgVertex& vtx); + // Remove DfgVertex form this graph (assumes it is contained). + inline void removeVertex(DfgVertex& vtx); + +public: + // Number of vertices in this graph + size_t size() const { return m_size; } + + // Parent module + AstModule* modulep() const { return m_modulep; } + + // Name of this graph + const string& name() const { return m_name; } + + // Calls given function 'f' for each vertex in the graph. It is safe to manipulate any vertices + // in the graph, or to delete/unlink the vertex passed to 'f' during iteration. It is however + // not safe to delete/unlink any vertex in the same graph other than the one passed to 'f'. + inline void forEachVertex(std::function f); + + // 'const' variant of 'forEachVertex'. No mutation allowed. + inline void forEachVertex(std::function f) const; + + // Same as 'forEachVertex' but iterates in reverse order. + inline void forEachVertexInReverse(std::function f); + + // Returns first vertex of type 'Vertex' that satisfies the given predicate 'p', + // or nullptr if no such vertex exists in the graph. + template + inline Vertex* findVertex(std::function p) const; + + // Add contents of other graph to this graph. Leaves other graph empty. + void addGraph(DfgGraph& other); + + // Topologically sort the list of vertices in this graph (such that 'forEachVertex' will + // iterate in topological order), or reverse topologically if the passed boolean argument is + // true. Returns true on success (the graph is acyclic and a topological order exists), false + // if the graph is cyclic. If the graph is cyclic, the vertex ordering is not modified. + bool sortTopologically(bool reverse = false); + + // Split this graph into individual components (unique sub-graphs with no edges between them). + // Leaves 'this' graph empty. + std::vector> splitIntoComponents(); + + // Apply the given function to all vertices in the graph. The function return value indicates + // that a change has been made to the graph. Repeat until no changes reported. + void runToFixedPoint(std::function f); + + // Dump graph in Graphviz format into the given stream 'os'. 'label' is added to the name of + // the graph which is included in the output. + void dumpDot(std::ostream& os, const string& label = "") const; + // Dump graph in Graphviz format into a new file with the given 'fileName'. 'label' is added to + // the name of the graph which is included in the output. + void dumpDotFile(const string& fileName, const string& label = "") const; + // Dump graph in Graphviz format into a new automatically numbered debug file. 'label' is + // added to the name of the graph, which is included in the file name and the output. + void dumpDotFilePrefixed(const string& label = "") const; + // Dump upstream (source) logic cone starting from given vertex into a file with the given + // 'fileName'. 'name' is the name of the graph, which is included in the output. + void dumpDotUpstreamCone(const string& fileName, const DfgVertex& vtx, + const string& name = "") const; + // Dump all individual logic cones driving external variables in Graphviz format into separate + // new automatically numbered debug files. 'label' is added to the name of the graph, which is + // included in the file names and the output. This is useful for very large graphs that are + // otherwise difficult to browse visually due to their size. + void dumpDotAllVarConesPrefixed(const string& label = "") const; +}; + +//------------------------------------------------------------------------------ +// Dataflow graph edge +//------------------------------------------------------------------------------ + +class DfgEdge final { + friend class DfgVertex; + template + friend class DfgVertexWithArity; + + DfgEdge* m_nextp = nullptr; // Next edge in sink list + DfgEdge* m_prevp = nullptr; // Previous edge in sink list + DfgVertex* m_sourcep = nullptr; // The source vertex driving this edge + DfgVertex* const m_sinkp; // The sink vertex. The sink owns the edge, so immutable + + explicit DfgEdge(DfgVertex* sinkp) // The sink vertices own the edges, hence private + : m_sinkp{sinkp} {} + +public: + // The source (driver) of this edge + DfgVertex* sourcep() const { return m_sourcep; } + // The sink (consumer) of this edge + DfgVertex* sinkp() const { return m_sinkp; } + // Remove driver of this edge + void unlinkSource(); + // Relink this edge to be driven from the given new source vertex + void relinkSource(DfgVertex* newSourcep); +}; + +//------------------------------------------------------------------------------ +// Dataflow graph vertex +//------------------------------------------------------------------------------ + +// Reuse the generated type constants +using DfgType = VNType; + +// Base data flow graph vertex +class DfgVertex VL_NOT_FINAL { + friend class DfgGraph; + friend class DfgEdge; + friend class DfgVisitor; + + // STATE + V3ListEnt m_verticesEnt; // V3List handle of this vertex, kept under the DfgGraph +protected: + DfgEdge* m_sinksp = nullptr; // List of sinks of this vertex + FileLine* const m_filelinep; // Source location + AstNodeDType* m_dtypep = nullptr; // Data type of the result of this vertex + const DfgType m_type; + + // CONSTRUCTOR + DfgVertex(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type); + +public: + virtual ~DfgVertex() = default; + + // METHODS +private: + // Visitor accept method + virtual void accept(DfgVisitor& v) = 0; + + // Part of Vertex equality only dependent on this vertex + virtual bool selfEquals(const DfgVertex& that) const; + + // Part of Vertex hash only dependent on this vertex + virtual V3Hash selfHash() const; + +public: + // Returns true if an AstNode with the given 'dtype' can be represented as a DfgVertex + static bool isSupportedDType(const AstNodeDType* dtypep) { + // Conservatively only support bit-vector like basic types and packed arrays of the same + dtypep = dtypep->skipRefp(); + if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) { + return typep->keyword().isIntNumeric(); + } + if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) { + return isSupportedDType(typep->subDTypep()); + } + return false; + } + + // Return data type used to represent any packed value of the given 'width'. All packed types + // of a given width use the same canonical data type, as the only interesting information is + // the total width. + static AstNodeDType* dtypeForWidth(uint32_t width) { + return v3Global.rootp()->typeTablep()->findLogicDType(width, width, VSigning::UNSIGNED); + } + + // Return data type used to represent the type of 'nodep' when converted to a DfgVertex + static AstNodeDType* dtypeFor(const AstNode* nodep) { + UDEBUGONLY(UASSERT_OBJ(isSupportedDType(nodep->dtypep()), nodep, "Unsupported dtype");); + // Currently all supported types are packed, so this is simple + return dtypeForWidth(nodep->width()); + } + + // Source location + FileLine* fileline() const { return m_filelinep; } + // The data type of the result of the nodes + AstNodeDType* dtypep() const { return m_dtypep; } + + // Width of result + uint32_t width() const { + // Everything supported is packed now, so we can just do this: + return dtypep()->width(); + } + + // Cache type for 'equals' below + using EqualsCache = std::unordered_map, bool>; + + // Vertex equality (based on this vertex and all upstream vertices feeding into this vertex). + // Returns true, if the vertices can be substituted for each other without changing the + // semantics of the logic. The 'cache' argument is used to store results to avoid repeat + // evaluations, but it requires that the upstream sources of the compared vertices do not + // change between invocations. + bool equals(const DfgVertex& that, EqualsCache& cache) const; + + // Uncached version of 'equals' + bool equals(const DfgVertex& that) const { + EqualsCache cache; // Still cache recursive calls within this invocation + return equals(that, cache); + } + + // Cache type for 'hash' below + using HashCache = std::unordered_map; + + // Hash of vertex (depends on this vertex and all upstream vertices feeding into this vertex). + // The 'cache' argument is used to store results to avoid repeat evaluations, but it requires + // that the upstream sources of the vertex do not change between invocations. + V3Hash hash(HashCache& cache) const; + + // Uncached version of 'hash' + V3Hash hash() const { + HashCache cache; // Still cache recursive calls within this invocation + return hash(cache); + } + + // Source edges of this vertex + virtual std::pair sourceEdges() { return {nullptr, 0}; } + + // Source edges of this vertex + virtual std::pair sourceEdges() const { return {nullptr, 0}; } + + // Arity (number of sources) of this vertex + size_t arity() const { return sourceEdges().second; } + + // Predicate: has 1 or more sinks + bool hasSinks() const { return m_sinksp != nullptr; } + + // Predicate: has 2 or more sinks + bool hasMultipleSinks() const { return m_sinksp && m_sinksp->m_nextp; } + + // Fanout (number of sinks) of this vertex (expensive to compute) + uint32_t fanout() const; + + // Unlink from container (graph or builder), then delete this vertex + void unlinkDelete(DfgGraph& dfg); + + // Relink all sinks to be driven from the given new source + void replaceWith(DfgVertex* newSourcep); + + // Calls given function 'f' for each source vertex of this vertex + // Unconnected source edges are not iterated. + inline void forEachSource(std::function f) const; + + // Calls given function 'f' for each source edge of this vertex. Also passes source index. + inline void forEachSourceEdge(std::function f); + + // Calls given function 'f' for each source edge of this vertex. Also passes source index. + inline void forEachSourceEdge(std::function f) const; + + // Calls given function 'f' for each sink vertex of this vertex + inline void forEachSink(std::function f); + + // Calls given function 'f' for each sink vertex of this vertex + inline void forEachSink(std::function f) const; + + // Calls given function 'f' for each sink edge of this vertex. + // Unlinking/deleting the given sink during iteration is safe, but not other sinks of this + // vertex. + inline void forEachSinkEdge(std::function f); + + // Calls given function 'f' for each sink edge of this vertex. + inline void forEachSinkEdge(std::function f) const; + + // Returns first sink vertex of type 'Vertex' which satisfies the given predicate 'p', + // or nullptr if no such sink vertex exists + template + inline Vertex* findSink(std::function p) const; + + // Returns first sink vertex of type 'Vertex', or nullptr if no such sink vertex exists. + // This is a special case of 'findSink' above with the predicate always true. + template + inline Vertex* findSink() const; + + // Is this a DfgConst that is all zeroes + inline bool isZero() const; + + // Is this a DfgConst that is all ones + inline bool isOnes() const; + + // Methods that allow DfgVertex to participate in error reporting/messaging + void v3errorEnd(std::ostringstream& str) const { m_filelinep->v3errorEnd(str); } + void v3errorEndFatal(std::ostringstream& str) const VL_ATTR_NORETURN { + m_filelinep->v3errorEndFatal(str); + } + string warnContextPrimary() const { return fileline()->warnContextPrimary(); } + string warnContextSecondary() const { return fileline()->warnContextSecondary(); } + string warnMore() const { return fileline()->warnMore(); } + string warnOther() const { return fileline()->warnOther(); } + + // Subtype test + template + bool is() const { + static_assert(std::is_base_of::value, "'T' must be a subtype of DfgVertex"); + return m_type == T::dfgType(); + } + + // Ensure subtype, then cast to that type + template + T* as() { + UASSERT_OBJ(is(), this, + "DfgVertex is not of expected type, but instead has type '" << typeName() + << "'"); + return static_cast(this); + } + template + const T* as() const { + UASSERT_OBJ(is(), this, + "DfgVertex is not of expected type, but instead has type '" << typeName() + << "'"); + return static_cast(this); + } + + // Cast to subtype, or null if different + template + T* cast() { + return is() ? static_cast(this) : nullptr; + } + template + const T* cast() const { + return is() ? static_cast(this) : nullptr; + } + + // Human-readable vertex type as string for debugging + const string typeName() const { return m_type.ascii(); } + + // Human-readable name for source operand with given index for debugging + virtual const string srcName(size_t idx) const = 0; +}; + +// DfgVertices are, well ... DfgVertices +template <> +constexpr bool DfgVertex::is() const { + return true; +} +template <> +constexpr DfgVertex* DfgVertex::as() { + return this; +} +template <> +constexpr const DfgVertex* DfgVertex::as() const { + return this; +} +template <> +constexpr DfgVertex* DfgVertex::cast() { + return this; +} +template <> +constexpr const DfgVertex* DfgVertex::cast() const { + return this; +} + +template +class DfgVertexWithArity VL_NOT_FINAL : public DfgVertex { + static_assert(1 <= Arity && Arity <= 4, "Arity must be between 1 and 4 inclusive"); + + // Uninitialized storage for source edges + typename std::aligned_storage::type + m_sourceEdges; + + constexpr DfgEdge& sourceEdge(size_t index) { + return reinterpret_cast(&m_sourceEdges)[index]; + } + constexpr const DfgEdge& sourceEdge(size_t index) const { + return reinterpret_cast(&m_sourceEdges)[index]; + } + +protected: + DfgVertexWithArity(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type) + : DfgVertex{dfg, flp, dtypep, type} { + // Initialize source edges + for (size_t i = 0; i < Arity; ++i) new (&sourceEdge(i)) DfgEdge{this}; + } + + virtual ~DfgVertexWithArity() = default; + +public: + std::pair sourceEdges() override { // + return {&sourceEdge(0), Arity}; + } + std::pair sourceEdges() const override { + return {&sourceEdge(0), Arity}; + } + + template + DfgVertex* source() const { + static_assert(Index < Arity, "Source index out of range"); + return sourceEdge(Index).m_sourcep; + } + + template + void relinkSource(DfgVertex* newSourcep) { + static_assert(Index < Arity, "Source index out of range"); + UASSERT_OBJ(sourceEdge(Index).m_sinkp == this, this, "Inconsistent"); + sourceEdge(Index).relinkSource(newSourcep); + } + + // Named source getter/setter for unary vertices + template + typename std::enable_if::type srcp() const { + static_assert(A == Arity, "Should not be changed"); + return source<0>(); + } + template + typename std::enable_if::type srcp(DfgVertex* vtxp) { + static_assert(A == Arity, "Should not be changed"); + relinkSource<0>(vtxp); + } + + // Named source getter/setter for binary vertices + template + typename std::enable_if::type lhsp() const { + static_assert(A == Arity, "Should not be changed"); + return source<0>(); + } + template + typename std::enable_if::type lhsp(DfgVertex* vtxp) { + static_assert(A == Arity, "Should not be changed"); + relinkSource<0>(vtxp); + } + + template + typename std::enable_if::type rhsp() const { + static_assert(A == Arity, "Should not be changed"); + return source<1>(); + } + template + typename std::enable_if::type rhsp(DfgVertex* vtxp) { + static_assert(A == Arity, "Should not be changed"); + relinkSource<1>(vtxp); + } +}; + +//------------------------------------------------------------------------------ +// Vertex classes +//------------------------------------------------------------------------------ + +class DfgVar final : public DfgVertexWithArity<1> { + friend class DfgVertex; + friend class DfgVisitor; + + AstVar* const m_varp; // The AstVar associated with this vertex (not owned by this vertex) + FileLine* m_assignmentFlp; // The FileLine of the original assignment driving this var + bool m_hasModRefs = false; // This AstVar is referenced outside the DFG, but in the module + bool m_hasExtRefs = false; // This AstVar is referenced from outside the module + + void accept(DfgVisitor& visitor) override; + bool selfEquals(const DfgVertex& that) const override; + V3Hash selfHash() const override; + static constexpr DfgType dfgType() { return DfgType::atVar; }; + +public: + DfgVar(DfgGraph& dfg, AstVar* varp) + : DfgVertexWithArity<1>{dfg, varp->fileline(), dtypeFor(varp), dfgType()} + , m_varp{varp} {} + + AstVar* varp() const { return m_varp; } + FileLine* assignmentFileline() const { return m_assignmentFlp; } + void assignmentFileline(FileLine* flp) { m_assignmentFlp = flp; } + bool hasModRefs() const { return m_hasModRefs; } + void setHasModRefs() { m_hasModRefs = true; } + bool hasExtRefs() const { return m_hasExtRefs; } + void setHasExtRefs() { m_hasExtRefs = true; } + bool hasRefs() const { return m_hasModRefs || m_hasExtRefs; } + + DfgVertex* driverp() const { return srcp(); } + void driverp(DfgVertex* vtxp) { srcp(vtxp); } + + // Variable cannot be removed, even if redundant in the DfgGraph (might be used externally) + bool keep() const { + // Keep if referenced outside this module + if (hasExtRefs()) return true; + // Keep if traced + if (v3Global.opt.trace() && varp()->isTrace()) return true; + // Keep if public + if (varp()->isSigPublic()) return true; + // Otherwise it can be removed + return false; + } + + const string srcName(size_t) const override { return "driverp"; } +}; + +class DfgConst final : public DfgVertex { + friend class DfgVertex; + friend class DfgVisitor; + + AstConst* const m_constp; // The AstConst associated with this vertex (owned by this vertex) + + void accept(DfgVisitor& visitor) override; + bool selfEquals(const DfgVertex& that) const override; + V3Hash selfHash() const override; + static constexpr DfgType dfgType() { return DfgType::atConst; }; + +public: + DfgConst(DfgGraph& dfg, AstConst* constp) + : DfgVertex{dfg, constp->fileline(), dtypeFor(constp), dfgType()} + , m_constp{constp} {} + + ~DfgConst() { VL_DO_DANGLING(m_constp->deleteTree(), m_constp); } + + AstConst* constp() const { return m_constp; } + V3Number& num() const { return m_constp->num(); } + + uint32_t toU32() const { return num().toUInt(); } + int32_t toI32() const { return num().toSInt(); } + + bool isZero() const { return num().isEqZero(); } + bool isOnes() const { return num().isEqAllOnes(width()); } + + const string srcName(size_t) const override { // LCOV_EXCL_START + VL_UNREACHABLE; + return ""; + } // LCOV_EXCL_STOP +}; + +// The rest of the DfgVertex subclasses are generated by 'astgen' from AstNodeMath nodes +#include "V3Dfg__gen_vertex_classes.h" + +//------------------------------------------------------------------------------ +// Dfg vertex visitor +//------------------------------------------------------------------------------ + +class DfgVisitor VL_NOT_FINAL { +public: + // Dispatch to most specific 'visit' method on 'vtxp' + void iterate(DfgVertex* vtxp) { vtxp->accept(*this); } + + virtual void visit(DfgVar* vtxp); + virtual void visit(DfgConst* vtxp); +#include "V3Dfg__gen_visitor_decls.h" +}; + +//------------------------------------------------------------------------------ +// Inline method definitions +//------------------------------------------------------------------------------ + +void DfgGraph::addVertex(DfgVertex& vtx) { + ++m_size; + vtx.m_verticesEnt.pushBack(m_vertices, &vtx); +} + +void DfgGraph::removeVertex(DfgVertex& vtx) { + --m_size; + vtx.m_verticesEnt.unlink(m_vertices, &vtx); +} + +void DfgGraph::forEachVertex(std::function f) { + for (DfgVertex *vtxp = m_vertices.begin(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->m_verticesEnt.nextp(); + f(*vtxp); + } +} + +void DfgGraph::forEachVertex(std::function f) const { + for (const DfgVertex* vtxp = m_vertices.begin(); vtxp; vtxp = vtxp->m_verticesEnt.nextp()) { + f(*vtxp); + } +} + +void DfgGraph::forEachVertexInReverse(std::function f) { + for (DfgVertex *vtxp = m_vertices.rbegin(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->m_verticesEnt.prevp(); + f(*vtxp); + } +} + +template +Vertex* DfgGraph::findVertex(std::function p) const { + static_assert(std::is_base_of::value, + "'Vertex' must be subclass of 'DfgVertex'"); + for (DfgVertex* vtxp = m_vertices.begin(); vtxp; vtxp = vtxp->m_verticesEnt.nextp()) { + if (Vertex* const vvtxp = vtxp->cast()) { + if (p(*vvtxp)) return vvtxp; + } + } + return nullptr; +} + +void DfgVertex::forEachSource(std::function f) const { + const auto pair = sourceEdges(); + const DfgEdge* const edgesp = pair.first; + const size_t arity = pair.second; + for (size_t i = 0; i < arity; ++i) { + if (DfgVertex* const sourcep = edgesp[i].m_sourcep) f(*sourcep); + } +} + +void DfgVertex::forEachSink(std::function f) { + for (const DfgEdge* edgep = m_sinksp; edgep; edgep = edgep->m_nextp) f(*edgep->m_sinkp); +} + +void DfgVertex::forEachSink(std::function f) const { + for (const DfgEdge* edgep = m_sinksp; edgep; edgep = edgep->m_nextp) f(*edgep->m_sinkp); +} + +void DfgVertex::forEachSourceEdge(std::function f) { + const auto pair = sourceEdges(); + DfgEdge* const edgesp = pair.first; + const size_t arity = pair.second; + for (size_t i = 0; i < arity; ++i) f(edgesp[i], i); +} + +void DfgVertex::forEachSourceEdge(std::function f) const { + const auto pair = sourceEdges(); + const DfgEdge* const edgesp = pair.first; + const size_t arity = pair.second; + for (size_t i = 0; i < arity; ++i) f(edgesp[i], i); +} + +void DfgVertex::forEachSinkEdge(std::function f) { + for (DfgEdge *edgep = m_sinksp, *nextp; edgep; edgep = nextp) { + nextp = edgep->m_nextp; + f(*edgep); + } +} + +void DfgVertex::forEachSinkEdge(std::function f) const { + for (DfgEdge *edgep = m_sinksp, *nextp; edgep; edgep = nextp) { + nextp = edgep->m_nextp; + f(*edgep); + } +} + +template +Vertex* DfgVertex::findSink(std::function p) const { + static_assert(std::is_base_of::value, + "'Vertex' must be subclass of 'DfgVertex'"); + for (DfgEdge* edgep = m_sinksp; edgep; edgep = edgep->m_nextp) { + if (Vertex* const sinkp = edgep->m_sinkp->cast()) { + if (p(*sinkp)) return sinkp; + } + } + return nullptr; +} + +template +Vertex* DfgVertex::findSink() const { + static_assert(!std::is_same::value, + "'Vertex' must be proper subclass of 'DfgVertex'"); + return findSink([](const Vertex&) { return true; }); +} + +bool DfgVertex::isZero() const { + if (const DfgConst* const constp = cast()) return constp->isZero(); + return false; +} + +bool DfgVertex::isOnes() const { + if (const DfgConst* const constp = cast()) return constp->isOnes(); + return false; +} + +#endif diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp new file mode 100644 index 000000000..bae5e7a33 --- /dev/null +++ b/src/V3DfgAstToDfg.cpp @@ -0,0 +1,267 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Convert AstModule to DfgGraph +// +// 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 +// +//************************************************************************* +// +// Convert and AstModule to a DfgGraph. We proceed by visiting convertable logic blocks (e.g.: +// AstAssignW of appropriate type and with no delays), recursively constructing DfgVertex instances +// for the expressions that compose the subject logic block. If all expressions in the current +// logic block can be converted, then we delete the logic block (now represented in the DfgGraph), +// and connect the corresponding DfgVertex instances appropriately. If some of the expressions were +// not convertible in the current logic block, we revert (delete) the DfgVertex instances created +// for the logic block, and leave the logic block in the AstModule. Any variable reference from +// non-converted logic blocks (or other constructs under the AstModule) are marked as being +// referenced in the AstModule, which is relevant for later optimization. +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3Error.h" +#include "V3Global.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +namespace { + +// Create a DfgVertex out of a AstNodeMath. For most AstNodeMath subtypes, this can be done +// automatically. For the few special cases, we provide specializations below +template +Vertex* makeVertex(const AstForDfg* nodep, DfgGraph& dfg) { + return new Vertex{dfg, nodep->fileline(), DfgVertex::dtypeFor(nodep)}; +} + +//====================================================================== +// Currently unhandled nodes +// LCOV_EXCL_START +// AstCCast changes width, but should not exists where DFG optimization is currently invoked +template <> +DfgCCast* makeVertex(const AstCCast*, DfgGraph&) { + return nullptr; +} +// Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway +template <> +DfgAtoN* makeVertex(const AstAtoN*, DfgGraph&) { + return nullptr; +} +// Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway +template <> +DfgCompareNN* makeVertex(const AstCompareNN*, DfgGraph&) { + return nullptr; +} +// Unhandled in DfgToAst, but also operates on unpacked arrays which we don't optimize anyway +template <> +DfgSliceSel* makeVertex(const AstSliceSel*, DfgGraph&) { + return nullptr; +} +// LCOV_EXCL_STOP + +} // namespace + +class AstToDfgVisitor final : public VNVisitor { + // NODE STATE + + // AstNode::user1p // DfgVertex for this AstNode + const VNUser1InUse m_user1InUse; + + // STATE + + DfgGraph* const m_dfgp; // The graph being built + V3DfgOptimizationContext& m_ctx; // The optimization context for stats + bool m_foundUnhandled = false; // Found node not implemented as DFG or not implemented 'visit' + std::vector m_uncommittedVertices; // Vertices that we might decide to revert + + // METHODS + void markReferenced(AstNode* nodep) { + nodep->foreach([this](const AstVarRef* refp) { + // No need to (and in fact cannot) mark variables with unsupported dtypes + if (!DfgVertex::isSupportedDType(refp->varp()->dtypep())) return; + getNet(refp->varp())->setHasModRefs(); + }); + } + + void commitVertices() { m_uncommittedVertices.clear(); } + + void revertUncommittedVertices() { + for (DfgVertex* const vtxp : m_uncommittedVertices) vtxp->unlinkDelete(*m_dfgp); + m_uncommittedVertices.clear(); + } + + DfgVar* getNet(AstVar* varp) { + if (!varp->user1p()) { + // Note DfgVar vertices are not added to m_uncommittedVertices, because we want to + // hold onto them via AstVar::user1p, and the AstVar which might be referenced via + // multiple AstVarRef instances, so we will never revert a DfgVar once created. This + // means we can end up with DfgVar vertices in the graph which have no connections at + // all (which is fine for later processing). + varp->user1p(new DfgVar{*m_dfgp, varp}); + } + return varp->user1u().to(); + } + + DfgVertex* getVertex(AstNode* nodep) { + DfgVertex* vtxp = nodep->user1u().to(); + UASSERT_OBJ(vtxp, nodep, "Missing Dfg vertex"); + return vtxp; + } + + // Returns true if the expression cannot (or should not) be represented by DFG + bool unhandled(AstNodeMath* nodep) { + // Short-circuiting if something was already unhandled + if (!m_foundUnhandled) { + // Impure nodes cannot be represented + if (!nodep->isPure()) { + m_foundUnhandled = true; + ++m_ctx.m_nonRepImpure; + } + // Check node has supported dtype + if (!DfgVertex::isSupportedDType(nodep->dtypep())) { + m_foundUnhandled = true; + ++m_ctx.m_nonRepDType; + } + } + return m_foundUnhandled; + } + + // VISITORS + void visit(AstNode* nodep) override { + // Conservatively treat this node as unhandled + m_foundUnhandled = true; + ++m_ctx.m_nonRepUnknown; + markReferenced(nodep); + } + void visit(AstCell* nodep) override { markReferenced(nodep); } + void visit(AstNodeProcedure* nodep) override { markReferenced(nodep); } + void visit(AstVar* nodep) override { + // No need to (and in fact cannot) handle variables with unsupported dtypes + if (!DfgVertex::isSupportedDType(nodep->dtypep())) return; + // Mark ports as having external references + if (nodep->isIO()) getNet(nodep)->setHasExtRefs(); + // Mark variables that are the target of a hierarchical reference + // (these flags were set up in DataflowPrepVisitor) + if (nodep->user2()) getNet(nodep)->setHasExtRefs(); + } + + void visit(AstAssignW* nodep) override { + // Cannot handle assignment with timing control yet + if (nodep->timingControlp()) { + markReferenced(nodep); + ++m_ctx.m_nonRepTiming; + return; + } + + // Cannot handle mismatched widths. Mismatched assignments should have been fixed up in + // earlier passes anyway, so this should never be hit, but being paranoid just in case. + if (nodep->lhsp()->width() != nodep->rhsp()->width()) { // LCOV_EXCL_START + markReferenced(nodep); + ++m_ctx.m_nonRepWidth; + return; + } // LCOV_EXCL_START + + // Simple assignment with whole variable on left-hand side + if (AstVarRef* const vrefp = VN_CAST(nodep->lhsp(), VarRef)) { + UASSERT_OBJ(m_uncommittedVertices.empty(), nodep, "Should not nest"); + + // Build DFG vertices representing the two sides + { + m_foundUnhandled = false; + iterate(vrefp); + iterate(nodep->rhsp()); + // If this assignment contains an AstNode not representable by a DfgVertex, + // then revert the graph. + if (m_foundUnhandled) { + revertUncommittedVertices(); + markReferenced(nodep); + return; + } + } + + // Connect the vertices representing the 2 sides + DfgVar* const lVtxp = getVertex(vrefp)->as(); + DfgVertex* const rVtxp = getVertex(nodep->rhsp()); + lVtxp->driverp(rVtxp); + lVtxp->assignmentFileline(nodep->fileline()); + commitVertices(); + + // Remove assignment from Ast. Now represented by the Dfg. + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + + // + ++m_ctx.m_representable; + return; + } + + // TODO: handle complex left-hand sides + markReferenced(nodep); + ++m_ctx.m_nonRepLhs; + } + + void visit(AstVarRef* nodep) override { + UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex"); + if (unhandled(nodep)) return; + + if (nodep->access().isRW() // Cannot represent read-write references + || nodep->varp()->isIfaceRef() // Cannot handle interface references + || nodep->varp()->delayp() // Cannot handle delayed variables + || nodep->classOrPackagep() // Cannot represent cross module references + ) { + markReferenced(nodep); + m_foundUnhandled = true; + ++m_ctx.m_nonRepVarRef; + return; + } + + // Sadly sometimes AstVarRef does not have the same dtype as the referenced variable + if (!DfgVertex::isSupportedDType(nodep->varp()->dtypep())) { + m_foundUnhandled = true; + ++m_ctx.m_nonRepVarRef; + return; + } + + nodep->user1p(getNet(nodep->varp())); + } + + void visit(AstConst* nodep) override { + UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex"); + if (unhandled(nodep)) return; + DfgVertex* const vtxp = new DfgConst{*m_dfgp, nodep->cloneTree(false)}; + m_uncommittedVertices.push_back(vtxp); + nodep->user1p(vtxp); + } + + // The rest of the 'visit' methods are generated by 'astgen' +#include "V3Dfg__gen_ast_to_dfg.h" + + // CONSTRUCTOR + explicit AstToDfgVisitor(AstModule& module, V3DfgOptimizationContext& ctx) + : m_dfgp{new DfgGraph{module, module.name()}} + , m_ctx{ctx} { + // Build the DFG + iterateChildren(&module); + UASSERT_OBJ(m_uncommittedVertices.empty(), &module, "Uncommitted vertices remain"); + } + +public: + static DfgGraph* apply(AstModule& module, V3DfgOptimizationContext& ctx) { + return AstToDfgVisitor{module, ctx}.m_dfgp; + } +}; + +DfgGraph* V3DfgPasses::astToDfg(AstModule& module, V3DfgOptimizationContext& ctx) { + return AstToDfgVisitor::apply(module, ctx); +} diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp new file mode 100644 index 000000000..6084ba5be --- /dev/null +++ b/src/V3DfgDfgToAst.cpp @@ -0,0 +1,314 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Convert DfgGraph to AstModule +// +// 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 +// +//************************************************************************* +// +// Convert DfgGraph back to AstModule. We recursively construct AstNodeMath expressions for each +// DfgVertex which represents a storage location (e.g.: DfgVar), or has multiple sinks without +// driving a storage location (and hence needs a temporary variable to duplication). The recursion +// stops when we reach a DfgVertex representing a storage location (e.g.: DfgVar), or a vertex that +// that has multiple sinks (as these nodes will have a [potentially new temporary] corresponding +// storage location). Redundant variables (those whose source vertex drives multiple variables) are +// eliminated when possible. Vertices driving multiple variables are rendered once, driving an +// arbitrarily (but deterministically) chosen canonical variable, and the corresponding redundant +// variables are assigned from the canonical variable. +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3UniqueNames.h" + +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +namespace { + +// Create an AstNodeMath out of a DfgVertex. For most AstNodeMath subtypes, this can be done +// automatically. For the few special cases, we provide specializations below +template +Node* makeNode(const DfgForAst* vtxp, Ops... ops) { + Node* const nodep = new Node{vtxp->fileline(), ops...}; + UASSERT_OBJ(nodep->width() == static_cast(vtxp->width()), vtxp, + "Incorrect width in AstNode created from DfgVertex " + << vtxp->typeName() << ": " << nodep->width() << " vs " << vtxp->width()); + return nodep; +} + +//====================================================================== +// Vertices needing special conversion + +template <> +AstExtend* makeNode( // + const DfgExtend* vtxp, AstNodeMath* op1) { + return new AstExtend{vtxp->fileline(), op1, static_cast(vtxp->width())}; +} + +template <> +AstExtendS* makeNode( // + const DfgExtendS* vtxp, AstNodeMath* op1) { + return new AstExtendS{vtxp->fileline(), op1, static_cast(vtxp->width())}; +} + +template <> +AstShiftL* makeNode( // + const DfgShiftL* vtxp, AstNodeMath* op1, AstNodeMath* op2) { + return new AstShiftL{vtxp->fileline(), op1, op2, static_cast(vtxp->width())}; +} + +template <> +AstShiftR* makeNode( // + const DfgShiftR* vtxp, AstNodeMath* op1, AstNodeMath* op2) { + return new AstShiftR{vtxp->fileline(), op1, op2, static_cast(vtxp->width())}; +} + +template <> +AstShiftRS* makeNode( // + const DfgShiftRS* vtxp, AstNodeMath* op1, AstNodeMath* op2) { + return new AstShiftRS{vtxp->fileline(), op1, op2, static_cast(vtxp->width())}; +} + +//====================================================================== +// Currently unhandled nodes - see corresponding AstToDfg functions +// LCOV_EXCL_START +template <> +AstCCast* makeNode(const DfgCCast* vtxp, AstNodeMath*) { + vtxp->v3fatal("not implemented"); +} +template <> +AstAtoN* makeNode(const DfgAtoN* vtxp, AstNodeMath*) { + vtxp->v3fatal("not implemented"); +} +template <> +AstCompareNN* makeNode(const DfgCompareNN* vtxp, + AstNodeMath*, AstNodeMath*) { + vtxp->v3fatal("not implemented"); +} +template <> +AstSliceSel* makeNode( + const DfgSliceSel* vtxp, AstNodeMath*, AstNodeMath*, AstNodeMath*) { + vtxp->v3fatal("not implemented"); +} +// LCOV_EXCL_STOP + +} // namespace + +class DfgToAstVisitor final : DfgVisitor { + // STATE + + AstModule* const m_modp; // The parent/result module + V3DfgOptimizationContext& m_ctx; // The optimization context for stats + AstNodeMath* m_resultp = nullptr; // The result node of the current traversal + // Map from DfgVertex to the AstVar holding the value of that DfgVertex after conversion + std::unordered_map m_resultVars; + // Map from an AstVar, to the canonical AstVar that can be substituted for that AstVar + std::unordered_map m_canonVars; + V3UniqueNames m_tmpNames{"_VdfgTmp"}; // For generating temporary names + DfgVertex::HashCache m_hashCache; // For caching hashes + + // METHODS + + // Given a DfgVar, return the canonical AstVar that can be used for this DfgVar. + // Also builds the m_canonVars map as a side effect. + AstVar* getCanonicalVar(const DfgVar* vtxp) { + // Variable only read by DFG + if (!vtxp->driverp()) return vtxp->varp(); + + // Look up map + const auto it = m_canonVars.find(vtxp->varp()); + if (it != m_canonVars.end()) return it->second; + + // Not known yet, compute it (for all vars from the same driver) + std::vector varps; + vtxp->driverp()->forEachSink([&](const DfgVertex& vtx) { + if (const DfgVar* const varVtxp = vtx.cast()) varps.push_back(varVtxp); + }); + UASSERT_OBJ(!varps.empty(), vtxp, "The input vtxp->varp() is always available"); + std::stable_sort(varps.begin(), varps.end(), [](const DfgVar* ap, const DfgVar* bp) { + if (ap->hasExtRefs() != bp->hasExtRefs()) return ap->hasExtRefs(); + const FileLine& aFl = *(ap->fileline()); + const FileLine& bFl = *(bp->fileline()); + if (const int cmp = aFl.operatorCompare(bFl)) return cmp < 0; + return ap->varp()->name() < bp->varp()->name(); + }); + AstVar* const canonVarp = varps.front()->varp(); + + // Add results to map + for (const DfgVar* const varp : varps) m_canonVars.emplace(varp->varp(), canonVarp); + + // Return it + return canonVarp; + } + + // Given a DfgVertex, return an AstVar that will hold the value of the given DfgVertex once we + // are done with converting this Dfg into Ast form. + AstVar* getResultVar(const DfgVertex* vtxp) { + const auto pair = m_resultVars.emplace(vtxp, nullptr); + AstVar*& varp = pair.first->second; + if (pair.second) { + // If this vertex is a DfgVar, then we know the variable. If this node is not a DfgVar, + // then first we try to find a DfgVar driven by this node, and use that, otherwise we + // create a temporary + if (const DfgVar* const thisDfgVarp = vtxp->cast()) { + // This is a DfgVar + varp = getCanonicalVar(thisDfgVarp); + } else if (const DfgVar* const sinkDfgVarp = vtxp->findSink()) { + // We found a DfgVar driven by this node + varp = getCanonicalVar(sinkDfgVarp); + } else { + // No DfgVar driven by this node. Create a temporary. + const string name = m_tmpNames.get(vtxp->hash(m_hashCache).toString()); + // Note: It is ok for these temporary variables to be always unsigned. They are + // read only by other expressions within the graph and all expressions interpret + // their operands based on the expression type, not the operand type. + AstNodeDType* const dtypep = v3Global.rootp()->findBitDType( + vtxp->width(), vtxp->width(), VSigning::UNSIGNED); + varp = new AstVar{vtxp->fileline(), VVarType::MODULETEMP, name, dtypep}; + // Add temporary AstVar to containing module + m_modp->addStmtsp(varp); + } + // Add to map + } + return varp; + } + + AstNodeMath* convertDfgVertexToAstNodeMath(DfgVertex* vtxp) { + UASSERT_OBJ(!m_resultp, vtxp, "Result already computed"); + iterate(vtxp); + UASSERT_OBJ(m_resultp, vtxp, "Missing result"); + AstNodeMath* const resultp = m_resultp; + m_resultp = nullptr; + return resultp; + } + + AstNodeMath* convertSource(DfgVertex* vtxp) { + if (vtxp->hasMultipleSinks()) { + // Vertices with multiple sinks need a temporary variable, just return a reference + return new AstVarRef{vtxp->fileline(), getResultVar(vtxp), VAccess::READ}; + } else { + // Vertex with single sink is simply recursively converted + UASSERT_OBJ(vtxp->hasSinks(), vtxp, "Must have one sink: " << vtxp->typeName()); + return convertDfgVertexToAstNodeMath(vtxp); + } + } + + // VISITORS + void visit(DfgVertex* vtxp) override { // LCOV_EXCL_START + vtxp->v3fatal("Unhandled DfgVertex: " << vtxp->typeName()); + } // LCOV_EXCL_STOP + + void visit(DfgVar* vtxp) override { + m_resultp = new AstVarRef{vtxp->fileline(), getCanonicalVar(vtxp), VAccess::READ}; + } + + void visit(DfgConst* vtxp) override { // + m_resultp = vtxp->constp()->cloneTree(false); + } + + // The rest of the 'visit' methods are generated by 'astgen' +#include "V3Dfg__gen_dfg_to_ast.h" + + // Constructor + explicit DfgToAstVisitor(DfgGraph& dfg, V3DfgOptimizationContext& ctx) + : m_modp{dfg.modulep()} + , m_ctx{ctx} { + // We can eliminate some variables completely + std::vector redundantVarps; + + // Render the logic + dfg.forEachVertex([&](DfgVertex& vtx) { + // Compute the AstNodeMath expression representing this DfgVertex + AstNodeMath* rhsp = nullptr; + AstNodeMath* lhsp = nullptr; + FileLine* assignmentFlp = nullptr; + if (const DfgVar* const dfgVarp = vtx.cast()) { + // DfgVar instances (these might be driving the given AstVar variable) + // If there is no driver (i.e.: this DfgVar is an input to the Dfg), then nothing + // to do + if (!dfgVarp->driverp()) return; + // The driver of this DfgVar might drive multiple variables. Only emit one + // assignment from the driver to an arbitrarily chosen canonical variable, and + // assign the other variables from that canonical variable + AstVar* const canonVarp = getCanonicalVar(dfgVarp); + if (canonVarp == dfgVarp->varp()) { + // This is the canonical variable, so render the driver + rhsp = convertDfgVertexToAstNodeMath(dfgVarp->driverp()); + } else if (dfgVarp->keep()) { + // Not the canonical variable but it must be kept, just assign from the + // canonical variable. + rhsp = new AstVarRef{canonVarp->fileline(), canonVarp, VAccess::READ}; + } else { + // Not a canonical var, and it can be removed. We will replace all references + // to it with the canonical variable, and hence this can be removed. + redundantVarps.push_back(dfgVarp->varp()); + ++m_ctx.m_replacedVars; + return; + } + // The Lhs is the variable driven by this DfgVar + lhsp = new AstVarRef{vtx.fileline(), dfgVarp->varp(), VAccess::WRITE}; + // Set location to the location of the original assignment to this variable + assignmentFlp = dfgVarp->assignmentFileline(); + } else if (vtx.hasMultipleSinks() && !vtx.findSink()) { + // DfgVertex that has multiple sinks, but does not drive a DfgVar (needs temporary) + // Just render the logic + rhsp = convertDfgVertexToAstNodeMath(&vtx); + // The lhs is a temporary + lhsp = new AstVarRef{vtx.fileline(), getResultVar(&vtx), VAccess::WRITE}; + // Render vertex + assignmentFlp = vtx.fileline(); + // Stats + ++m_ctx.m_intermediateVars; + } else { + // Every other DfgVertex will be inlined by 'convertDfgVertexToAstNodeMath' as an + // AstNodeMath at use, and hence need not be converted. + return; + } + // Add assignment of the value to the variable + m_modp->addStmtsp(new AstAssignW{assignmentFlp, lhsp, rhsp}); + ++m_ctx.m_resultEquations; + }); + + // Remap all references to point to the canonical variables, if one exists + VNDeleter deleter; + m_modp->foreach([&](AstVarRef* refp) { + // Any variable that is written outside the DFG will have itself as the canonical + // var, so need not be replaced, furthermore, if a variable is traced, we don't + // want to update the write ref we just created above, so we only replace read only + // references. + if (!refp->access().isReadOnly()) return; + const auto it = m_canonVars.find(refp->varp()); + if (it == m_canonVars.end()) return; + if (it->second == refp->varp()) return; + refp->replaceWith(new AstVarRef{refp->fileline(), it->second, refp->access()}); + deleter.pushDeletep(refp); + }); + + // Remove redundant variables + for (AstVar* const varp : redundantVarps) varp->unlinkFrBack()->deleteTree(); + } + +public: + static AstModule* apply(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { + return DfgToAstVisitor{dfg, ctx}.m_modp; + } +}; + +AstModule* V3DfgPasses::dfgToAst(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { + return DfgToAstVisitor::apply(dfg, ctx); +} diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp new file mode 100644 index 000000000..d4491b5e0 --- /dev/null +++ b/src/V3DfgOptimizer.cpp @@ -0,0 +1,292 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Dataflow based optimization of 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 +// +//************************************************************************* +// +// High level entry points from Ast world to the DFG optimizer. +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3DfgOptimizer.h" + +#include "V3Ast.h" +#include "V3AstUserAllocator.h" +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3Error.h" +#include "V3Global.h" +#include "V3Graph.h" +#include "V3UniqueNames.h" + +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +// Extract more combinational logic equations from procedures for better optimization opportunities +class DataflowExtractVisitor final : public VNVisitor { + // NODE STATE + // AstVar::user3 -> bool: Flag indicating variable is subject of force or release + // statement AstVar::user4 -> bool: Flag indicating variable is combinationally driven + // AstNodeModule::user4 -> Extraction candidates (via m_extractionCandidates) + const VNUser3InUse m_user3InUse; + const VNUser4InUse m_user4InUse; + + // Expressions considered for extraction as separate assignment to gain more opportunities for + // optimization, together with the list of variables they read. + using Candidates = std::vector>>; + + // Expressions considered for extraction. All the candidates are pure expressions. + AstUser4Allocator m_extractionCandidates; + + // STATE + AstNodeModule* m_modp = nullptr; // The module being visited + Candidates* m_candidatesp = nullptr; + bool m_impure = false; // True if the visited tree has a side effect + bool m_inForceReleaseLhs = false; // Iterating LHS of force/release + // List of AstVar nodes read by the visited tree. 'vector' rather than 'set' as duplicates are + // somewhat unlikely and we can handle them later. + std::vector m_readVars; + + // METHODS + + // Node considered for extraction as a combinational equation. Trace variable usage/purity. + void iterateExtractionCandidate(AstNode* nodep) { + UASSERT_OBJ(!VN_IS(nodep->backp(), NodeMath), nodep, + "Should not try to extract nested expressions (only root expressions)"); + + // Simple VarRefs should not be extracted, as they only yield trivial assignments. + // Similarly, don't extract anything if no candidate map is set up (for non-modules). + // We still need to visit them though, to mark hierarchical references. + if (VN_IS(nodep, NodeVarRef) || !m_candidatesp) { + iterate(nodep); + return; + } + + // Don't extract plain constants + if (VN_IS(nodep, Const)) return; + + // Candidates can't nest, so no need for VL_RESTORER, just initialize iteration state + m_impure = false; + m_readVars.clear(); + + // Trace variable usage + iterate(nodep); + + // We only extract pure expressions + if (m_impure) return; + + // Do not extract expressions without any variable references + if (m_readVars.empty()) return; + + // Add to candidate list + m_candidatesp->emplace_back(VN_AS(nodep, NodeMath), std::move(m_readVars)); + } + + // VISIT methods + + void visit(AstNetlist* nodep) override { + // Analyse the whole design + iterateChildrenConst(nodep); + + // Replace candidate expressions only reading combinationally driven signals with variables + V3UniqueNames names{"_VdfgExtracted__"}; + for (AstNodeModule* modp = nodep->modulesp(); modp; + modp = VN_AS(modp->nextp(), NodeModule)) { + // Only extract from proper modules + if (!VN_IS(modp, Module)) continue; + + for (const auto& pair : m_extractionCandidates(modp)) { + AstNodeMath* const nodep = pair.first; + + // Do not extract expressions without any variable references + if (pair.second.empty()) continue; + + // Check if all variables read by this expression are driven combinationally, + // and move on if not. Also don't extract it if one of the variables is subject + // to a force/release, as releasing nets must have immediate effect, but adding + // extra combinational logic can change semantics (see t_force_release_net*). + { + bool hasBadVar = false; + for (const AstVar* const readVarp : pair.second) { + // variable is target of force/release or not combinationally driven + if (readVarp->user3() || !readVarp->user4()) { + hasBadVar = true; + break; + } + } + if (hasBadVar) continue; + } + + // Create temporary variable + FileLine* const flp = nodep->fileline(); + const string name = names.get(nodep); + AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, nodep->dtypep()}; + varp->trace(false); + modp->addStmtsp(varp); + + // Replace expression with temporary variable + nodep->replaceWith(new AstVarRef{flp, varp, VAccess::READ}); + + // Add assignment driving temporary variable + modp->addStmtsp( + new AstAssignW{flp, new AstVarRef{flp, varp, VAccess::WRITE}, nodep}); + } + } + } + + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_modp); + m_modp = nodep; + iterateChildrenConst(nodep); + } + + void visit(AstAlways* nodep) override { + VL_RESTORER(m_candidatesp); + // Only extract from combinational logic under proper modules + const bool isComb = !nodep->sensesp() + && (nodep->keyword() == VAlwaysKwd::ALWAYS + || nodep->keyword() == VAlwaysKwd::ALWAYS_COMB + || nodep->keyword() == VAlwaysKwd::ALWAYS_LATCH); + m_candidatesp + = isComb && VN_IS(m_modp, Module) ? &m_extractionCandidates(m_modp) : nullptr; + iterateChildrenConst(nodep); + } + + void visit(AstAssignW* nodep) override { + // Mark LHS variable as combinationally driven + if (AstVarRef* const vrefp = VN_CAST(nodep->lhsp(), VarRef)) vrefp->varp()->user4(true); + // + iterateChildrenConst(nodep); + } + + void visit(AstAssign* nodep) override { + iterateExtractionCandidate(nodep->rhsp()); + iterate(nodep->lhsp()); + } + + void visit(AstAssignDly* nodep) override { + iterateExtractionCandidate(nodep->rhsp()); + iterate(nodep->lhsp()); + } + + void visit(AstIf* nodep) override { + iterateExtractionCandidate(nodep->condp()); + iterateAndNextConstNull(nodep->thensp()); + iterateAndNextConstNull(nodep->elsesp()); + } + + void visit(AstAssignForce* nodep) override { + iterate(nodep->rhsp()); + UASSERT_OBJ(!m_inForceReleaseLhs, nodep, "Should not nest"); + m_inForceReleaseLhs = true; + iterate(nodep->lhsp()); + m_inForceReleaseLhs = false; + } + + void visit(AstRelease* nodep) override { + UASSERT_OBJ(!m_inForceReleaseLhs, nodep, "Should not nest"); + m_inForceReleaseLhs = true; + iterate(nodep->lhsp()); + m_inForceReleaseLhs = false; + } + + void visit(AstNodeMath* nodep) override { iterateChildrenConst(nodep); } + + void visit(AstNodeVarRef* nodep) override { + if (nodep->access().isWriteOrRW()) { + // If it writes a variable, mark as impure + m_impure = true; + // Mark target of force/release + if (m_inForceReleaseLhs) nodep->varp()->user3(true); + } else { + // Otherwise, add read reference + m_readVars.push_back(nodep->varp()); + } + } + + void visit(AstNode* nodep) override { + // Conservatively assume unhandled nodes are impure. This covers all AstNodeFTaskRef + // as AstNodeFTaskRef are sadly not AstNodeMath. + m_impure = true; + // Still need to gather all references/force/release, etc. + iterateChildrenConst(nodep); + } + + // CONSTRUCTOR + explicit DataflowExtractVisitor(AstNetlist* netlistp) { iterate(netlistp); } + +public: + static void apply(AstNetlist* netlistp) { DataflowExtractVisitor{netlistp}; } +}; + +void V3DfgOptimizer::extract(AstNetlist* netlistp) { + UINFO(2, __FUNCTION__ << ": " << endl); + // Extract more optimization candidates + DataflowExtractVisitor::apply(netlistp); + V3Global::dumpCheckGlobalTree("dfg-extract", 0, dumpTree() >= 3); +} + +void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) { + UINFO(2, __FUNCTION__ << ": " << endl); + + // NODE STATE + // AstVar::user1 -> Used by V3DfgPasses::astToDfg + // AstVar::user2 -> bool: Flag indicating referenced by AstVarXRef + const VNUser2InUse user2InUse; + + // Mark cross-referenced variables + netlistp->foreach([](const AstVarXRef* xrefp) { xrefp->varp()->user2(true); }); + + V3DfgOptimizationContext ctx{label}; + + // Run the optimization phase + for (AstNode* nodep = netlistp->modulesp(); nodep; nodep = nodep->nextp()) { + // Only optimize proper modules + AstModule* const modp = VN_CAST(nodep, Module); + if (!modp) continue; + + UINFO(3, "Applying DFG optimization to module'" << modp->name() << "'" << endl); + ++ctx.m_modules; + + // Build the DFG of this module + const std::unique_ptr dfg{V3DfgPasses::astToDfg(*modp, ctx)}; + if (dumpDfg() >= 9) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-input"); + + // Split the DFG into independent components + const std::vector>& components = dfg->splitIntoComponents(); + + // For each component + for (auto& component : components) { + // Reverse topologically sort the component + const bool acyclic = component->sortTopologically(/* reverse: */ true); + // Optimize the component (iff it is not cyclic) + if (VL_LIKELY(acyclic)) { + V3DfgPasses::optimize(*component, ctx); + } else if (dumpDfg() >= 7) { + component->dumpDotFilePrefixed(ctx.prefix() + "cyclic"); + } + // Add back under the main DFG (we will convert back in one go) + dfg->addGraph(*component); + } + + // Convert back to Ast + if (dumpDfg() >= 9) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-optimized"); + AstModule* const resultModp = V3DfgPasses::dfgToAst(*dfg, ctx); + UASSERT_OBJ(resultModp == modp, modp, "Should be the same module"); + } + V3Global::dumpCheckGlobalTree("dfg-optimize", 0, dumpTree() >= 3); +} diff --git a/src/V3DfgOptimizer.h b/src/V3DfgOptimizer.h new file mode 100644 index 000000000..5377dcd23 --- /dev/null +++ b/src/V3DfgOptimizer.h @@ -0,0 +1,35 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Dataflow based optimization of 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 +// +//************************************************************************* + +#ifndef VERILATOR_V3DFGOPTIMIZER_H_ +#define VERILATOR_V3DFGOPTIMIZER_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Ast.h" + +//============================================================================ + +namespace V3DfgOptimizer { +// Extract further logic blocks from the design for additional optimization opportunities +void extract(AstNetlist*); + +// Optimize the design +void optimize(AstNetlist*, const string& label); +} // namespace V3DfgOptimizer + +#endif // Guard diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp new file mode 100644 index 000000000..a2c998025 --- /dev/null +++ b/src/V3DfgPasses.cpp @@ -0,0 +1,213 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Implementations of simple passes over DfgGraph +// +// 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 +// +//************************************************************************* + +#include "config_build.h" + +#include "V3DfgPasses.h" + +#include "V3Dfg.h" +#include "V3Global.h" +#include "V3String.h" + +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +V3DfgCseContext::~V3DfgCseContext() { + V3Stats::addStat("Optimizations, DFG " + m_label + " CSE, expressions eliminated", + m_eliminated); +} + +DfgRemoveVarsContext::~DfgRemoveVarsContext() { + V3Stats::addStat("Optimizations, DFG " + m_label + " Remove vars, variables removed", + m_removed); +} + +static std::string getPrefix(const std::string& label) { + if (label.empty()) return ""; + std::string str = VString::removeWhitespace(label); + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { // + return c == ' ' ? '-' : std::tolower(c); + }); + str += "-"; + return str; +} + +V3DfgOptimizationContext::V3DfgOptimizationContext(const std::string& label) + : m_label{label} + , m_prefix{getPrefix(label)} {} + +V3DfgOptimizationContext::~V3DfgOptimizationContext() { + const string prefix = "Optimizations, DFG " + m_label + " "; + V3Stats::addStat(prefix + "General, modules", m_modules); + V3Stats::addStat(prefix + "Ast2Dfg, representable", m_representable); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (dtype)", m_nonRepDType); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (impure)", m_nonRepImpure); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (timing)", m_nonRepTiming); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (lhs)", m_nonRepLhs); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (node)", m_nonRepNode); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (unknown)", m_nonRepUnknown); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (var ref)", m_nonRepVarRef); + V3Stats::addStat(prefix + "Ast2Dfg, non-representable (width)", m_nonRepWidth); + V3Stats::addStat(prefix + "Dfg2Ast, intermediate variables", m_intermediateVars); + V3Stats::addStat(prefix + "Dfg2Ast, replaced variables", m_replacedVars); + V3Stats::addStat(prefix + "Dfg2Ast, result equations", m_resultEquations); +} + +// 'Inline' DfgVar nodes with known drivers +void V3DfgPasses::inlineVars(DfgGraph& dfg) { + dfg.forEachVertex([](DfgVertex& vtx) { + // For each DfgVar that has a known driver + if (DfgVar* const varVtxp = vtx.cast()) { + if (DfgVertex* const driverp = varVtxp->driverp()) { + // Make consumers of the DfgVar consume the driver directly + varVtxp->forEachSinkEdge([=](DfgEdge& edge) { edge.relinkSource(driverp); }); + } + } + }); +} + +// Common subexpression elimination +void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) { + DfgVertex::HashCache hashCache; + DfgVertex::EqualsCache equalsCache; + std::unordered_multimap verticesWithEqualHashes; + + // In reverse, as the graph is sometimes in reverse topological order already + dfg.forEachVertexInReverse([&](DfgVertex& vtx) { + // Don't merge constants + if (vtx.is()) return; + // For everything else... + const V3Hash hash = vtx.hash(hashCache); + auto pair = verticesWithEqualHashes.equal_range(hash); + for (auto it = pair.first, end = pair.second; it != end; ++it) { + DfgVertex* const candidatep = it->second; + if (candidatep->equals(vtx, equalsCache)) { + ++ctx.m_eliminated; + vtx.replaceWith(candidatep); + vtx.unlinkDelete(dfg); + return; + } + } + verticesWithEqualHashes.emplace(hash, &vtx); + }); +} + +void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { + dfg.forEachVertex([&](DfgVertex& vtx) { + // We can eliminate certain redundant DfgVar vertices + DfgVar* const varp = vtx.cast(); + if (!varp) return; + + // Can't remove if it has consumers + if (varp->hasSinks()) return; + + // Can't remove if read in the module and driven here (i.e.: it's an output of the DFG) + if (varp->hasModRefs() && varp->driverp()) return; + + // Can't remove if referenced externally, or other special reasons + if (varp->keep()) return; + + // If the driver of this variable has multiple non-variable sinks, then we would need + // a temporary when rendering the graph. Instead of introducing a temporary, keep the + // first variable that is driven by that driver + if (DfgVertex* const driverp = varp->driverp()) { + unsigned nonVarSinks = 0; + const DfgVar* firstSinkVarp = nullptr; + const bool keepFirst = driverp->findSink([&](const DfgVertex& sink) { + if (const DfgVar* const sinkVarp = sink.cast()) { + if (!firstSinkVarp) firstSinkVarp = sinkVarp; + } else { + ++nonVarSinks; + } + // We can stop as soon as we found the first var, and 2 non-var sinks + return firstSinkVarp && nonVarSinks >= 2; + }); + // Keep this DfgVar if needed + if (keepFirst && firstSinkVarp == varp) return; + } + + // OK, we can delete this DfgVar! + ++ctx.m_removed; + + // If not referenced outside the DFG, then also delete the referenced AstVar, + // as it is now unused. + if (!varp->hasRefs()) varp->varp()->unlinkFrBack()->deleteTree(); + + // Unlink and delete vertex + vtx.unlinkDelete(dfg); + }); +} + +void V3DfgPasses::removeUnused(DfgGraph& dfg) { + const auto processVertex = [&](DfgVertex& vtx) { + // Keep variables + if (vtx.is()) return false; + // Keep if it has sinks + if (vtx.hasSinks()) return false; + // Unlink and delete vertex + vtx.unlinkDelete(dfg); + return true; + }; + + dfg.runToFixedPoint(processVertex); +} + +void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { + // There is absolutely nothing useful we can do with a graph of size 2 or less + if (dfg.size() <= 2) return; + + // We consider a DFG trivial if it contains no more than 1 non-variable, non-constant vertex + unsigned excitingVertices = 0; + const bool isTrivial = !dfg.findVertex([&](const DfgVertex& vtx) { // + if (vtx.is()) return false; + if (vtx.is()) return false; + return ++excitingVertices >= 2; + }); + + int passNumber = 0; + + const auto apply = [&](int dumpLevel, const string name, std::function pass) { + pass(); + if (dumpDfg() >= dumpLevel) { + const string strippedName = VString::removeWhitespace(name); + const string label + = ctx.prefix() + "pass-" + cvtToStr(passNumber) + "-" + strippedName; + dfg.dumpDotFilePrefixed(label); + } + ++passNumber; + }; + + if (!isTrivial) { + // Optimize non-trivial graph + if (dumpDfg() >= 8) { dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "input"); } + apply(3, "input ", [&]() {}); + apply(4, "inlineVars ", [&]() { inlineVars(dfg); }); + apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext0); }); + if (v3Global.opt.fDfgPeephole()) { + apply(4, "peephole ", [&]() { peephole(dfg, ctx.m_peepholeContext); }); + } + apply(4, "removeVars ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); }); + apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext1); }); + apply(3, "optimized ", [&]() { removeUnused(dfg); }); + if (dumpDfg() >= 8) { dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "optimized"); } + } else { + // We can still eliminate redundancies from trivial graphs + apply(5, "trivial-input ", [&]() {}); + apply(6, "trivial-inlineVars ", [&]() { inlineVars(dfg); }); + apply(5, "trivial-optimized ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); }); + } +} diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h new file mode 100644 index 000000000..20adfd201 --- /dev/null +++ b/src/V3DfgPasses.h @@ -0,0 +1,113 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Passes over DfgGraph +// +// 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_V3DFGPASSES_H_ +#define VERILATOR_V3DFGPASSES_H_ + +#include "config_build.h" + +#include "V3DfgPeephole.h" + +class AstModule; +class DfgGraph; + +//=========================================================================== +// Various context objects hold data that need to persist across invocations +// of a DFG pass. + +class V3DfgCseContext final { + const std::string m_label; // Label to apply to stats + +public: + VDouble0 m_eliminated; // Number of common sub-expressions eliminated + V3DfgCseContext(const std::string& label) + : m_label{label} {} + ~V3DfgCseContext(); +}; + +class DfgRemoveVarsContext final { + const std::string m_label; // Label to apply to stats + +public: + VDouble0 m_removed; // Number of redundant variables removed + DfgRemoveVarsContext(const std::string& label) + : m_label{label} {} + ~DfgRemoveVarsContext(); +}; + +class V3DfgOptimizationContext final { + const std::string m_label; // Label to add to stats, etc. + const std::string m_prefix; // Prefix to add to file dumps (derived from label) + +public: + VDouble0 m_modules; // Number of modules optimized + VDouble0 m_representable; // Number of combinational equations representable + VDouble0 m_nonRepDType; // Equations non-representable due to data type + VDouble0 m_nonRepImpure; // Equations non-representable due to impure node + VDouble0 m_nonRepTiming; // Equations non-representable due to timing control + VDouble0 m_nonRepLhs; // Equations non-representable due to lhs + VDouble0 m_nonRepNode; // Equations non-representable due to node type + VDouble0 m_nonRepUnknown; // Equations non-representable due to unknown node + VDouble0 m_nonRepVarRef; // Equations non-representable due to variable reference + VDouble0 m_nonRepWidth; // Equations non-representable due to width mismatch + VDouble0 m_intermediateVars; // Number of intermediate variables introduced + VDouble0 m_replacedVars; // Number of variables replaced + VDouble0 m_resultEquations; // Number of result combinational equations + + V3DfgCseContext m_cseContext0{m_label + " 1st"}; + V3DfgCseContext m_cseContext1{m_label + " 2nd"}; + V3DfgPeepholeContext m_peepholeContext{m_label}; + DfgRemoveVarsContext m_removeVarsContext{m_label}; + V3DfgOptimizationContext(const std::string& label); + ~V3DfgOptimizationContext(); + + const std::string& prefix() const { return m_prefix; } +}; + +namespace V3DfgPasses { +//=========================================================================== +// Top level entry points +//=========================================================================== + +// Construct a DfGGraph representing the combinational logic in the given AstModule. The logic +// that is represented by the graph is removed from the given AstModule. Returns the +// constructed DfgGraph. +DfgGraph* astToDfg(AstModule&, V3DfgOptimizationContext&); + +// Optimize the given DfgGraph +void optimize(DfgGraph&, V3DfgOptimizationContext&); + +// Convert DfgGraph back into Ast, and insert converted graph back into its parent module. +// Returns the parent module. +AstModule* dfgToAst(DfgGraph&, V3DfgOptimizationContext&); + +//=========================================================================== +// Intermediate/internal operations +//=========================================================================== + +// Inline variables +void inlineVars(DfgGraph&); +// Common subexpression elimination +void cse(DfgGraph&, V3DfgCseContext&); +// Peephole optimizations +void peephole(DfgGraph&, V3DfgPeepholeContext&); +// Remove redundant variables +void removeVars(DfgGraph&, DfgRemoveVarsContext&); +// Remove unused nodes +void removeUnused(DfgGraph&); +} // namespace V3DfgPasses + +#endif diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp new file mode 100644 index 000000000..c27607e21 --- /dev/null +++ b/src/V3DfgPeephole.cpp @@ -0,0 +1,1144 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Peephole optimizations over DfgGraph +// +// 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 +// +//************************************************************************* +// +// A pattern-matching based optimizer for DfgGraph. This is in some aspects similar to V3Const, but +// more powerful in that it does not care about ordering combinational statement. This is also less +// broadly applicable than V3Const, as it does not apply to procedural statements with sequential +// execution semantics. +// +//************************************************************************* + +#include "config_build.h" + +#include "V3DfgPeephole.h" + +#include "V3Ast.h" +#include "V3Dfg.h" +#include "V3DfgPasses.h" +#include "V3Stats.h" + +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +V3DfgPeepholeContext::V3DfgPeepholeContext(const std::string& label) + : m_label{label} { + const auto checkEnabled = [this](VDfgPeepholePattern id) { + string str{id.ascii()}; + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { // + return c == '_' ? '-' : std::tolower(c); + }); + m_enabled[id] = v3Global.opt.fDfgPeepholeEnabled(str); + }; +#define OPTIMIZATION_CHECK_ENABLED(id, name) checkEnabled(VDfgPeepholePattern::id); + FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_CHECK_ENABLED) +#undef OPTIMIZATION_CHECK_ENABLED +} + +V3DfgPeepholeContext::~V3DfgPeepholeContext() { + const auto emitStat = [this](VDfgPeepholePattern id) { + string str{id.ascii()}; + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { // + return c == '_' ? ' ' : std::tolower(c); + }); + V3Stats::addStat("Optimizations, DFG " + m_label + " Peephole, " + str, m_count[id]); + }; +#define OPTIMIZATION_EMIT_STATS(id, name) emitStat(VDfgPeepholePattern::id); + FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_EMIT_STATS) +#undef OPTIMIZATION_EMIT_STATS +} + +class V3DfgPeephole final : public DfgVisitor { + + // STATE + DfgGraph& m_dfg; // The DfgGraph being visited + V3DfgPeepholeContext& m_ctx; // The config structure + bool m_changed = false; // Changed a vertex + AstNodeDType* const m_bitDType = DfgVertex::dtypeForWidth(1); // Common, so grab it up front + +#define APPLYING(id) if (checkApplying(VDfgPeepholePattern::id)) + + // METHODS + bool checkApplying(VDfgPeepholePattern id) { + if (!m_ctx.m_enabled[id]) return false; + UINFO(9, "Applying DFG patten " << id.ascii() << endl); + ++m_ctx.m_count[id]; + m_changed = true; + return true; + } + + // Shorthand + static AstNodeDType* dtypeForWidth(uint32_t width) { return DfgVertex::dtypeForWidth(width); } + + // Create a new DfgConst vertex with the given width and value + DfgConst* makeConst(FileLine* flp, uint32_t width, uint32_t value) { + const int widthInt = static_cast(width); + return new DfgConst{m_dfg, new AstConst{flp, AstConst::WidthedValue{}, widthInt, value}}; + } + + // Create a new 32-bit DfgConst vertex + DfgConst* makeI32(FileLine* flp, uint32_t value) { return makeConst(flp, 32, value); } + + // Create a new DfgConst vertex with the given width and value zero + DfgConst* makeZero(FileLine* flp, uint32_t width) { return makeConst(flp, width, 0); } + + // Transformations that apply to all commutative binary vertices + void commutativeBinary(DfgVertexWithArity<2>* vtxp) { + DfgVertex* const lhsp = vtxp->source<0>(); + DfgVertex* const rhsp = vtxp->source<1>(); + // Ensure Const is on left-hand side to simplify other patterns + if (lhsp->is()) return; + if (rhsp->is()) { + APPLYING(SWAP_CONST_IN_COMMUTATIVE_BINARY) { + vtxp->lhsp(rhsp); + vtxp->rhsp(lhsp); + return; + } + } + // Ensure Not is on the left-hand side to simplify other patterns + if (lhsp->is()) return; + if (rhsp->is()) { + APPLYING(SWAP_NOT_IN_COMMUTATIVE_BINARY) { + vtxp->lhsp(rhsp); + vtxp->rhsp(lhsp); + return; + } + } + // If both sides are variable references, order the side in some defined way. This allows + // CSE to later merge 'a op b' with 'b op a'. + if (lhsp->is() && rhsp->is()) { + AstVar* const lVarp = lhsp->as()->varp(); + AstVar* const rVarp = rhsp->as()->varp(); + if (lVarp->name() > rVarp->name()) { + APPLYING(SWAP_VAR_IN_COMMUTATIVE_BINARY) { + vtxp->lhsp(rhsp); + vtxp->rhsp(lhsp); + return; + } + } + } + } + + // Bitwise operation with one side Const, and the other side a Concat + template + bool tryPushBitwiseOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) { + UASSERT_OBJ(constp->width() == concatp->width(), vtxp, "Mismatched widths"); + + FileLine* const flp = vtxp->fileline(); + + // If at least one of the sides of the Concat constant, or width 1 (i.e.: can be + // further simplified), then push the Vertex past the Concat + if (concatp->lhsp()->is() || concatp->rhsp()->is() // + || concatp->lhsp()->width() == 1 || concatp->rhsp()->width() == 1) { + APPLYING(PUSH_BITWISE_OP_THROUGH_CONCAT) { + const uint32_t width = concatp->width(); + AstNodeDType* const lDtypep = concatp->lhsp()->dtypep(); + AstNodeDType* const rDtypep = concatp->rhsp()->dtypep(); + const uint32_t lWidth = lDtypep->width(); + const uint32_t rWidth = rDtypep->width(); + + // The new Lhs vertex + Vertex* const newLhsp = new Vertex{m_dfg, flp, lDtypep}; + DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth); + newLhsConstp->num().opSel(constp->num(), width - 1, rWidth); + newLhsp->lhsp(newLhsConstp); + newLhsp->rhsp(concatp->lhsp()); + + // The new Rhs vertex + Vertex* const newRhsp = new Vertex{m_dfg, flp, rDtypep}; + DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth); + newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0); + newRhsp->lhsp(newRhsConstp); + newRhsp->rhsp(concatp->rhsp()); + + // The replacement Concat vertex + DfgConcat* const newConcat + = new DfgConcat{m_dfg, concatp->fileline(), concatp->dtypep()}; + newConcat->lhsp(newLhsp); + newConcat->rhsp(newRhsp); + + // Replace this vertex + vtxp->replaceWith(newConcat); + return true; + } + } + return false; + } + + template + bool tryPushCompareOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) { + UASSERT_OBJ(constp->width() == concatp->width(), vtxp, "Mismatched widths"); + + FileLine* const flp = vtxp->fileline(); + + // If at least one of the sides of the Concat is constant, then push the Vertex past the + // Concat + if (concatp->lhsp()->is() || concatp->rhsp()->is()) { + APPLYING(PUSH_COMPARE_OP_THROUGH_CONCAT) { + const uint32_t width = concatp->width(); + const uint32_t lWidth = concatp->lhsp()->width(); + const uint32_t rWidth = concatp->rhsp()->width(); + + // The new Lhs vertex + Vertex* const newLhsp = new Vertex{m_dfg, flp, m_bitDType}; + DfgConst* const newLhsConstp = makeZero(constp->fileline(), lWidth); + newLhsConstp->num().opSel(constp->num(), width - 1, rWidth); + newLhsp->lhsp(newLhsConstp); + newLhsp->rhsp(concatp->lhsp()); + + // The new Rhs vertex + Vertex* const newRhsp = new Vertex{m_dfg, flp, m_bitDType}; + DfgConst* const newRhsConstp = makeZero(constp->fileline(), rWidth); + newRhsConstp->num().opSel(constp->num(), rWidth - 1, 0); + newRhsp->lhsp(newRhsConstp); + newRhsp->rhsp(concatp->rhsp()); + + // The replacement Vertex + DfgVertexWithArity<2>* const replacementp + = std::is_same::value + ? new DfgAnd{m_dfg, concatp->fileline(), m_bitDType} + : nullptr; + UASSERT_OBJ(replacementp, vtxp, + "Unhandled vertex type in 'tryPushCompareOpThroughConcat': " + << vtxp->typeName()); + replacementp->relinkSource<0>(newLhsp); + replacementp->relinkSource<1>(newRhsp); + + // Replace this vertex + vtxp->replaceWith(replacementp); + return true; + } + } + return false; + } + + template + void optimizeReduction(Vertex* vtxp) { + static_assert(std::is_same::value + || std::is_same::value + || std::is_same::value, + "Invalid 'Vertex' type for this method"); + + DfgVertex* const srcp = vtxp->srcp(); + + // Reduction of 1-bit value -- Currently unreachable as V3Const can remove these, but + // in the future they will be created during this optimization. + if (srcp->width() == 1) { // LCOV_EXCL_START + APPLYING(REMOVE_WIDTH_ONE_REDUCTION) { + vtxp->replaceWith(srcp); + return; + } + } // LCOV_EXCL_STOP + + if (DfgCond* const condp = srcp->cast()) { + if (condp->thenp()->is() || condp->elsep()->is()) { + APPLYING(PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) { + // The new 'then' vertex + Vertex* const newThenp = new Vertex{m_dfg, vtxp->fileline(), m_bitDType}; + newThenp->srcp(condp->thenp()); + + // The new 'else' vertex + Vertex* const newElsep = new Vertex{m_dfg, vtxp->fileline(), m_bitDType}; + newElsep->srcp(condp->elsep()); + + // The replacement Cond vertex + DfgCond* const newCondp = new DfgCond{m_dfg, condp->fileline(), m_bitDType}; + newCondp->condp(condp->condp()); + newCondp->thenp(newThenp); + newCondp->elsep(newElsep); + + // Replace this vertex + vtxp->replaceWith(newCondp); + return; + } + } + } + + if (DfgConst* const constp = srcp->cast()) { + APPLYING(REPLACE_REDUCTION_OF_CONST) { + DfgConst* const replacementp = makeZero(vtxp->fileline(), 1); + if (std::is_same::value) { + replacementp->num().opRedAnd(constp->num()); + } else if (std::is_same::value) { + replacementp->num().opRedOr(constp->num()); + } else { + replacementp->num().opRedXor(constp->num()); + } + vtxp->replaceWith(replacementp); + return; + } + } + } + + void optimizeShiftRHS(DfgVertexWithArity<2>* vtxp) { + if (const DfgConcat* const concatp = vtxp->rhsp()->cast()) { + if (concatp->lhsp()->isZero()) { // Drop redundant zero extension + APPLYING(REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) { // + vtxp->rhsp(concatp->rhsp()); + } + } + } + } + + // VISIT methods + + void visit(DfgVertex*) override {} + + void visit(DfgExtend* vtxp) override { + const uint32_t extension = vtxp->width() - vtxp->srcp()->width(); + UASSERT_OBJ(extension > 0, vtxp, "Useless Extend"); + + FileLine* const flp = vtxp->fileline(); + + // Convert Extend into Concat with zeros. This simplifies other patterns as they only need + // to handle Concat, which is more generic, and don't need special cases for Extend. + APPLYING(REPLACE_EXTEND) { + DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(makeZero(flp, extension)); + replacementp->rhsp(vtxp->srcp()); + vtxp->replaceWith(replacementp); + } + } + + void visit(DfgNot* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->srcp()->width(), vtxp, + "Mismatched width: " << vtxp->width() << " != " << vtxp->srcp()->width()); + + // Not of Cond + if (DfgCond* const condp = vtxp->srcp()->cast()) { + // If at least one of the branches are a constant, push the Not past the Cond + if (condp->thenp()->is() || condp->elsep()->is()) { + APPLYING(PUSH_NOT_THROUGH_COND) { + // The new 'then' vertex + DfgNot* const newThenp = new DfgNot{m_dfg, vtxp->fileline(), vtxp->dtypep()}; + newThenp->srcp(condp->thenp()); + + // The new 'else' vertex + DfgNot* const newElsep = new DfgNot{m_dfg, vtxp->fileline(), vtxp->dtypep()}; + newElsep->srcp(condp->elsep()); + + // The replacement Cond vertex + DfgCond* const newCondp + = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()}; + newCondp->condp(condp->condp()); + newCondp->thenp(newThenp); + newCondp->elsep(newElsep); + + // Replace this vertex + vtxp->replaceWith(newCondp); + return; + } + } + } + + // Not of Not + if (DfgNot* const notp = vtxp->srcp()->cast()) { + UASSERT_OBJ(vtxp->width() == notp->srcp()->width(), vtxp, "Width mismatch"); + APPLYING(REMOVE_NOT_NOT) { + vtxp->replaceWith(notp->srcp()); + return; + } + } + + // Not of Neq + if (DfgNeq* const neqp = vtxp->srcp()->cast()) { + APPLYING(REPLACE_NOT_NEQ) { + DfgEq* const replacementp = new DfgEq{m_dfg, neqp->fileline(), vtxp->dtypep()}; + replacementp->lhsp(neqp->lhsp()); + replacementp->rhsp(neqp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } + } + + // Not of Const + if (DfgConst* const constp = vtxp->srcp()->cast()) { + APPLYING(REPLACE_NOT_OF_CONST) { + DfgConst* const replacementp = makeZero(vtxp->fileline(), vtxp->width()); + replacementp->num().opNot(constp->num()); + vtxp->replaceWith(replacementp); + return; + } + } + } + + void visit(DfgAnd* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + + commutativeBinary(vtxp); + + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + + FileLine* const flp = vtxp->fileline(); + + // Bubble pushing + if (lhsp->is() && rhsp->is()) { + APPLYING(REPLACE_AND_OF_NOT_AND_NOT) { + DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()}; + orp->lhsp(lhsp->as()->srcp()); + orp->rhsp(rhsp->as()->srcp()); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(orp); + vtxp->replaceWith(notp); + return; + } + } + + if (DfgConst* const lhsConstp = lhsp->cast()) { + if (DfgConst* const rhsConstp = rhsp->cast()) { + APPLYING(REPLACE_AND_OF_CONST_AND_CONST) { + DfgConst* const replacementp = makeZero(flp, vtxp->width()); + replacementp->num().opAnd(lhsConstp->num(), rhsConstp->num()); + vtxp->replaceWith(replacementp); + return; + } + } + + if (lhsConstp->isZero()) { + APPLYING(REPLACE_AND_WITH_ZERO) { + vtxp->replaceWith(lhsConstp); + return; + } + } + + if (lhsConstp->isOnes()) { + APPLYING(REMOVE_AND_WITH_ONES) { + vtxp->replaceWith(rhsp); + return; + } + } + + if (DfgConcat* const rhsConcatp = rhsp->cast()) { + if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; + } + } + + if (DfgNot* const lhsNotp = lhsp->cast()) { + // ~A & A is all zeroes + if (lhsNotp->srcp() == rhsp) { + APPLYING(REPLACE_CONTRADICTORY_AND) { + DfgConst* const replacementp = makeZero(flp, vtxp->width()); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + + void visit(DfgOr* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + + commutativeBinary(vtxp); + + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + + FileLine* const flp = vtxp->fileline(); + + // Bubble pushing + if (DfgNot* const lhsNotp = lhsp->cast()) { + if (DfgNot* const rhsNotp = rhsp->cast()) { + APPLYING(REPLACE_OR_OF_NOT_AND_NOT) { + DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()}; + andp->lhsp(lhsNotp->srcp()); + andp->rhsp(rhsNotp->srcp()); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(andp); + vtxp->replaceWith(notp); + return; + } + } + if (DfgNeq* const rhsNeqp = rhsp->cast()) { + APPLYING(REPLACE_OR_OF_NOT_AND_NEQ) { + DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()}; + andp->lhsp(lhsNotp->srcp()); + DfgEq* const newRhsp = new DfgEq{m_dfg, rhsp->fileline(), rhsp->dtypep()}; + newRhsp->lhsp(rhsNeqp->lhsp()); + newRhsp->rhsp(rhsNeqp->rhsp()); + andp->rhsp(newRhsp); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(andp); + vtxp->replaceWith(notp); + return; + } + } + } + + if (DfgConcat* const lhsConcatp = lhsp->cast()) { + if (DfgConcat* const rhsConcatp = rhsp->cast()) { + if (lhsConcatp->lhsp()->width() == rhsConcatp->lhsp()->width()) { + if (lhsConcatp->lhsp()->isZero() && rhsConcatp->rhsp()->isZero()) { + APPLYING(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO) { + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(rhsConcatp->lhsp()); + replacementp->rhsp(lhsConcatp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } + } + if (lhsConcatp->rhsp()->isZero() && rhsConcatp->lhsp()->isZero()) { + APPLYING(REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS) { + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(lhsConcatp->lhsp()); + replacementp->rhsp(rhsConcatp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + } + + if (DfgConst* const lhsConstp = lhsp->cast()) { + if (DfgConst* const rhsConstp = rhsp->cast()) { + APPLYING(REPLACE_OR_OF_CONST_AND_CONST) { + DfgConst* const replacementp = makeZero(flp, vtxp->width()); + replacementp->num().opOr(lhsConstp->num(), rhsConstp->num()); + vtxp->replaceWith(replacementp); + return; + } + } + + if (lhsConstp->isZero()) { + APPLYING(REMOVE_OR_WITH_ZERO) { + vtxp->replaceWith(rhsp); + return; + } + } + + if (lhsConstp->isOnes()) { + APPLYING(REPLACE_OR_WITH_ONES) { + vtxp->replaceWith(lhsp); + return; + } + } + + if (DfgConcat* const rhsConcatp = rhsp->cast()) { + if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; + } + } + + if (DfgNot* const lhsNotp = lhsp->cast()) { + // ~A | A is all ones + if (lhsNotp->srcp() == rhsp) { + APPLYING(REPLACE_TAUTOLOGICAL_OR) { + DfgConst* const replacementp = makeZero(flp, vtxp->width()); + replacementp->num().setAllBits1(); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + + void visit(DfgXor* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + + commutativeBinary(vtxp); + + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + + if (DfgConst* const lhsConstp = lhsp->cast()) { + if (DfgConcat* const rhsConcatp = rhsp->cast()) { + if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; + } + } + } + + void visit(DfgAdd* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + + commutativeBinary(vtxp); + } + + void visit(DfgSub* vtxp) override { + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + UASSERT_OBJ(lhsp->width() == rhsp->width(), vtxp, "Width mismatch"); + UASSERT_OBJ(lhsp->width() == vtxp->width(), vtxp, "Width mismatch"); + if (DfgConst* const rConstp = rhsp->cast()) { + if (rConstp->isZero()) { + APPLYING(REMOVE_SUB_ZERO) { + vtxp->replaceWith(lhsp); + return; + } + } + if (vtxp->width() == 1 && rConstp->toU32() == 1) { + APPLYING(REPLACE_SUB_WITH_NOT) { + DfgNot* const replacementp = new DfgNot{m_dfg, vtxp->fileline(), m_bitDType}; + replacementp->srcp(lhsp); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + + void visit(DfgShiftL* vtxp) override { optimizeShiftRHS(vtxp); } + void visit(DfgShiftR* vtxp) override { optimizeShiftRHS(vtxp); } + void visit(DfgShiftRS* vtxp) override { optimizeShiftRHS(vtxp); } + + void visit(DfgEq* vtxp) override { + commutativeBinary(vtxp); + + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + + if (DfgConst* const lhsConstp = lhsp->cast()) { + if (DfgConst* const rhsConstp = rhsp->cast()) { + APPLYING(REPLACE_EQ_OF_CONST_AND_CONST) { + DfgConst* const replacementp = makeZero(vtxp->fileline(), 1); + if (lhsConstp->constp()->sameTree(rhsConstp->constp())) { + replacementp->num().setLong(1); + } + vtxp->replaceWith(replacementp); + return; + } + } + + if (DfgConcat* const rhsConcatp = rhsp->cast()) { + if (tryPushCompareOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; + } + } + } + + void visit(DfgSel* vtxp) override { + DfgVertex* const fromp = vtxp->fromp(); + DfgConst* const lsbp = vtxp->lsbp()->cast(); + DfgConst* const widthp = vtxp->widthp()->cast(); + if (!lsbp || !widthp) return; + + FileLine* const flp = vtxp->fileline(); + + UASSERT_OBJ(lsbp->toI32() >= 0, vtxp, "Negative LSB in Sel"); + + const uint32_t lsb = lsbp->toU32(); + const uint32_t width = widthp->toU32(); + const uint32_t msb = lsb + width - 1; + + UASSERT_OBJ(width == vtxp->width(), vtxp, "Incorrect Sel width"); + + // Full width select, replace with the source. + if (fromp->width() == width) { + UASSERT_OBJ(lsb == 0, fromp, "OOPS"); + APPLYING(REMOVE_FULL_WIDTH_SEL) { + vtxp->replaceWith(fromp); + return; + } + } + + // Sel from Concat + if (DfgConcat* const concatp = fromp->cast()) { + DfgVertex* const lhsp = concatp->lhsp(); + DfgVertex* const rhsp = concatp->rhsp(); + + if (msb < rhsp->width()) { + // If the select is entirely from rhs, then replace with sel from rhs + APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) { // + vtxp->fromp(rhsp); + } + } else if (lsb >= rhsp->width()) { + // If the select is entirely from the lhs, then replace with sel from lhs + APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) { + vtxp->fromp(lhsp); + vtxp->lsbp(makeI32(flp, lsb - rhsp->width())); + } + } else if (lsb == 0 || msb == concatp->width() - 1 // + || lhsp->is() || rhsp->is()) { + // If the select straddles both sides, but at least one of the sides is wholly + // selected, or at least one of the sides is a Const, then push the Sel past + // the Concat + APPLYING(PUSH_SEL_THROUGH_CONCAT) { + const uint32_t rSelWidth = rhsp->width() - lsb; + const uint32_t lSelWidth = width - rSelWidth; + + // The new Lhs vertex + DfgSel* const newLhsp = new DfgSel{m_dfg, flp, dtypeForWidth(lSelWidth)}; + newLhsp->fromp(lhsp); + newLhsp->lsbp(makeI32(lsbp->fileline(), 0)); + newLhsp->widthp(makeI32(widthp->fileline(), lSelWidth)); + + // The new Rhs vertex + DfgSel* const newRhsp = new DfgSel{m_dfg, flp, dtypeForWidth(rSelWidth)}; + newRhsp->fromp(rhsp); + newRhsp->lsbp(makeI32(lsbp->fileline(), lsb)); + newRhsp->widthp(makeI32(widthp->fileline(), rSelWidth)); + + // The replacement Concat vertex + DfgConcat* const newConcat + = new DfgConcat{m_dfg, concatp->fileline(), vtxp->dtypep()}; + newConcat->lhsp(newLhsp); + newConcat->rhsp(newRhsp); + + // Replace this vertex + vtxp->replaceWith(newConcat); + return; + } + } + } + + if (DfgReplicate* const repp = fromp->cast()) { + // If the Sel is wholly into the source of the Replicate, push the Sel through the + // Replicate and apply it directly to the source of the Replicate. + const uint32_t srcWidth = repp->srcp()->width(); + if (width <= srcWidth) { + const uint32_t newLsb = lsb % srcWidth; + if (newLsb + width <= srcWidth) { + APPLYING(PUSH_SEL_THROUGH_REPLICATE) { + vtxp->fromp(repp->srcp()); + vtxp->lsbp(makeI32(flp, newLsb)); + } + } + } + } + + // Sel from Not + if (DfgNot* const notp = fromp->cast()) { + // Replace "Sel from Not" with "Not of Sel" + if (!notp->hasMultipleSinks()) { + UASSERT_OBJ(notp->srcp()->width() == notp->width(), notp, "Mismatched widths"); + APPLYING(PUSH_SEL_THROUGH_NOT) { + // Make Sel select from source of Not + vtxp->fromp(notp->srcp()); + // Add Not after Sel + DfgNot* const replacementp + = new DfgNot{m_dfg, notp->fileline(), vtxp->dtypep()}; + vtxp->replaceWith(replacementp); + replacementp->srcp(vtxp); + } + } + } + + // Sel from Sel + if (DfgSel* const selp = fromp->cast()) { + UASSERT_OBJ(widthp->toU32() <= selp->width(), vtxp, "Out of bound Sel"); + if (DfgConst* const sourceLsbp = selp->lsbp()->cast()) { + UASSERT_OBJ(sourceLsbp->toI32() >= 0, selp, "negative"); + UASSERT_OBJ(selp->widthp()->as()->toU32() >= widthp->toU32(), selp, + "negative"); + APPLYING(REPLACE_SEL_FROM_SEL) { + // Make this Sel select from the source of the source Sel + vtxp->fromp(selp->fromp()); + // Adjust LSB + vtxp->lsbp(makeI32(flp, lsb + sourceLsbp->toU32())); + } + } + } + + // Sel from Cond + if (DfgCond* const condp = fromp->cast()) { + // If at least one of the branches are a constant, push the select past the cond + if (condp->thenp()->is() || condp->elsep()->is()) { + APPLYING(PUSH_SEL_THROUGH_COND) { + // The new 'then' vertex + DfgSel* const newThenp = new DfgSel{m_dfg, flp, vtxp->dtypep()}; + newThenp->fromp(condp->thenp()); + newThenp->lsbp(makeI32(lsbp->fileline(), lsb)); + newThenp->widthp(makeI32(widthp->fileline(), width)); + + // The new 'else' vertex + DfgSel* const newElsep = new DfgSel{m_dfg, flp, vtxp->dtypep()}; + newElsep->fromp(condp->elsep()); + newElsep->lsbp(makeI32(lsbp->fileline(), lsb)); + newElsep->widthp(makeI32(widthp->fileline(), width)); + + // The replacement Cond vertex + DfgCond* const newCondp + = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()}; + newCondp->condp(condp->condp()); + newCondp->thenp(newThenp); + newCondp->elsep(newElsep); + + // Replace this vertex + vtxp->replaceWith(newCondp); + return; + } + } + } + + // Sel from ShiftL + if (DfgShiftL* const shiftLp = fromp->cast()) { + // If selecting bottom bits of left shift, push the Sel before the shift + if (lsb == 0) { + UASSERT_OBJ(shiftLp->lhsp()->width() >= width, vtxp, "input of shift narrow"); + APPLYING(PUSH_SEL_THROUGH_SHIFTL) { + vtxp->fromp(shiftLp->lhsp()); + DfgShiftL* const newShiftLp + = new DfgShiftL{m_dfg, shiftLp->fileline(), vtxp->dtypep()}; + vtxp->replaceWith(newShiftLp); + newShiftLp->lhsp(vtxp); + newShiftLp->rhsp(shiftLp->rhsp()); + } + } + } + + // Sel from Const + if (DfgConst* const constp = fromp->cast()) { + APPLYING(REPLACE_SEL_FROM_CONST) { + DfgConst* const replacementp = makeZero(flp, width); + replacementp->num().opSel(constp->num(), msb, lsb); + vtxp->replaceWith(replacementp); + return; + } + } + } + + void visit(DfgRedOr* vtxp) override { optimizeReduction(vtxp); } + void visit(DfgRedAnd* vtxp) override { optimizeReduction(vtxp); } + void visit(DfgRedXor* vtxp) override { optimizeReduction(vtxp); } + + void visit(DfgConcat* vtxp) override { + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + UASSERT_OBJ(vtxp->width() == lhsp->width() + rhsp->width(), vtxp, + "Incorrect Concat width: " << vtxp->width() << " != " << lhsp->width() << " + " + << rhsp->width()); + + FileLine* const flp = vtxp->fileline(); + + { + const auto joinConsts + = [this](DfgConst* lConstp, DfgConst* rConstp, FileLine* flp) -> DfgConst* { + DfgConst* const newConstp = makeZero(flp, lConstp->width() + rConstp->width()); + newConstp->num().opSelInto(rConstp->num(), 0, rConstp->width()); + newConstp->num().opSelInto(lConstp->num(), rConstp->width(), lConstp->width()); + return newConstp; + }; + + DfgConst* const lConstp = lhsp->cast(); + DfgConst* const rConstp = rhsp->cast(); + + if (lConstp && rConstp) { + APPLYING(REPLACE_CONCAT_OF_CONSTS) { + vtxp->replaceWith(joinConsts(lConstp, rConstp, flp)); + return; + } + } + + if (lConstp) { + if (DfgConcat* const rConcatp = rhsp->cast()) { + if (DfgConst* const rlConstp = rConcatp->lhsp()->cast()) { + APPLYING(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS) { + DfgConst* const joinedConstp = joinConsts(lConstp, rlConstp, flp); + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(joinedConstp); + replacementp->rhsp(rConcatp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } + } + } + + if (lConstp->isZero()) { + if (DfgSel* const rSelp = rhsp->cast()) { + if (DfgConst* const rSelLsbConstp = rSelp->lsbp()->cast()) { + if (vtxp->width() == rSelp->fromp()->width() + && rSelLsbConstp->toU32() == lConstp->width()) { + const uint32_t rSelWidth + = rSelp->widthp()->as()->toU32(); + UASSERT_OBJ(lConstp->width() + rSelWidth == vtxp->width(), vtxp, + "Inconsistent"); + APPLYING(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) { + DfgShiftR* const replacementp + = new DfgShiftR{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(rSelp->fromp()); + replacementp->rhsp(makeI32(flp, lConstp->width())); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + } + } + + if (rConstp) { + if (DfgConcat* const lConcatp = lhsp->cast()) { + if (DfgConst* const lrConstp = lConcatp->rhsp()->cast()) { + APPLYING(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS) { + DfgConst* const joinedConstp = joinConsts(lrConstp, rConstp, flp); + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(lConcatp->lhsp()); + replacementp->rhsp(joinedConstp); + vtxp->replaceWith(replacementp); + return; + } + } + } + + if (rConstp->isZero()) { + if (DfgSel* const lSelp = lhsp->cast()) { + if (DfgConst* const lSelLsbConstp = lSelp->lsbp()->cast()) { + if (vtxp->width() == lSelp->fromp()->width() + && lSelLsbConstp->toU32() == 0) { + const uint32_t lSelWidth + = lSelp->widthp()->as()->toU32(); + UASSERT_OBJ(lSelWidth + rConstp->width() == vtxp->width(), vtxp, + "Inconsistent"); + APPLYING(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) { + DfgShiftL* const replacementp + = new DfgShiftL{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(lSelp->fromp()); + replacementp->rhsp(makeI32(flp, rConstp->width())); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + } + } + } + + { + DfgNot* const lNot = lhsp->cast(); + DfgNot* const rNot = rhsp->cast(); + if (lNot && rNot) { + APPLYING(PUSH_CONCAT_THROUGH_NOTS) { + vtxp->lhsp(lNot->srcp()); + vtxp->rhsp(rNot->srcp()); + DfgNot* const replacementp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + vtxp->replaceWith(replacementp); + replacementp->srcp(vtxp); + return; + } + } + } + + { + const auto joinSels = [this](DfgSel* lSelp, DfgSel* rSelp, FileLine* flp) -> DfgSel* { + DfgConst* const lLsbp = lSelp->lsbp()->cast(); + DfgConst* const lWidthp = lSelp->widthp()->cast(); + DfgConst* const rLsbp = rSelp->lsbp()->cast(); + DfgConst* const rWidthp = rSelp->widthp()->cast(); + if (lLsbp && lWidthp && rLsbp && rWidthp) { + if (lSelp->fromp()->equals(*rSelp->fromp())) { + if (lLsbp->toU32() == rLsbp->toU32() + rWidthp->toU32()) { + // Two consecutive Sels, make a single Sel. + const uint32_t width = lWidthp->toU32() + rWidthp->toU32(); + AstNodeDType* const dtypep = dtypeForWidth(width); + DfgSel* const joinedSelp = new DfgSel{m_dfg, flp, dtypep}; + joinedSelp->fromp(rSelp->fromp()); + joinedSelp->lsbp(rSelp->lsbp()); + joinedSelp->widthp(makeI32(flp, width)); + return joinedSelp; + } + } + } + return nullptr; + }; + + DfgSel* const lSelp = lhsp->cast(); + DfgSel* const rSelp = rhsp->cast(); + if (lSelp && rSelp) { + if (DfgSel* const jointSelp = joinSels(lSelp, rSelp, flp)) { + APPLYING(REMOVE_CONCAT_OF_ADJOINING_SELS) { + vtxp->replaceWith(jointSelp); + return; + } + } + } + if (lSelp) { + if (DfgConcat* const rConcatp = rhsp->cast()) { + if (DfgSel* const rlSelp = rConcatp->lhsp()->cast()) { + if (DfgSel* const jointSelp = joinSels(lSelp, rlSelp, flp)) { + APPLYING(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS) { + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(jointSelp); + replacementp->rhsp(rConcatp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + } + if (rSelp) { + if (DfgConcat* const lConcatp = lhsp->cast()) { + if (DfgSel* const lrlSelp = lConcatp->rhsp()->cast()) { + if (DfgSel* const jointSelp = joinSels(lrlSelp, rSelp, flp)) { + APPLYING(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS) { + DfgConcat* const replacementp + = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(lConcatp->lhsp()); + replacementp->rhsp(jointSelp); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + } + } + } + + void visit(DfgCond* vtxp) override { + DfgVertex* const condp = vtxp->condp(); + DfgVertex* const thenp = vtxp->thenp(); + DfgVertex* const elsep = vtxp->elsep(); + UASSERT_OBJ(vtxp->width() == thenp->width(), vtxp, "Width mismatch"); + UASSERT_OBJ(vtxp->width() == elsep->width(), vtxp, "Width mismatch"); + + if (condp->width() != 1) return; + + FileLine* const flp = vtxp->fileline(); + + if (condp->isOnes()) { + APPLYING(REMOVE_COND_WITH_TRUE_CONDITION) { + vtxp->replaceWith(thenp); + return; + } + } + + if (condp->isZero()) { + APPLYING(REMOVE_COND_WITH_FALSE_CONDITION) { + vtxp->replaceWith(elsep); + return; + } + } + + if (DfgNot* const condNotp = condp->cast()) { + APPLYING(SWAP_COND_WITH_NOT_CONDITION) { + vtxp->condp(condNotp->srcp()); + vtxp->thenp(elsep); + vtxp->elsep(thenp); + visit(vtxp); + return; + } + } + + if (DfgNeq* const condNeqp = condp->cast()) { + APPLYING(SWAP_COND_WITH_NEQ_CONDITION) { + DfgEq* const newCondp = new DfgEq{m_dfg, condp->fileline(), condp->dtypep()}; + newCondp->lhsp(condNeqp->lhsp()); + newCondp->rhsp(condNeqp->rhsp()); + vtxp->condp(newCondp); + vtxp->thenp(elsep); + vtxp->elsep(thenp); + visit(vtxp); + return; + } + } + + if (DfgNot* const thenNotp = thenp->cast()) { + if (DfgNot* const elseNotp = elsep->cast()) { + APPLYING(PULL_NOTS_THROUGH_COND) { + DfgNot* const replacementp + = new DfgNot{m_dfg, thenp->fileline(), vtxp->dtypep()}; + vtxp->thenp(thenNotp->srcp()); + vtxp->elsep(elseNotp->srcp()); + vtxp->replaceWith(replacementp); + replacementp->srcp(vtxp); + return; + } + } + } + + if (vtxp->width() == 1) { + AstNodeDType* const dtypep = vtxp->dtypep(); + if (thenp->isZero()) { // a ? 0 : b becomes ~a & b + APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) { + DfgAnd* const repalcementp = new DfgAnd{m_dfg, flp, dtypep}; + DfgNot* const notp = new DfgNot{m_dfg, flp, dtypep}; + notp->srcp(condp); + repalcementp->lhsp(notp); + repalcementp->rhsp(elsep); + vtxp->replaceWith(repalcementp); + return; + } + } + if (thenp->isOnes()) { // a ? 1 : b becomes a | b + APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ONES) { + DfgOr* const repalcementp = new DfgOr{m_dfg, flp, dtypep}; + repalcementp->lhsp(condp); + repalcementp->rhsp(elsep); + vtxp->replaceWith(repalcementp); + return; + } + } + if (elsep->isZero()) { // a ? b : 0 becomes a & b + APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ZERO) { + DfgAnd* const repalcementp = new DfgAnd{m_dfg, flp, dtypep}; + repalcementp->lhsp(condp); + repalcementp->rhsp(thenp); + vtxp->replaceWith(repalcementp); + return; + } + } + if (elsep->isOnes()) { // a ? b : 1 becomes ~a | b + APPLYING(REPLACE_COND_WITH_ELSE_BRANCH_ONES) { + DfgOr* const repalcementp = new DfgOr{m_dfg, flp, dtypep}; + DfgNot* const notp = new DfgNot{m_dfg, flp, dtypep}; + notp->srcp(condp); + repalcementp->lhsp(notp); + repalcementp->rhsp(thenp); + vtxp->replaceWith(repalcementp); + return; + } + } + } + } + +#undef APPLYING + + // Process one vertex. Return true if graph changed + bool processVertex(DfgVertex& vtx) { + // Keep DfgVars in this pass, we will remove them later if they become redundant + // Note: We want to keep the original variables for non-var vertices that drive multiple + // sinks (otherwise we would need to introduce a temporary, it is better for debugging to + // keep the original variable name, if one is available), so we can't remove redundant + // variables here. + if (vtx.is()) return false; + + // If it has no sinks (unused), we can remove it + if (!vtx.hasSinks()) { + vtx.unlinkDelete(m_dfg); + return true; + } + + // Transform node + m_changed = false; + iterate(&vtx); + if (!vtx.hasSinks()) vtx.unlinkDelete(m_dfg); // If it became unused, we can remove it + return m_changed; + } + + V3DfgPeephole(DfgGraph& dfg, V3DfgPeepholeContext& ctx) + : m_dfg{dfg} + , m_ctx{ctx} {} + +public: + static void apply(DfgGraph& dfg, V3DfgPeepholeContext& ctx) { + V3DfgPeephole visitor{dfg, ctx}; + dfg.runToFixedPoint([&](DfgVertex& vtx) { return visitor.processVertex(vtx); }); + } +}; + +void V3DfgPasses::peephole(DfgGraph& dfg, V3DfgPeepholeContext& ctx) { + V3DfgPeephole::apply(dfg, ctx); +} diff --git a/src/V3DfgPeephole.h b/src/V3DfgPeephole.h new file mode 100644 index 000000000..1836341db --- /dev/null +++ b/src/V3DfgPeephole.h @@ -0,0 +1,126 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Peephole optimizations over DfgGraph +// +// 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_V3DFGPEEPHOLE_H_ +#define VERILATOR_V3DFGPEEPHOLE_H_ + +#include "config_build.h" + +#include + +#define _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, arg) macro(arg, #arg) + +// clang-format off +#define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_CONST_IN_COMMUTATIVE_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_NOT_IN_COMMUTATIVE_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_VAR_IN_COMMUTATIVE_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMPARE_OP_THROUGH_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_WIDTH_ONE_REDUCTION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_REDUCTION_OF_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EXTEND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_NOT_THROUGH_COND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_NOT_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_NEQ) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_OF_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_NOT_AND_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_CONST_AND_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_WITH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_AND_WITH_ONES) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONTRADICTORY_AND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NEQ) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONST_AND_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_OR_WITH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_WITH_ONES) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_TAUTOLOGICAL_OR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SUB_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SUB_WITH_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EQ_OF_CONST_AND_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_FULL_WIDTH_SEL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_RHS_OF_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_LHS_OF_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_REPLICATE) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_SEL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_COND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SHIFTL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_OF_CONSTS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_CONCAT_THROUGH_NOTS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_CONCAT_OF_ADJOINING_SELS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_COND_WITH_FALSE_CONDITION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_COND_WITH_TRUE_CONDITION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NOT_CONDITION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NEQ_CONDITION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ONES) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ONES) + +// clang-format on + +class VDfgPeepholePattern final { +public: + enum en : unsigned { +#define OPTIMIZATION_ID(id, name) id, + FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_ID) +#undef OPTIMIZATION_ID + _ENUM_END + }; + enum en m_e; + const char* ascii() const { + static const char* const names[] = { +#define OPTIMIZATION_NAME(id, name) name, + FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_NAME) +#undef OPTIMIZATION_NAME + "_ENUM_END" // + }; + return names[m_e]; + } + + // cppcheck-suppress noExplicitConstructor + VDfgPeepholePattern(en _e) + : m_e{_e} {} + operator en() const { return m_e; } +}; + +struct V3DfgPeepholeContext final { + const std::string m_label; // Label to apply to stats + + // Enable flags for each optimization + bool m_enabled[VDfgPeepholePattern::_ENUM_END]; + // Count of applications for each optimization (for statistics) + VDouble0 m_count[VDfgPeepholePattern::_ENUM_END]; + + V3DfgPeepholeContext(const std::string& label); + ~V3DfgPeepholeContext(); +}; + +#endif diff --git a/src/V3Error.h b/src/V3Error.h index 209936898..d5be7e005 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -458,6 +458,7 @@ inline void v3errorEndFatal(std::ostringstream& sstr) { #define VL_DEFINE_DEBUG_FUNCTIONS \ VL_DEFINE_DEBUG(); /* Define 'int debug()' */ \ VL_DEFINE_DUMP(); /* Define 'int dump()' */ \ + VL_DEFINE_DUMP(Dfg); /* Define 'int dumpDfg()' */ \ VL_DEFINE_DUMP(Graph); /* Define 'int dumpGraph()' */ \ VL_DEFINE_DUMP(Tree); /* Define 'int dumpTree()' */ \ static_assert(true, "") diff --git a/src/V3Hash.h b/src/V3Hash.h index c5dd1b631..3df97c079 100644 --- a/src/V3Hash.h +++ b/src/V3Hash.h @@ -69,4 +69,9 @@ public: std::ostream& operator<<(std::ostream& os, const V3Hash& rhs); +template <> +struct std::hash { + std::size_t operator()(const V3Hash& h) const noexcept { return h.value(); } +}; + #endif // Guard diff --git a/src/V3List.h b/src/V3List.h index 883db89af..83ef4de17 100644 --- a/src/V3List.h +++ b/src/V3List.h @@ -44,6 +44,8 @@ public: // METHODS T begin() const { return m_headp; } T end() const { return nullptr; } + T rbegin() const { return m_tailp; } + T rend() const { return nullptr; } bool empty() const { return m_headp == nullptr; } void reset() { // clear() without walking the list m_headp = nullptr; @@ -78,6 +80,7 @@ public: #endif } T nextp() const { return m_nextp; } + T prevp() const { return m_prevp; } // METHODS void pushBack(V3List& listr, T newp) { // "this" must be a element inside of *newp diff --git a/src/V3OptionParser.cpp b/src/V3OptionParser.cpp index 1a1f7d84a..cc019b33d 100644 --- a/src/V3OptionParser.cpp +++ b/src/V3OptionParser.cpp @@ -55,6 +55,7 @@ struct V3OptionParser::Impl { template class ActionOnOff; // "-opt" and "-no-opt" for bool-ish class ActionCbCall; // Callback without argument for "-opt" + class ActionCbFOnOff; // Callback for "-fopt" and "-fno-opt" class ActionCbOnOff; // Callback for "-opt" and "-no-opt" template class ActionCbVal; // Callback for "-opt val" @@ -108,6 +109,8 @@ V3OPTION_PARSER_DEF_ACT_CLASS(ActionOnOff, VOptionBool, m_valp->setTrueOrFalse(! } V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbCall, void(void), m_cb(), en::NONE); +V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbFOnOff, void(bool), m_cb(!hasPrefixFNo(optp)), + en::FONOFF); V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbOnOff, void(bool), m_cb(!hasPrefixNo(optp)), en::ONOFF); template <> V3OPTION_PARSER_DEF_ACT_CB_CLASS(ActionCbVal, void(int), m_cb(std::atoi(argp)), en::VALUE); @@ -238,6 +241,7 @@ V3OPTION_PARSER_DEF_OP(OnOff, bool*, ActionOnOff) V3OPTION_PARSER_DEF_OP(OnOff, VOptionBool*, ActionOnOff) #endif V3OPTION_PARSER_DEF_OP(CbCall, Impl::ActionCbCall::CbType, ActionCbCall) +V3OPTION_PARSER_DEF_OP(CbFOnOff, Impl::ActionCbFOnOff::CbType, ActionCbFOnOff) V3OPTION_PARSER_DEF_OP(CbOnOff, Impl::ActionCbOnOff::CbType, ActionCbOnOff) V3OPTION_PARSER_DEF_OP(CbVal, Impl::ActionCbVal::CbType, ActionCbVal) V3OPTION_PARSER_DEF_OP(CbVal, Impl::ActionCbVal::CbType, ActionCbVal) diff --git a/src/V3OptionParser.h b/src/V3OptionParser.h index 2c5dbf6fb..23da89245 100644 --- a/src/V3OptionParser.h +++ b/src/V3OptionParser.h @@ -109,7 +109,8 @@ public: struct Set {}; // For ActionSet struct CbCall {}; // For ActionCbCall - struct CbOnOff {}; // For ActionOnOff of ActionFOnOff + struct CbFOnOff {}; // For ActionCbFOnOff + struct CbOnOff {}; // For ActionCbOnOff struct CbPartialMatch {}; // For ActionCbPartialMatch struct CbPartialMatchVal {}; // For ActionCbPartialMatchVal struct CbVal {}; // For ActionCbVal @@ -134,6 +135,7 @@ public: #endif ActionIfs& operator()(const char* optp, CbCall, std::function) const; + ActionIfs& operator()(const char* optp, CbFOnOff, std::function) const; ActionIfs& operator()(const char* optp, CbOnOff, std::function) const; ActionIfs& operator()(const char* optp, CbVal, std::function) const; ActionIfs& operator()(const char* optp, CbVal, std::function) const; @@ -153,6 +155,7 @@ public: const auto FOnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::FOnOff{}; \ const auto OnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::OnOff{}; \ const auto CbCall VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbCall{}; \ + const auto CbFOnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbFOnOff{}; \ const auto CbOnOff VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbOnOff{}; \ const auto CbPartialMatch VL_ATTR_UNUSED = V3OptionParser::AppendHelper::CbPartialMatch{}; \ const auto CbPartialMatchVal VL_ATTR_UNUSED \ diff --git a/src/V3Options.cpp b/src/V3Options.cpp index c662cc59a..3fd6efe76 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1123,6 +1123,19 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char DECL_OPTION("-fconst", FOnOff, &m_fConst); DECL_OPTION("-fconst-bit-op-tree", FOnOff, &m_fConstBitOpTree); DECL_OPTION("-fdedup", FOnOff, &m_fDedupe); + DECL_OPTION("-fdfg", CbFOnOff, [this](bool flag) { + m_fDfgPreInline = flag; + m_fDfgPostInline = flag; + }); + DECL_OPTION("-fdfg-peephole", FOnOff, &m_fDfgPeephole); + DECL_OPTION("-fdfg-peephole-", CbPartialMatch, [this](const char* optp) { // + m_fDfgPeepholeDisabled.erase(optp); + }); + DECL_OPTION("-fno-dfg-peephole-", CbPartialMatch, [this](const char* optp) { // + m_fDfgPeepholeDisabled.emplace(optp); + }); + DECL_OPTION("-fdfg-pre-inline", FOnOff, &m_fDfgPreInline); + DECL_OPTION("-fdfg-post-inline", FOnOff, &m_fDfgPostInline); DECL_OPTION("-fexpand", FOnOff, &m_fExpand); DECL_OPTION("-fgate", FOnOff, &m_fGate); DECL_OPTION("-finline", FOnOff, &m_fInline); @@ -1854,6 +1867,8 @@ void V3Options::optimize(int level) { m_fConst = flag; m_fConstBitOpTree = flag; m_fDedupe = flag; + m_fDfgPreInline = flag; + m_fDfgPostInline = flag; m_fExpand = flag; m_fGate = flag; m_fInline = flag; diff --git a/src/V3Options.h b/src/V3Options.h index bc27f0b2b..d4e80a38f 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -214,6 +214,7 @@ private: DebugLevelMap m_dumpLevel; // argument: --dumpi- std::map m_parameters; // Parameters std::map m_hierBlocks; // main switch: --hierarchical-block + V3StringSet m_fDfgPeepholeDisabled; // argument: -f[no-]dfg-peephole- bool m_preprocOnly = false; // main switch: -E bool m_makePhony = false; // main switch: -MP @@ -350,6 +351,9 @@ private: bool m_fConst; // main switch: -fno-const: constant folding bool m_fConstBitOpTree; // main switch: -fno-const-bit-op-tree constant bit op tree bool m_fDedupe; // main switch: -fno-dedupe: logic deduplication + bool m_fDfgPeephole = true; // main switch: -fno-dfg-peephole + bool m_fDfgPreInline; // main switch: -fno-dfg-pre-inline and -fno-dfg + bool m_fDfgPostInline; // main switch: -fno-dfg-post-inline and -fno-dfg bool m_fExpand; // main switch: -fno-expand: expansion of C macros bool m_fGate; // main switch: -fno-gate: gate wire elimination bool m_fInline; // main switch: -fno-inline: module inlining @@ -592,6 +596,12 @@ public: bool fConst() const { return m_fConst; } bool fConstBitOpTree() const { return m_fConstBitOpTree; } bool fDedupe() const { return m_fDedupe; } + bool fDfgPeephole() const { return m_fDfgPeephole; } + bool fDfgPreInline() const { return m_fDfgPreInline; } + bool fDfgPostInline() const { return m_fDfgPostInline; } + bool fDfgPeepholeEnabled(const std::string& name) const { + return !m_fDfgPeepholeDisabled.count(name); + } bool fExpand() const { return m_fExpand; } bool fGate() const { return m_fGate; } bool fInline() const { return m_fInline; } diff --git a/src/Verilator.cpp b/src/Verilator.cpp index eedc41193..2bd4798a7 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -40,6 +40,7 @@ #include "V3Depth.h" #include "V3DepthBlock.h" #include "V3Descope.h" +#include "V3DfgOptimizer.h" #include "V3EmitC.h" #include "V3EmitCMain.h" #include "V3EmitCMake.h" @@ -235,6 +236,16 @@ static void process() { v3Global.constRemoveXs(true); } + if (v3Global.opt.fDfgPreInline() || v3Global.opt.fDfgPostInline()) { + // If doing DFG optimization, extract some additional candidates + V3DfgOptimizer::extract(v3Global.rootp()); + } + + if (v3Global.opt.fDfgPreInline()) { + // Pre inline DFG optimization + V3DfgOptimizer::optimize(v3Global.rootp(), " pre inline"); + } + if (!(v3Global.opt.xmlOnly() && !v3Global.opt.flatten())) { // Module inlining // Cannot remove dead variables after this, as alias information for final @@ -245,6 +256,11 @@ static void process() { } } + if (v3Global.opt.fDfgPostInline()) { + // Post inline DFG optimization + V3DfgOptimizer::optimize(v3Global.rootp(), "post inline"); + } + // --PRE-FLAT OPTIMIZATIONS------------------ // Initial const/dead to reduce work for ordering code diff --git a/src/astgen b/src/astgen index c1d571ecd..183ff5837 100755 --- a/src/astgen +++ b/src/astgen @@ -174,6 +174,7 @@ class Node: Nodes = {} SortedNodes = None +DfgVertices = None ClassRefs = {} Stages = {} @@ -987,6 +988,142 @@ def write_op_checks(filename): ''') +def write_dfg_vertex_classes(filename): + with open_file(filename) as fh: + fh.write("\n") + for node in DfgVertices: + fh.write("class Dfg{} final : public DfgVertexWithArity<{}> {{\n". + format(node.name, node.arity)) + fh.write(" friend class DfgVertex;\n") + fh.write(" friend class DfgVisitor;\n") + fh.write(" void accept(DfgVisitor& visitor) override;\n") + fh.write( + " static constexpr DfgType dfgType() {{ return DfgType::at{t}; }};\n" + .format(t=node.name)) + fh.write("public:\n") + fh.write( + " Dfg{t}(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep) : DfgVertexWithArity<{a}>{{dfg, flp, dtypep, dfgType()}} {{}}\n" + .format(t=node.name, a=node.arity)) + # Accessors + operandNames = tuple( + node.getOp(n)[0] for n in range(1, node.arity + 1)) + assert not operandNames or len(operandNames) == node.arity + for i, n in enumerate(operandNames): + fh.write( + " DfgVertex* {n}() const {{ return source<{i}>(); }}\n". + format(n=n, i=i)) + for i, n in enumerate(operandNames): + fh.write( + " void {n}(DfgVertex* vtxp) {{ relinkSource<{i}>(vtxp); }}\n" + .format(n=n, i=i)) + if operandNames: + names = ", ".join(map(lambda _: '"' + _ + '"', operandNames)) + fh.write( + " const string srcName(size_t idx) const override {\n") + fh.write( + " static const char* names[{a}] = {{ {ns} }};\n". + format(a=node.arity, ns=names)) + fh.write(" return names[idx];\n") + fh.write(" }\n") + fh.write("};\n") + fh.write("\n") + fh.write("\n") + + fh.write("\n\ntemplate\n") + fh.write("struct DfgForAstImpl;\n\n") + for node in DfgVertices: + fh.write("template <>\n") + fh.write( + "struct DfgForAstImpl {{\n".format(name=node.name)) + fh.write(" using type = Dfg{name};\n".format(name=node.name)) + fh.write("};\n") + fh.write("\ntemplate\n") + fh.write("using DfgForAst = typename DfgForAstImpl::type;\n") + + fh.write("\n\ntemplate\n") + fh.write("struct AstForDfgImpl;\n\n") + for node in DfgVertices: + fh.write("template <>\n") + fh.write( + "struct AstForDfgImpl {{\n".format(name=node.name)) + fh.write(" using type = Ast{name};\n".format(name=node.name)) + fh.write("};\n") + fh.write("\ntemplate\n") + fh.write("using AstForDfg = typename AstForDfgImpl::type;\n") + + +def write_dfg_visitor_decls(filename): + with open_file(filename) as fh: + fh.write("\n") + fh.write("virtual void visit(DfgVertex*) = 0;\n") + for node in DfgVertices: + fh.write("virtual void visit(Dfg{}*);\n".format(node.name)) + + +def write_dfg_definitions(filename): + with open_file(filename) as fh: + fh.write("\n") + for node in DfgVertices: + fh.write( + "void Dfg{}::accept(DfgVisitor& visitor) {{ visitor.visit(this); }}\n" + .format(node.name)) + fh.write("\n") + for node in DfgVertices: + fh.write( + "void DfgVisitor::visit(Dfg{}* vtxp) {{ visit(static_cast(vtxp)); }}\n" + .format(node.name)) + + +def write_dfg_ast_to_dfg(filename): + with open_file(filename) as fh: + fh.write("\n") + for node in DfgVertices: + fh.write( + "void visit(Ast{t}* nodep) override {{\n".format(t=node.name)) + fh.write( + ' UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");\n' + ) + fh.write(" if (unhandled(nodep)) return;\n") + fh.write( + " Dfg{t}* const vtxp = makeVertex(nodep, *m_dfgp);\n" + .format(t=node.name)) + fh.write(" if (!vtxp) {\n") + fh.write(" m_foundUnhandled = true;\n") + fh.write(" ++m_ctx.m_nonRepNode;\n") + fh.write(" return;\n") + fh.write(" }\n\n") + fh.write(" m_uncommittedVertices.push_back(vtxp);\n") + for i in range(node.arity): + fh.write(" iterate(nodep->op{j}p());\n".format(j=i + 1)) + fh.write(" if (m_foundUnhandled) return;\n") + fh.write( + ' UASSERT_OBJ(nodep->op{j}p()->user1p(), nodep, "Child {j} missing Dfg vertex");\n' + .format(j=i + 1)) + fh.write( + " vtxp->relinkSource<{i}>(nodep->op{j}p()->user1u().to());\n\n" + .format(i=i, j=i + 1)) + fh.write(" nodep->user1p(vtxp);\n") + fh.write("}\n") + + +def write_dfg_dfg_to_ast(filename): + with open_file(filename) as fh: + fh.write("\n") + for node in DfgVertices: + fh.write( + "void visit(Dfg{t}* vtxp) override {{\n".format(t=node.name)) + for i in range(node.arity): + fh.write( + " AstNodeMath* const op{j}p = convertSource(vtxp->source<{i}>());\n" + .format(i=i, j=i + 1)) + fh.write( + " m_resultp = makeNode(vtxp".format(t=node.name)) + for i in range(node.arity): + fh.write(", op{j}p".format(j=i + 1)) + fh.write(");\n") + fh.write("}\n") + + ###################################################################### # main @@ -1041,6 +1178,11 @@ for node in SortedNodes: "%Error: Non-final AstNode subclasses must be named AstNode*: Ast" + node.name) +DfgBases = (Nodes["NodeUniop"], Nodes["NodeBiop"], Nodes["NodeTriop"]) +DfgVertices = tuple( + node for node in SortedNodes + if node.isLeaf and any(node.isSubClassOf(base) for base in DfgBases)) + # Check ordering of node definitions files = tuple(sorted(set(_.file for _ in SortedNodes))) @@ -1089,6 +1231,11 @@ if Args.classes: write_yystype("V3Ast__gen_yystype.h") write_macros("V3Ast__gen_macros.h") write_op_checks("V3Ast__gen_op_checks.h") + write_dfg_vertex_classes("V3Dfg__gen_vertex_classes.h") + write_dfg_visitor_decls("V3Dfg__gen_visitor_decls.h") + write_dfg_definitions("V3Dfg__gen_definitions.h") + write_dfg_ast_to_dfg("V3Dfg__gen_ast_to_dfg.h") + write_dfg_dfg_to_ast("V3Dfg__gen_dfg_to_ast.h") for cpt in Args.infiles: if not re.search(r'.cpp$', cpt): diff --git a/test_regress/t/t_altera_lpm.v b/test_regress/t/t_altera_lpm.v index 44676b6dc..0a3671e49 100644 --- a/test_regress/t/t_altera_lpm.v +++ b/test_regress/t/t_altera_lpm.v @@ -46,6 +46,7 @@ //END_MODULE_NAME-------------------------------------------------------------- //See also: https://github.com/twosigma/verilator_support +// verilator lint_off BLKANDNBLK // verilator lint_off COMBDLY // verilator lint_off INITIALDLY // verilator lint_off MULTIDRIVEN diff --git a/test_regress/t/t_cdc_async_bad.out b/test_regress/t/t_cdc_async_bad.out index 697a80fe0..3d767fbe4 100644 --- a/test_regress/t/t_cdc_async_bad.out +++ b/test_regress/t/t_cdc_async_bad.out @@ -1,14 +1,14 @@ %Warning-DEPRECATED: Option --cdc is deprecated and is planned for removal ... For warning description see https://verilator.org/warn/DEPRECATED?v=latest ... Use "/* verilator lint_off DEPRECATED */" and lint_on around source to disable this message. -%Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:28:21: Logic in path that feeds async reset, via signal: 't.rst2_bad_n' - 28 | wire rst2_bad_n = rst0_n | rst1_n; - | ^ -%Warning-CDCRSTLOGIC: See details in obj_vlt/t_cdc_async_bad/Vt_cdc_async_bad__cdc.txt %Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:53:21: Logic in path that feeds async reset, via signal: 't.rst6a_bad_n' 53 | wire rst6a_bad_n = rst6_bad_n ^ $c1("0"); | ^ +%Warning-CDCRSTLOGIC: See details in obj_vlt/t_cdc_async_bad/Vt_cdc_async_bad__cdc.txt %Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:54:21: Logic in path that feeds async reset, via signal: 't.rst6b_bad_n' 54 | wire rst6b_bad_n = rst6_bad_n ^ $c1("1"); | ^ +%Warning-CDCRSTLOGIC: t/t_cdc_async_bad.v:28:21: Logic in path that feeds async reset, via signal: 't.rst2_bad_n' + 28 | wire rst2_bad_n = rst0_n | rst1_n; + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_cellarray.pl b/test_regress/t/t_cellarray.pl index 710539001..fa5c2725e 100755 --- a/test_regress/t/t_cellarray.pl +++ b/test_regress/t/t_cellarray.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - v_flags2 => ["--stats"], + v_flags2 => ["--stats -fno-dfg"], ); execute( diff --git a/test_regress/t/t_const_opt.pl b/test_regress/t/t_const_opt.pl index 36f064cb4..1ee302ca7 100755 --- a/test_regress/t/t_const_opt.pl +++ b/test_regress/t/t_const_opt.pl @@ -11,7 +11,8 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - verilator_flags2 => ["-Wno-UNOPTTHREADS", "--stats", "$Self->{t_dir}/$Self->{name}.cpp"], + verilator_flags2 => ["-Wno-UNOPTTHREADS", "-fno-dfg", + "--stats", "$Self->{t_dir}/$Self->{name}.cpp"], ); execute( diff --git a/test_regress/t/t_dfg_circular.pl b/test_regress/t/t_dfg_circular.pl new file mode 100755 index 000000000..a4e59f8b5 --- /dev/null +++ b/test_regress/t/t_dfg_circular.pl @@ -0,0 +1,18 @@ +#!/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 => 1); + +compile( + verilator_flags2 => ["--dumpi-dfg 9"] + ); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_circular.v b/test_regress/t/t_dfg_circular.v new file mode 100644 index 000000000..5eb9a7049 --- /dev/null +++ b/test_regress/t/t_dfg_circular.v @@ -0,0 +1,19 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + wire a; + wire b; + + assign a = b + 1'b1; + assign b = a + 1'b1; + +endmodule diff --git a/test_regress/t/t_dfg_peephole.cpp b/test_regress/t/t_dfg_peephole.cpp new file mode 100644 index 000000000..cd2c25318 --- /dev/null +++ b/test_regress/t/t_dfg_peephole.cpp @@ -0,0 +1,37 @@ +// +// DESCRIPTION: Verilator: DFG optimzier equivalence testing +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 +// + +#include +#include + +#include +#include +#include + +int main(int, char**) { + // Create contexts + VerilatedContext ctx; + + // Create models + Vref ref{&ctx}; + Vopt opt{&ctx}; + + ref.clk = 0; + opt.clk = 0; + + while (!ctx.gotFinish()) { + ref.eval(); + opt.eval(); +#include "checks.h" + // increment time + ctx.timeInc(1); + + ref.clk = !ref.clk; + opt.clk = !opt.clk; + } +} diff --git a/test_regress/t/t_dfg_peephole.pl b/test_regress/t/t_dfg_peephole.pl new file mode 100755 index 000000000..ded92d5f5 --- /dev/null +++ b/test_regress/t/t_dfg_peephole.pl @@ -0,0 +1,126 @@ +#!/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); + +$Self->{sim_time} = 2000000; + +# Compile un-optimized +compile( + verilator_flags2 => ["--stats", "--build", "-fno-dfg", "+define+REF", + "-Mdir", "$Self->{obj_dir}/obj_ref", "--prefix", "Vref"], + verilator_make_gmake => 0, + verilator_make_cmake => 0 + ); + +# Generate the equivalence checks +my $rdFile = "$Self->{obj_dir}/obj_ref/Vref.h"; +my $wrFile = "$Self->{obj_dir}/checks.h"; +my $rfh = IO::File->new("<$rdFile") or error("$! $rdFile"); +my $wfh = IO::File->new(">$wrFile") or error("$! $wrFile"); +while (defined(my $line = $rfh->getline)) { + next if $line !~ /.*\b(dfg_[A-Z_]*)\b/; + my $signal = $1; + print $wfh "if (ref.$signal != opt.$signal) {\n"; + print $wfh " std::cout << \"Mismatched $signal\" << std::endl;\n"; + print $wfh " std::cout << \"Ref: 0x\" << std::hex << (ref.$signal + 0) << std::endl;\n"; + print $wfh " std::cout << \"Opt: 0x\" << std::hex << (opt.$signal + 0) << std::endl;\n"; + print $wfh " std::exit(1);\n"; + print $wfh "}\n"; +} +close $rdFile; +close $wrFile; + +# Compile optimized - also builds executable +compile( + verilator_flags2 => ["--stats", "--build", "--exe", + "-Mdir", "$Self->{obj_dir}/obj_opt", "--prefix", "Vopt", + "-CFLAGS \"-I .. -I ../obj_ref\"", + "../obj_ref/Vref__ALL.a", + "../../t/$Self->{name}.cpp"], + verilator_make_gmake => 0, + verilator_make_cmake => 0 + ); + +# Execute test to check equivalence +execute( + executable => "$Self->{obj_dir}/obj_opt/Vopt", + check_finished => 1, + ); + +sub check { + my $name = shift; + $name = lc $name; + $name =~ s/_/ /g; + file_grep("$Self->{obj_dir}/obj_opt/Vopt__stats.txt", qr/DFG\s+(pre|post) inline Peephole, ${name}\s+([1-9]\d*)/i); +} + +# Check optimizations +check("SWAP_CONST_IN_COMMUTATIVE_BINARY"); +check("SWAP_NOT_IN_COMMUTATIVE_BINARY"); +check("SWAP_VAR_IN_COMMUTATIVE_BINARY"); +check("PUSH_BITWISE_OP_THROUGH_CONCAT"); +check("PUSH_COMPARE_OP_THROUGH_CONCAT"); +#check("REMOVE_WIDTH_ONE_REDUCTION"); V3Const eats this +check("PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH"); +check("REPLACE_REDUCTION_OF_CONST"); +check("REPLACE_EXTEND"); +check("PUSH_NOT_THROUGH_COND"); +check("REMOVE_NOT_NOT"); +check("REPLACE_NOT_NEQ"); +check("REPLACE_NOT_OF_CONST"); +check("REPLACE_AND_OF_NOT_AND_NOT"); +check("REPLACE_AND_OF_CONST_AND_CONST"); +check("REPLACE_AND_WITH_ZERO"); +check("REMOVE_AND_WITH_ONES"); +check("REPLACE_CONTRADICTORY_AND"); +check("REPLACE_OR_OF_NOT_AND_NOT"); +check("REPLACE_OR_OF_NOT_AND_NEQ"); +check("REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO"); +check("REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS"); +check("REPLACE_OR_OF_CONST_AND_CONST"); +check("REMOVE_OR_WITH_ZERO"); +check("REPLACE_OR_WITH_ONES"); +check("REPLACE_TAUTOLOGICAL_OR"); +check("REMOVE_SUB_ZERO"); +check("REPLACE_SUB_WITH_NOT"); +check("REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT"); +check("REPLACE_EQ_OF_CONST_AND_CONST"); +check("REMOVE_FULL_WIDTH_SEL"); +check("REMOVE_SEL_FROM_RHS_OF_CONCAT"); +check("REMOVE_SEL_FROM_LHS_OF_CONCAT"); +check("PUSH_SEL_THROUGH_CONCAT"); +check("PUSH_SEL_THROUGH_REPLICATE"); +check("PUSH_SEL_THROUGH_NOT"); +check("REPLACE_SEL_FROM_SEL"); +check("PUSH_SEL_THROUGH_COND"); +check("PUSH_SEL_THROUGH_SHIFTL"); +check("REPLACE_SEL_FROM_CONST"); +check("REPLACE_CONCAT_OF_CONSTS"); +check("REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS"); +check("REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS"); +check("REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR"); +check("REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL"); +check("PUSH_CONCAT_THROUGH_NOTS"); +check("REMOVE_CONCAT_OF_ADJOINING_SELS"); +check("REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS"); +check("REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS"); +check("REMOVE_COND_WITH_FALSE_CONDITION"); +check("REMOVE_COND_WITH_TRUE_CONDITION"); +check("SWAP_COND_WITH_NOT_CONDITION"); +check("SWAP_COND_WITH_NEQ_CONDITION"); +check("PULL_NOTS_THROUGH_COND"); +check("REPLACE_COND_WITH_THEN_BRANCH_ZERO"); +check("REPLACE_COND_WITH_THEN_BRANCH_ONES"); +check("REPLACE_COND_WITH_ELSE_BRANCH_ZERO"); +check("REPLACE_COND_WITH_ELSE_BRANCH_ONES"); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v new file mode 100644 index 000000000..e5609c9c4 --- /dev/null +++ b/test_regress/t/t_dfg_peephole.v @@ -0,0 +1,227 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +`define STRINGIFY(x) `"x`" + +`define signal(name, expr) wire [$bits(expr)-1:0] dfg_``name = expr; + + +module t (/*AUTOARG*/ + // Outputs + dfg_SWAP_CONST_IN_COMMUTATIVE_BINARY, + dfg_SWAP_NOT_IN_COMMUTATIVE_BINARY, + dfg_SWAP_VAR_IN_COMMUTATIVE_BINARY, + dfg_PUSH_BITWISE_OP_THROUGH_CONCAT, + dfg_PUSH_BITWISE_OP_THROUGH_CONCAT_2, + dfg_PUSH_COMPARE_OP_THROUGH_CONCAT, dfg_REMOVE_WIDTH_ONE_REDUCTION, + dfg_PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH, + dfg_REPLACE_REDUCTION_OF_CONST_AND, + dfg_REPLACE_REDUCTION_OF_CONST_OR, + dfg_REPLACE_REDUCTION_OF_CONST_XOR, dfg_REPLACE_EXTEND, + dfg_PUSH_NOT_THROUGH_COND, dfg_REMOVE_NOT_NOT, dfg_REPLACE_NOT_NEQ, + dfg_REPLACE_NOT_OF_CONST, dfg_REPLACE_AND_OF_NOT_AND_NOT, + dfg_REPLACE_AND_OF_CONST_AND_CONST, dfg_REPLACE_AND_WITH_ZERO, + dfg_REMOVE_AND_WITH_ONES, dfg_REPLACE_CONTRADICTORY_AND, + dfg_REPLACE_OR_OF_NOT_AND_NOT, dfg_REPLACE_OR_OF_NOT_AND_NEQ, + dfg_REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO, + dfg_REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS, + dfg_REPLACE_OR_OF_CONST_AND_CONST, dfg_REMOVE_OR_WITH_ZERO, + dfg_REPLACE_OR_WITH_ONES, dfg_REPLACE_TAUTOLOGICAL_OR, + dfg_REMOVE_SUB_ZERO, dfg_REPLACE_SUB_WITH_NOT, + dfg_REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT, + dfg_REPLACE_EQ_OF_CONST_AND_CONST, dfg_REMOVE_FULL_WIDTH_SEL, + dfg_REMOVE_SEL_FROM_RHS_OF_CONCAT, + dfg_REMOVE_SEL_FROM_LHS_OF_CONCAT, dfg_PUSH_SEL_THROUGH_CONCAT, + dfg_PUSH_SEL_THROUGH_REPLICATE, dfg_REPLACE_SEL_FROM_CONST, + dfg_REPLACE_CONCAT_OF_CONSTS, + dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS, + dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS, + dfg_REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR, + dfg_REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL, + dfg_PUSH_CONCAT_THROUGH_NOTS, dfg_REMOVE_CONCAT_OF_ADJOINING_SELS, + dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS, + dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS, + dfg_REMOVE_COND_WITH_FALSE_CONDITION, + dfg_REMOVE_COND_WITH_TRUE_CONDITION, + dfg_SWAP_COND_WITH_NOT_CONDITION, dfg_SWAP_COND_WITH_NEQ_CONDITION, + dfg_PULL_NOTS_THROUGH_COND, dfg_REPLACE_COND_WITH_THEN_BRANCH_ZERO, + dfg_REPLACE_COND_WITH_THEN_BRANCH_ONES, + dfg_REPLACE_COND_WITH_ELSE_BRANCH_ZERO, + dfg_REPLACE_COND_WITH_ELSE_BRANCH_ONES, dfg_PUSH_SEL_THROUGH_COND, + dfg_PUSH_SEL_THROUGH_SHIFTL, dfg_REPLACE_SEL_FROM_SEL, + // Inputs + clk + ); + input clk; + + // Sadly verilog-mode cannot look in macros so need to define these + // separately + output dfg_SWAP_CONST_IN_COMMUTATIVE_BINARY; + output dfg_SWAP_NOT_IN_COMMUTATIVE_BINARY; + output dfg_SWAP_VAR_IN_COMMUTATIVE_BINARY; + output dfg_PUSH_BITWISE_OP_THROUGH_CONCAT; + output dfg_PUSH_BITWISE_OP_THROUGH_CONCAT_2; + output dfg_PUSH_COMPARE_OP_THROUGH_CONCAT; + output dfg_REMOVE_WIDTH_ONE_REDUCTION; + output dfg_PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH; + output dfg_REPLACE_REDUCTION_OF_CONST_AND; + output dfg_REPLACE_REDUCTION_OF_CONST_OR; + output dfg_REPLACE_REDUCTION_OF_CONST_XOR; + output dfg_REPLACE_EXTEND; + output dfg_PUSH_NOT_THROUGH_COND; + output dfg_REMOVE_NOT_NOT; + output dfg_REPLACE_NOT_NEQ; + output dfg_REPLACE_NOT_OF_CONST; + output dfg_REPLACE_AND_OF_NOT_AND_NOT; + output dfg_REPLACE_AND_OF_CONST_AND_CONST; + output dfg_REPLACE_AND_WITH_ZERO; + output dfg_REMOVE_AND_WITH_ONES; + output dfg_REPLACE_CONTRADICTORY_AND; + output dfg_REPLACE_OR_OF_NOT_AND_NOT; + output dfg_REPLACE_OR_OF_NOT_AND_NEQ; + output dfg_REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO; + output dfg_REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS; + output dfg_REPLACE_OR_OF_CONST_AND_CONST; + output dfg_REMOVE_OR_WITH_ZERO; + output dfg_REPLACE_OR_WITH_ONES; + output dfg_REPLACE_TAUTOLOGICAL_OR; + output dfg_REMOVE_SUB_ZERO; + output dfg_REPLACE_SUB_WITH_NOT; + output dfg_REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT; + output dfg_REPLACE_EQ_OF_CONST_AND_CONST; + output dfg_REMOVE_FULL_WIDTH_SEL; + output dfg_REMOVE_SEL_FROM_RHS_OF_CONCAT; + output dfg_REMOVE_SEL_FROM_LHS_OF_CONCAT; + output dfg_PUSH_SEL_THROUGH_CONCAT; + output dfg_PUSH_SEL_THROUGH_REPLICATE; + output dfg_REPLACE_SEL_FROM_CONST; + output dfg_REPLACE_CONCAT_OF_CONSTS; + output dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS; + output dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS; + output dfg_REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR; + output dfg_REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL; + output dfg_PUSH_CONCAT_THROUGH_NOTS; + output dfg_REMOVE_CONCAT_OF_ADJOINING_SELS; + output dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS; + output dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS; + output dfg_REMOVE_COND_WITH_FALSE_CONDITION; + output dfg_REMOVE_COND_WITH_TRUE_CONDITION; + output dfg_SWAP_COND_WITH_NOT_CONDITION; + output dfg_SWAP_COND_WITH_NEQ_CONDITION; + output dfg_PULL_NOTS_THROUGH_COND; + output dfg_REPLACE_COND_WITH_THEN_BRANCH_ZERO; + output dfg_REPLACE_COND_WITH_THEN_BRANCH_ONES; + output dfg_REPLACE_COND_WITH_ELSE_BRANCH_ZERO; + output dfg_REPLACE_COND_WITH_ELSE_BRANCH_ONES; + output dfg_PUSH_SEL_THROUGH_COND; + output dfg_PUSH_SEL_THROUGH_SHIFTL; + output dfg_REPLACE_SEL_FROM_SEL; + + integer cyc = 0; + + reg [63:0] crc = 64'h5aef0c8d_d70a4497; + reg [63:0] rcr; + wire logic [127:0] rcr_crc = {rcr, crc}; + wire logic [127:0] crc_rep = {2{crc}}; + wire logic [63:0] const_a; + wire logic [63:0] const_b; + + always @ (posedge clk) begin + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; + rcr <= ~crc; + +`ifdef REF + if (cyc >= 100_000) begin + $write("*-* All Finished *-*\n"); + $finish; + end +`endif + end + + // 64'0 but don't tell V3Const +`define ZERO (const_a & ~const_a) + // 64'1 but don't tell V3Const +`define ONES (const_a | ~const_a) + // x, but in a way only DFG understands +`define DFG(x) ((|`ONES) ? (x) : (~x)) + + `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, crc + const_a); + `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, crc + ~crc); + `signal(SWAP_VAR_IN_COMMUTATIVE_BINARY, rcr + crc); + `signal(PUSH_BITWISE_OP_THROUGH_CONCAT, 32'h12345678 ^ {8'h0, crc[23:0]}); + `signal(PUSH_BITWISE_OP_THROUGH_CONCAT_2, 32'h12345678 ^ {rcr[7:0], crc[23:0]}); + `signal(PUSH_COMPARE_OP_THROUGH_CONCAT, 4'b1011 == {2'b10, crc[1:0]}); + `signal(REMOVE_WIDTH_ONE_REDUCTION, &`DFG(crc[0])); + `signal(PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH, |(crc[32] ? crc[3:0] : 4'h0)); + `signal(REPLACE_REDUCTION_OF_CONST_AND, &const_a); + `signal(REPLACE_REDUCTION_OF_CONST_OR, |const_a); + `signal(REPLACE_REDUCTION_OF_CONST_XOR, ^const_a); + `signal(REPLACE_EXTEND, 4'(crc[0])); + `signal(PUSH_NOT_THROUGH_COND, ~(crc[0] ? crc[4:0] : 5'hb)); + `signal(REMOVE_NOT_NOT, ~`DFG(~`DFG(crc))); + `signal(REPLACE_NOT_NEQ, ~`DFG(crc != rcr)); + `signal(REPLACE_NOT_OF_CONST, ~4'd0); + `signal(REPLACE_AND_OF_NOT_AND_NOT, ~crc[0] & ~rcr[0]); + `signal(REPLACE_AND_OF_CONST_AND_CONST, const_a & const_b); + `signal(REPLACE_AND_WITH_ZERO, `ZERO & crc); + `signal(REMOVE_AND_WITH_ONES, `ONES & crc); + `signal(REPLACE_CONTRADICTORY_AND, crc & ~crc); + `signal(REPLACE_OR_OF_NOT_AND_NOT, ~crc[0] | ~rcr[0]); + `signal(REPLACE_OR_OF_NOT_AND_NEQ, ~crc[0] | (rcr != 64'd2)); + `signal(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO, {2'd0, crc[1:0]} | {rcr[1:0], 2'd0}); + `signal(REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS, {crc[1:0], 2'd0} | {2'd0, rcr[1:0]}); + `signal(REPLACE_OR_OF_CONST_AND_CONST, const_a | const_b); + `signal(REMOVE_OR_WITH_ZERO, `ZERO | crc); + `signal(REPLACE_OR_WITH_ONES, `ONES | crc); + `signal(REPLACE_TAUTOLOGICAL_OR, crc | ~crc); + `signal(REMOVE_SUB_ZERO, crc - `ZERO); + `signal(REPLACE_SUB_WITH_NOT, crc[0] - 1'b1); + `signal(REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT, crc << {2'b0, crc[2:0]}); + `signal(REPLACE_EQ_OF_CONST_AND_CONST, 4'd0 == 4'd1); + `signal(REMOVE_FULL_WIDTH_SEL, crc[63:0]); + `signal(REMOVE_SEL_FROM_RHS_OF_CONCAT, rcr_crc[63:0]); + `signal(REMOVE_SEL_FROM_LHS_OF_CONCAT, rcr_crc[127:64]); + `signal(PUSH_SEL_THROUGH_CONCAT, rcr_crc[120:0]); + `signal(PUSH_SEL_THROUGH_REPLICATE, crc_rep[0]); + `signal(REPLACE_SEL_FROM_CONST, const_a[2]); + `signal(REPLACE_CONCAT_OF_CONSTS, {const_a, const_b}); + `signal(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS, {`DFG({crc, const_a}), const_b}); + `signal(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS, {const_a, `DFG({const_b, crc})}); + `signal(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR, {62'd0, crc[63:62]}); + `signal(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL, {crc[1:0], 62'd0}); + `signal(PUSH_CONCAT_THROUGH_NOTS, {~crc, ~rcr} ); + `signal(REMOVE_CONCAT_OF_ADJOINING_SELS, {`DFG(crc[10:3]), `DFG(crc[2:1])}); + `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS, {crc[10:3], {crc[2:1], rcr}}); + `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS, {`DFG({rcr, crc[10:3]}), crc[2:1]}); + `signal(REMOVE_COND_WITH_FALSE_CONDITION, &`ZERO ? crc : rcr); + `signal(REMOVE_COND_WITH_TRUE_CONDITION, |`ONES ? crc : rcr); + `signal(SWAP_COND_WITH_NOT_CONDITION, (~crc[0] & |`ONES) ? crc : rcr); + `signal(SWAP_COND_WITH_NEQ_CONDITION, rcr != crc ? crc : rcr); + `signal(PULL_NOTS_THROUGH_COND, crc[0] ? ~crc[4:0] : ~rcr[4:0]); + `signal(REPLACE_COND_WITH_THEN_BRANCH_ZERO, crc[0] ? |`ZERO : crc[1]); + `signal(REPLACE_COND_WITH_THEN_BRANCH_ONES, crc[0] ? |`ONES : crc[1]); + `signal(REPLACE_COND_WITH_ELSE_BRANCH_ZERO, crc[0] ? crc[1] : |`ZERO); + `signal(REPLACE_COND_WITH_ELSE_BRANCH_ONES, crc[0] ? crc[1] : |`ONES); + + assign const_a = (crc | ~crc) & 64'h0123456789abcdef; + assign const_b = ~(crc & ~crc) & 64'h98badefc10325647; + + // Some selects need extra temporaries + wire [63:0] sel_from_cond = crc[0] ? crc : const_a; + wire [63:0] sel_from_shiftl = crc << 10; + wire [31:0] sel_from_sel = crc[10+:32]; + + `signal(PUSH_SEL_THROUGH_COND, sel_from_cond[2]); + `signal(PUSH_SEL_THROUGH_SHIFTL, sel_from_shiftl[20:0]); + `signal(REPLACE_SEL_FROM_SEL, sel_from_sel[4:3]); + + // Sel from not requires the operand to have a sinle sink, so can't use + // the chekc due to the raw expression referencing the operand + wire [63:0] sel_from_not_tmp = ~(crc >> rcr[2:0] << crc[3:0]); + wire sel_from_not = sel_from_not_tmp[2]; + always @(posedge clk) if ($c(0)) $display(sel_from_not); // Do not remove signal +endmodule diff --git a/test_regress/t/t_dfg_unhandled.pl b/test_regress/t/t_dfg_unhandled.pl new file mode 100755 index 000000000..6364d038e --- /dev/null +++ b/test_regress/t/t_dfg_unhandled.pl @@ -0,0 +1,20 @@ +#!/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 => 1); + +compile( + verilator_flags2 => ["--stats"], + ); + +file_grep($Self->{stats}, qr/Optimizations, DFG pre inline Ast2Dfg, non-representable \(impure\)\s+(\d+)/i, 1); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_unhandled.v b/test_regress/t/t_dfg_unhandled.v new file mode 100644 index 000000000..f01edfeff --- /dev/null +++ b/test_regress/t/t_dfg_unhandled.v @@ -0,0 +1,16 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input wire clk, + output wire [31:0] o0 + ); + + int file; + + assign o0 = $fgetc(file); // Impure + +endmodule diff --git a/test_regress/t/t_dump_dfg.pl b/test_regress/t/t_dump_dfg.pl new file mode 100755 index 000000000..2164a894c --- /dev/null +++ b/test_regress/t/t_dump_dfg.pl @@ -0,0 +1,21 @@ +#!/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 => 1); + +# For code coverage of graph dumping, so does not matter much what the input is +top_filename("t/t_bench_mux4k.v"); + +compile( + verilator_flags2 => ["--dump-dfg", "--dumpi-dfg 9"], + ); + +ok(1); +1; diff --git a/test_regress/t/t_flag_expand_limit.pl b/test_regress/t/t_flag_expand_limit.pl index 6270f76ca..60ff7ed44 100755 --- a/test_regress/t/t_flag_expand_limit.pl +++ b/test_regress/t/t_flag_expand_limit.pl @@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); compile( - verilator_flags2 => ['--expand-limit 1 --stats'], + verilator_flags2 => ['--expand-limit 1 --stats -fno-dfg'], ); file_grep($Self->{stats}, qr/Optimizations, expand limited\s+(\d+)/i, 4); diff --git a/test_regress/t/t_gate_chained.pl b/test_regress/t/t_gate_chained.pl index ac9dd39d1..ebaff169c 100755 --- a/test_regress/t/t_gate_chained.pl +++ b/test_regress/t/t_gate_chained.pl @@ -46,7 +46,7 @@ gen($Self->{top_filename}); compile( verilator_flags2 => ["--stats --x-assign fast --x-initial fast", - "-Wno-UNOPTTHREADS"], + "-Wno-UNOPTTHREADS -fno-dfg"], ); execute( diff --git a/test_regress/t/t_gate_ormux.pl b/test_regress/t/t_gate_ormux.pl index 02476bffd..3f5118673 100755 --- a/test_regress/t/t_gate_ormux.pl +++ b/test_regress/t/t_gate_ormux.pl @@ -15,7 +15,7 @@ $Self->{sim_time} = $Self->{cycles} * 10 + 1000; compile( v_flags2 => ["+define+SIM_CYCLES=$Self->{cycles}",], - verilator_flags2 => ["-Wno-UNOPTTHREADS", "--stats"], + verilator_flags2 => ["-Wno-UNOPTTHREADS", "--stats", "-fno-dfg"], ); if ($Self->{vlt}) { diff --git a/test_regress/t/t_inst_tree_inl1_pub0.pl b/test_regress/t/t_inst_tree_inl1_pub0.pl index 9e8e50970..b8f738b3e 100755 --- a/test_regress/t/t_inst_tree_inl1_pub0.pl +++ b/test_regress/t/t_inst_tree_inl1_pub0.pl @@ -14,7 +14,7 @@ top_filename("t/t_inst_tree.v"); my $out_filename = "$Self->{obj_dir}/V$Self->{name}.xml"; compile( - v_flags2 => ["$Self->{t_dir}/t_inst_tree_inl1_pub0.vlt"], + v_flags2 => ["-fno-dfg-post-inline", "$Self->{t_dir}/t_inst_tree_inl1_pub0.vlt"], ); if ($Self->{vlt_all}) { diff --git a/test_regress/t/t_inst_tree_inl1_pub1.pl b/test_regress/t/t_inst_tree_inl1_pub1.pl index 2c8ff9ac9..4240d8339 100755 --- a/test_regress/t/t_inst_tree_inl1_pub1.pl +++ b/test_regress/t/t_inst_tree_inl1_pub1.pl @@ -14,7 +14,7 @@ top_filename("t/t_inst_tree.v"); my $out_filename = "$Self->{obj_dir}/V$Self->{name}.xml"; compile( - v_flags2 => ["t/$Self->{name}.vlt", + v_flags2 => ["-fno-dfg-post-inline", "t/$Self->{name}.vlt", $Self->wno_unopthreads_for_few_cores()] ); diff --git a/test_regress/t/t_math_wide_bad.out b/test_regress/t/t_math_wide_bad.out index ec2179c13..b3c120d82 100644 --- a/test_regress/t/t_math_wide_bad.out +++ b/test_regress/t/t_math_wide_bad.out @@ -1,10 +1,10 @@ -%Error-UNSUPPORTED: t/t_math_wide_bad.v:22:18: Unsupported: operator POWSS operator of 576 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h - 22 | assign z2 = a ** 3; - | ^~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error-UNSUPPORTED: t/t_math_wide_bad.v:23:15: Unsupported: operator ISTORD operator of 64 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h 23 | assign r = real'(a); | ^~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_math_wide_bad.v:22:18: Unsupported: operator POWSS operator of 576 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h + 22 | assign z2 = a ** 3; + | ^~ %Error-UNSUPPORTED: t/t_math_wide_bad.v:21:17: Unsupported: operator MULS operator of 576 bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h 21 | assign z = a * b; | ^ diff --git a/test_regress/t/t_order_wireloop.pl b/test_regress/t/t_order_wireloop.pl index 9eef881b4..41a50e716 100755 --- a/test_regress/t/t_order_wireloop.pl +++ b/test_regress/t/t_order_wireloop.pl @@ -16,7 +16,7 @@ compile( # However we no longer gate optimize this # Can't use expect_filename here as unstable output expect => -'%Warning-UNOPTFLAT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Circular combinational logic: \'t.foo\' +'%Warning-UNOPTFLAT: t/t_order_wireloop.v:\d+:\d+: Signal unoptimizable: Circular combinational logic: \'bar\' ', ); diff --git a/test_regress/t/t_var_escape.out b/test_regress/t/t_var_escape.out index a4bcf2e13..3cf2f0549 100644 --- a/test_regress/t/t_var_escape.out +++ b/test_regress/t/t_var_escape.out @@ -1,32 +1,31 @@ $version Generated by VerilatedVcd $end -$date Tue Jul 24 18:44:43 2012 - $end +$date Thu Sep 22 13:02:07 2022 $end $timescale 1ps $end $scope module top $end - $var wire 1 * 9num $end - $var wire 1 + bra[ket]slash/dash-colon:9backslash\done $end - $var wire 1 ' clk $end - $var wire 1 ) double__underscore $end - $var wire 1 ( escaped_normal $end + $var wire 1 & 9num $end + $var wire 1 ' bra[ket]slash/dash-colon:9backslash\done $end + $var wire 1 # clk $end + $var wire 1 % double__underscore $end + $var wire 1 $ escaped_normal $end $scope module t $end - $var wire 1 * 9num $end - $var wire 32 & a0.cyc [31:0] $end - $var wire 1 + bra[ket]slash/dash-colon:9backslash\done $end + $var wire 1 $ 9num $end + $var wire 32 * a0.cyc [31:0] $end + $var wire 1 $ bra[ket]slash/dash-colon:9backslash\done $end $var wire 1 $ check:alias $end - $var wire 1 % check;alias $end + $var wire 1 ) check;alias $end $var wire 1 $ check_alias $end - $var wire 1 ' clk $end - $var wire 32 # cyc [31:0] $end - $var wire 1 ) double__underscore $end - $var wire 1 ( escaped_normal $end - $var wire 32 & other.cyc [31:0] $end + $var wire 1 # clk $end + $var wire 32 ( cyc [31:0] $end + $var wire 1 $ double__underscore $end + $var wire 1 $ escaped_normal $end + $var wire 32 * other.cyc [31:0] $end $var wire 1 $ wire $end $scope module a0 $end - $var wire 32 # cyc [31:0] $end + $var wire 32 ( cyc [31:0] $end $upscope $end $scope module mod.with_dot $end - $var wire 32 # cyc [31:0] $end + $var wire 32 ( cyc [31:0] $end $upscope $end $upscope $end $upscope $end @@ -34,130 +33,119 @@ $enddefinitions $end #0 +0# 1$ -0% -b11111111111111111111111111111110 & -b00000000000000000000000000000001 # -0' -1( -1) -1* -1+ +1% +1& +1' +b00000000000000000000000000000001 ( +0) +b11111111111111111111111111111110 * #10 +1# 0$ -1% -b11111111111111111111111111111101 & -b00000000000000000000000000000010 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000000010 ( +1) +b11111111111111111111111111111101 * #15 -0' +0# #20 +1# 1$ -0% -b11111111111111111111111111111100 & -b00000000000000000000000000000011 # +1% +1& 1' -1( -1) -1* -1+ +b00000000000000000000000000000011 ( +0) +b11111111111111111111111111111100 * #25 -0' +0# #30 +1# 0$ -1% -b11111111111111111111111111111011 & -b00000000000000000000000000000100 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000000100 ( +1) +b11111111111111111111111111111011 * #35 -0' +0# #40 +1# 1$ -0% -b11111111111111111111111111111010 & -b00000000000000000000000000000101 # +1% +1& 1' -1( -1) -1* -1+ +b00000000000000000000000000000101 ( +0) +b11111111111111111111111111111010 * #45 -0' +0# #50 +1# 0$ -1% -b11111111111111111111111111111001 & -b00000000000000000000000000000110 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000000110 ( +1) +b11111111111111111111111111111001 * #55 -0' +0# #60 +1# 1$ -0% -b11111111111111111111111111111000 & -b00000000000000000000000000000111 # +1% +1& 1' -1( -1) -1* -1+ +b00000000000000000000000000000111 ( +0) +b11111111111111111111111111111000 * #65 -0' +0# #70 +1# 0$ -1% -b11111111111111111111111111110111 & -b00000000000000000000000000001000 # -1' -0( -0) -0* -0+ +0% +0& +0' +b00000000000000000000000000001000 ( +1) +b11111111111111111111111111110111 * #75 -0' +0# #80 +1# 1$ -0% -b11111111111111111111111111110110 & -b00000000000000000000000000001001 # -1' -1( -1) -1* -1+ -#85 -0' -#90 -0$ 1% -b11111111111111111111111111110101 & -b00000000000000000000000000001010 # +1& 1' -0( +b00000000000000000000000000001001 ( 0) -0* -0+ -#95 -0' -#100 -1$ +b11111111111111111111111111110110 * +#85 +0# +#90 +1# +0$ 0% -b11111111111111111111111111110100 & -b00000000000000000000000000001011 # -1' -1( +0& +0' +b00000000000000000000000000001010 ( 1) -1* -1+ +b11111111111111111111111111110101 * +#95 +0# +#100 +1# +1$ +1% +1& +1' +b00000000000000000000000000001011 ( +0) +b11111111111111111111111111110100 * diff --git a/test_regress/t/t_xml_first.out b/test_regress/t/t_xml_first.out index 11cff436a..1056865dc 100644 --- a/test_regress/t/t_xml_first.out +++ b/test_regress/t/t_xml_first.out @@ -50,8 +50,8 @@ - - + + diff --git a/test_regress/t/t_xml_flat.out b/test_regress/t/t_xml_flat.out index bc2e4f9b8..d7ec257c2 100644 --- a/test_regress/t/t_xml_flat.out +++ b/test_regress/t/t_xml_flat.out @@ -99,8 +99,8 @@ - - + + From 9da012568c3a1dfe9625f5beb00a52b49fc754c5 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 26 Sep 2022 14:21:05 +0100 Subject: [PATCH 049/177] Ensure DFG stats are consistent --- src/V3DfgAstToDfg.cpp | 7 ++++++- src/V3DfgPasses.cpp | 7 +++++++ src/V3DfgPasses.h | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index bae5e7a33..251561bca 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -85,6 +85,7 @@ class AstToDfgVisitor final : public VNVisitor { V3DfgOptimizationContext& m_ctx; // The optimization context for stats bool m_foundUnhandled = false; // Found node not implemented as DFG or not implemented 'visit' std::vector m_uncommittedVertices; // Vertices that we might decide to revert + bool m_converting = false; // We are trying to convert some logic at the moment // METHODS void markReferenced(AstNode* nodep) { @@ -141,8 +142,8 @@ class AstToDfgVisitor final : public VNVisitor { // VISITORS void visit(AstNode* nodep) override { // Conservatively treat this node as unhandled + if (!m_foundUnhandled && m_converting) ++m_ctx.m_nonRepUnknown; m_foundUnhandled = true; - ++m_ctx.m_nonRepUnknown; markReferenced(nodep); } void visit(AstCell* nodep) override { markReferenced(nodep); } @@ -158,6 +159,10 @@ class AstToDfgVisitor final : public VNVisitor { } void visit(AstAssignW* nodep) override { + VL_RESTORER(m_converting); + m_converting = true; + ++m_ctx.m_inputEquations; + // Cannot handle assignment with timing control yet if (nodep->timingControlp()) { markReferenced(nodep); diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index a2c998025..2a164feb0 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -53,6 +53,7 @@ V3DfgOptimizationContext::V3DfgOptimizationContext(const std::string& label) V3DfgOptimizationContext::~V3DfgOptimizationContext() { const string prefix = "Optimizations, DFG " + m_label + " "; V3Stats::addStat(prefix + "General, modules", m_modules); + V3Stats::addStat(prefix + "Ast2Dfg, input equations", m_inputEquations); V3Stats::addStat(prefix + "Ast2Dfg, representable", m_representable); V3Stats::addStat(prefix + "Ast2Dfg, non-representable (dtype)", m_nonRepDType); V3Stats::addStat(prefix + "Ast2Dfg, non-representable (impure)", m_nonRepImpure); @@ -65,6 +66,12 @@ V3DfgOptimizationContext::~V3DfgOptimizationContext() { V3Stats::addStat(prefix + "Dfg2Ast, intermediate variables", m_intermediateVars); V3Stats::addStat(prefix + "Dfg2Ast, replaced variables", m_replacedVars); V3Stats::addStat(prefix + "Dfg2Ast, result equations", m_resultEquations); + + // Check the stats are consistent + UASSERT(m_inputEquations + == m_representable + m_nonRepDType + m_nonRepImpure + m_nonRepTiming + m_nonRepLhs + + m_nonRepNode + m_nonRepUnknown + m_nonRepVarRef + m_nonRepWidth, + "Inconsistent statistics"); } // 'Inline' DfgVar nodes with known drivers diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index 20adfd201..8ca377cc1 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -54,6 +54,7 @@ class V3DfgOptimizationContext final { public: VDouble0 m_modules; // Number of modules optimized + VDouble0 m_inputEquations; // Number of input combinational equations VDouble0 m_representable; // Number of combinational equations representable VDouble0 m_nonRepDType; // Equations non-representable due to data type VDouble0 m_nonRepImpure; // Equations non-representable due to impure node From 9c1cc5465de8c346d1ee847d0785fafd45c7a43d Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 26 Sep 2022 18:31:12 +0100 Subject: [PATCH 050/177] DFG: Support packed structure and union types --- src/V3Dfg.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 94fcac4ed..c0c55e5df 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -228,6 +228,9 @@ public: if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) { return isSupportedDType(typep->subDTypep()); } + if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) { + return typep->packed(); + } return false; } From 1b17acdb01b5be86363de93a5cef116a3ec6d41c Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 25 Sep 2022 16:03:15 +0100 Subject: [PATCH 051/177] DFG: Support AstSel and AstConcat on LHS of assignments Added DfgVertexVariadic to represent DFG vetices with a varying number of source operands. Converted DfgVar to be a variadic vertex, with each driver corresponding to a fixed range of bits in the packed variable. This allows us to handle AstSel on the LHS of assignments. Also added support for AstConcat on the LHS by selecting into the RHS as appropriate. This improves OpenTitan ST speed by ~13% --- src/V3Dfg.cpp | 10 +- src/V3Dfg.h | 172 +++++++++++++++++++++++++--------- src/V3DfgAstToDfg.cpp | 208 ++++++++++++++++++++++++++++++++---------- src/V3DfgDfgToAst.cpp | 138 +++++++++++++++++++--------- src/V3DfgPasses.cpp | 18 +++- src/V3DfgPasses.h | 1 + 6 files changed, 406 insertions(+), 141 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 24658be08..5350be82d 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -251,7 +251,7 @@ static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { // if (edge.sourcep()) { string headLabel; - if (vtx.arity() > 1) headLabel = std::toupper(vtx.srcName(idx)[0]); + if (vtx.arity() > 1) headLabel = vtx.srcName(idx); dumpDotEdge(os, edge, headLabel); } }); @@ -502,11 +502,15 @@ void DfgVertex::replaceWith(DfgVertex* newSorucep) { void DfgVar::accept(DfgVisitor& visitor) { visitor.visit(this); } bool DfgVar::selfEquals(const DfgVertex& that) const { - if (const DfgVar* otherp = that.cast()) return varp() == otherp->varp(); + if (const DfgVar* otherp = that.cast()) { + UASSERT_OBJ(varp() != otherp->varp() || this == otherp, this, + "There should only be one DfgVar for a given AstVar"); + return this == otherp; + } return false; } -V3Hash DfgVar::selfHash() const { return V3Hasher::uncachedHash(m_varp); } +V3Hash DfgVar::selfHash() const { return V3Hasher::uncachedHash(varp()); } // DfgConst ---------- void DfgConst::accept(DfgVisitor& visitor) { visitor.visit(this); } diff --git a/src/V3Dfg.h b/src/V3Dfg.h index c0c55e5df..aedb1c838 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -39,6 +39,7 @@ #include "V3Hasher.h" #include "V3List.h" +#include #include #include #include @@ -157,18 +158,18 @@ public: class DfgEdge final { friend class DfgVertex; - template - friend class DfgVertexWithArity; DfgEdge* m_nextp = nullptr; // Next edge in sink list DfgEdge* m_prevp = nullptr; // Previous edge in sink list DfgVertex* m_sourcep = nullptr; // The source vertex driving this edge - DfgVertex* const m_sinkp; // The sink vertex. The sink owns the edge, so immutable - - explicit DfgEdge(DfgVertex* sinkp) // The sink vertices own the edges, hence private - : m_sinkp{sinkp} {} + // Note that the sink vertex owns the edge, so it is immutable, but because we want to be able + // to allocate these as arrays, we use a default constructor + 'init' method to set m_sinkp. + DfgVertex* const m_sinkp = nullptr; // The sink vertex public: + DfgEdge() {} + void init(DfgVertex* sinkp) { const_cast(m_sinkp) = sinkp; } + // The source (driver) of this edge DfgVertex* sourcep() const { return m_sourcep; } // The sink (consumer) of this edge @@ -290,10 +291,10 @@ public: } // Source edges of this vertex - virtual std::pair sourceEdges() { return {nullptr, 0}; } + virtual std::pair sourceEdges() = 0; // Source edges of this vertex - virtual std::pair sourceEdges() const { return {nullptr, 0}; } + virtual std::pair sourceEdges() const = 0; // Arity (number of sources) of this vertex size_t arity() const { return sourceEdges().second; } @@ -429,45 +430,42 @@ template class DfgVertexWithArity VL_NOT_FINAL : public DfgVertex { static_assert(1 <= Arity && Arity <= 4, "Arity must be between 1 and 4 inclusive"); - // Uninitialized storage for source edges - typename std::aligned_storage::type - m_sourceEdges; - - constexpr DfgEdge& sourceEdge(size_t index) { - return reinterpret_cast(&m_sourceEdges)[index]; - } - constexpr const DfgEdge& sourceEdge(size_t index) const { - return reinterpret_cast(&m_sourceEdges)[index]; - } + std::array m_srcs; // Source edges protected: DfgVertexWithArity(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type) : DfgVertex{dfg, flp, dtypep, type} { // Initialize source edges - for (size_t i = 0; i < Arity; ++i) new (&sourceEdge(i)) DfgEdge{this}; + for (size_t i = 0; i < Arity; ++i) m_srcs[i].init(this); } - virtual ~DfgVertexWithArity() = default; + ~DfgVertexWithArity() override = default; public: std::pair sourceEdges() override { // - return {&sourceEdge(0), Arity}; + return {m_srcs.data(), Arity}; } std::pair sourceEdges() const override { - return {&sourceEdge(0), Arity}; + return {m_srcs.data(), Arity}; + } + + template + DfgEdge* sourceEdge() { + static_assert(Index < Arity, "Source index out of range"); + return &m_srcs[Index]; } template DfgVertex* source() const { static_assert(Index < Arity, "Source index out of range"); - return sourceEdge(Index).m_sourcep; + return m_srcs[Index].sourcep(); } template void relinkSource(DfgVertex* newSourcep) { static_assert(Index < Arity, "Source index out of range"); - UASSERT_OBJ(sourceEdge(Index).m_sinkp == this, this, "Inconsistent"); - sourceEdge(Index).relinkSource(newSourcep); + UASSERT_OBJ(m_srcs[Index].sinkp() == this, this, "Inconsistent"); + m_srcs[Index].relinkSource(newSourcep); } // Named source getter/setter for unary vertices @@ -506,41 +504,89 @@ public: } }; +class DfgVertexVariadic VL_NOT_FINAL : public DfgVertex { + DfgEdge* m_srcsp; // The source edges + uint32_t m_srcCnt = 0; // Number of sources used + uint32_t m_srcCap; // Number of sources allocated + + // Allocate a new source edge array + DfgEdge* allocSources(size_t n) { + DfgEdge* const srcsp = new DfgEdge[n]; + for (size_t i = 0; i < n; ++i) srcsp[i].init(this); + return srcsp; + } + + // Double the capacity of m_srcsp + void growSources() { + m_srcCap *= 2; + DfgEdge* const newsp = allocSources(m_srcCap); + for (size_t i = 0; i < m_srcCnt; ++i) { + DfgEdge* const oldp = m_srcsp + i; + // Skip over unlinked source edge + if (!oldp->sourcep()) continue; + // New edge driven from the same vertex as the old edge + newsp[i].relinkSource(oldp->sourcep()); + // Unlink the old edge, it will be deleted + oldp->unlinkSource(); + } + // Delete old source edges + delete[] m_srcsp; + // Keep hold of new source edges + m_srcsp = newsp; + } + +protected: + DfgVertexVariadic(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type, + uint32_t initialCapacity = 1) + : DfgVertex{dfg, flp, dtypep, type} + , m_srcsp{allocSources(initialCapacity)} + , m_srcCap{initialCapacity} {} + + ~DfgVertexVariadic() override { delete[] m_srcsp; }; + + DfgEdge* addSource() { + if (m_srcCnt == m_srcCap) growSources(); + return m_srcsp + m_srcCnt++; + } + + void resetSources() { + // #ifdef VL_DEBUG TODO: DEBUG ONLY + for (uint32_t i = 0; i < m_srcCnt; ++i) { + UASSERT_OBJ(!m_srcsp[i].sourcep(), m_srcsp[i].sourcep(), "Connected source"); + } + // #endif + m_srcCnt = 0; + } + +public: + DfgEdge* sourceEdge(size_t idx) const { return &m_srcsp[idx]; } + DfgVertex* source(size_t idx) const { return m_srcsp[idx].sourcep(); } + + std::pair sourceEdges() override { return {m_srcsp, m_srcCnt}; } + std::pair sourceEdges() const override { return {m_srcsp, m_srcCnt}; } +}; + //------------------------------------------------------------------------------ // Vertex classes //------------------------------------------------------------------------------ -class DfgVar final : public DfgVertexWithArity<1> { - friend class DfgVertex; - friend class DfgVisitor; - +class DfgVertexLValue VL_NOT_FINAL : public DfgVertexVariadic { AstVar* const m_varp; // The AstVar associated with this vertex (not owned by this vertex) - FileLine* m_assignmentFlp; // The FileLine of the original assignment driving this var bool m_hasModRefs = false; // This AstVar is referenced outside the DFG, but in the module bool m_hasExtRefs = false; // This AstVar is referenced from outside the module - void accept(DfgVisitor& visitor) override; - bool selfEquals(const DfgVertex& that) const override; - V3Hash selfHash() const override; - static constexpr DfgType dfgType() { return DfgType::atVar; }; - public: - DfgVar(DfgGraph& dfg, AstVar* varp) - : DfgVertexWithArity<1>{dfg, varp->fileline(), dtypeFor(varp), dfgType()} + DfgVertexLValue(DfgGraph& dfg, DfgType type, AstVar* varp, uint32_t initialCapacity) + : DfgVertexVariadic{dfg, varp->fileline(), dtypeFor(varp), type, initialCapacity} , m_varp{varp} {} AstVar* varp() const { return m_varp; } - FileLine* assignmentFileline() const { return m_assignmentFlp; } - void assignmentFileline(FileLine* flp) { m_assignmentFlp = flp; } bool hasModRefs() const { return m_hasModRefs; } void setHasModRefs() { m_hasModRefs = true; } bool hasExtRefs() const { return m_hasExtRefs; } void setHasExtRefs() { m_hasExtRefs = true; } bool hasRefs() const { return m_hasModRefs || m_hasExtRefs; } - DfgVertex* driverp() const { return srcp(); } - void driverp(DfgVertex* vtxp) { srcp(vtxp); } - // Variable cannot be removed, even if redundant in the DfgGraph (might be used externally) bool keep() const { // Keep if referenced outside this module @@ -552,8 +598,44 @@ public: // Otherwise it can be removed return false; } +}; - const string srcName(size_t) const override { return "driverp"; } +class DfgVar final : public DfgVertexLValue { + friend class DfgVertex; + friend class DfgVisitor; + + using DriverData = std::pair; + + std::vector m_driverData; // Additional data associate with each driver + + void accept(DfgVisitor& visitor) override; + bool selfEquals(const DfgVertex& that) const override; + V3Hash selfHash() const override; + static constexpr DfgType dfgType() { return DfgType::atVar; }; + +public: + DfgVar(DfgGraph& dfg, AstVar* varp) + : DfgVertexLValue{dfg, dfgType(), varp, 1u} {} + + bool isDrivenByDfg() const { return arity() > 0; } + bool isDrivenFullyByDfg() const { return arity() == 1 && source(0)->dtypep() == dtypep(); } + + void addDriver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp) { + m_driverData.emplace_back(flp, lsb); + DfgVertexVariadic::addSource()->relinkSource(vtxp); + } + + void resetSources() { + m_driverData.clear(); + DfgVertexVariadic::resetSources(); + } + + FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; } + uint32_t driverLsb(size_t idx) const { return m_driverData[idx].second; } + + const string srcName(size_t idx) const override { + return isDrivenFullyByDfg() ? "" : cvtToStr(driverLsb(idx)); + } }; class DfgConst final : public DfgVertex { @@ -572,7 +654,7 @@ public: : DfgVertex{dfg, constp->fileline(), dtypeFor(constp), dfgType()} , m_constp{constp} {} - ~DfgConst() { VL_DO_DANGLING(m_constp->deleteTree(), m_constp); } + ~DfgConst() override { VL_DO_DANGLING(m_constp->deleteTree(), m_constp); } AstConst* constp() const { return m_constp; } V3Number& num() const { return m_constp->num(); } @@ -583,6 +665,8 @@ public: bool isZero() const { return num().isEqZero(); } bool isOnes() const { return num().isEqAllOnes(width()); } + std::pair sourceEdges() override { return {nullptr, 0}; } + std::pair sourceEdges() const override { return {nullptr, 0}; } const string srcName(size_t) const override { // LCOV_EXCL_START VL_UNREACHABLE; return ""; diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index 251561bca..798686581 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -35,6 +35,8 @@ #include "V3Error.h" #include "V3Global.h" +#include + VL_DEFINE_DEBUG_FUNCTIONS; namespace { @@ -86,6 +88,7 @@ class AstToDfgVisitor final : public VNVisitor { bool m_foundUnhandled = false; // Found node not implemented as DFG or not implemented 'visit' std::vector m_uncommittedVertices; // Vertices that we might decide to revert bool m_converting = false; // We are trying to convert some logic at the moment + std::vector m_varps; // All the DfgVar vertices we created. // METHODS void markReferenced(AstNode* nodep) { @@ -110,7 +113,9 @@ class AstToDfgVisitor final : public VNVisitor { // multiple AstVarRef instances, so we will never revert a DfgVar once created. This // means we can end up with DfgVar vertices in the graph which have no connections at // all (which is fine for later processing). - varp->user1p(new DfgVar{*m_dfgp, varp}); + DfgVar* const vtxp = new DfgVar{*m_dfgp, varp}; + m_varps.push_back(vtxp); + varp->user1p(vtxp); } return varp->user1u().to(); } @@ -139,6 +144,95 @@ class AstToDfgVisitor final : public VNVisitor { return m_foundUnhandled; } + // Build DfgEdge representing the LValue assignment. Returns false if unsuccessful. + bool convertAssignment(FileLine* flp, AstNode* nodep, DfgVertex* vtxp) { + if (AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) { + m_foundUnhandled = false; + visit(vrefp); + if (m_foundUnhandled) return false; + getVertex(vrefp)->as()->addDriver(flp, 0, vtxp); + return true; + } + if (AstSel* const selp = VN_CAST(nodep, Sel)) { + AstVarRef* const vrefp = VN_CAST(selp->fromp(), VarRef); + AstConst* const lsbp = VN_CAST(selp->lsbp(), Const); + if (!vrefp || !lsbp || !VN_IS(selp->widthp(), Const)) { + ++m_ctx.m_nonRepLhs; + return false; + } + m_foundUnhandled = false; + visit(vrefp); + if (m_foundUnhandled) return false; + getVertex(vrefp)->as()->addDriver(flp, lsbp->toUInt(), vtxp); + return true; + } + if (AstConcat* const concatp = VN_CAST(nodep, Concat)) { + AstNode* const lhsp = concatp->lhsp(); + AstNode* const rhsp = concatp->rhsp(); + const uint32_t lWidth = lhsp->width(); + const uint32_t rWidth = rhsp->width(); + + { + FileLine* const lFlp = lhsp->fileline(); + DfgSel* const lVtxp = new DfgSel{*m_dfgp, lFlp, DfgVertex::dtypeFor(lhsp)}; + lVtxp->fromp(vtxp); + lVtxp->lsbp(new DfgConst{*m_dfgp, new AstConst{lFlp, rWidth}}); + lVtxp->widthp(new DfgConst{*m_dfgp, new AstConst{lFlp, lWidth}}); + if (!convertAssignment(flp, lhsp, lVtxp)) return false; + } + + { + FileLine* const rFlp = rhsp->fileline(); + DfgSel* const rVtxp = new DfgSel{*m_dfgp, rFlp, DfgVertex::dtypeFor(rhsp)}; + rVtxp->fromp(vtxp); + rVtxp->lsbp(new DfgConst{*m_dfgp, new AstConst{rFlp, 0u}}); + rVtxp->widthp(new DfgConst{*m_dfgp, new AstConst{rFlp, rWidth}}); + return convertAssignment(flp, rhsp, rVtxp); + } + } + ++m_ctx.m_nonRepLhs; + return false; + } + + bool convertEquation(AstNode* nodep, AstNode* lhsp, AstNode* rhsp) { + UASSERT_OBJ(m_uncommittedVertices.empty(), nodep, "Should not nest"); + + // Cannot handle mismatched widths. Mismatched assignments should have been fixed up in + // earlier passes anyway, so this should never be hit, but being paranoid just in case. + if (lhsp->width() != rhsp->width()) { // LCOV_EXCL_START + markReferenced(nodep); + ++m_ctx.m_nonRepWidth; + return false; + } // LCOV_EXCL_STOP + + VL_RESTORER(m_converting); + m_converting = true; + + m_foundUnhandled = false; + iterate(rhsp); + if (m_foundUnhandled) { + revertUncommittedVertices(); + markReferenced(nodep); + return false; + } + + if (!convertAssignment(nodep->fileline(), lhsp, getVertex(rhsp))) { + revertUncommittedVertices(); + markReferenced(nodep); + return false; + } + + // Connect the rhs vertex to the driven edge + commitVertices(); + + // Remove node from Ast. Now represented by the Dfg. + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + + // + ++m_ctx.m_representable; + return true; + } + // VISITORS void visit(AstNode* nodep) override { // Conservatively treat this node as unhandled @@ -159,8 +253,6 @@ class AstToDfgVisitor final : public VNVisitor { } void visit(AstAssignW* nodep) override { - VL_RESTORER(m_converting); - m_converting = true; ++m_ctx.m_inputEquations; // Cannot handle assignment with timing control yet @@ -170,50 +262,7 @@ class AstToDfgVisitor final : public VNVisitor { return; } - // Cannot handle mismatched widths. Mismatched assignments should have been fixed up in - // earlier passes anyway, so this should never be hit, but being paranoid just in case. - if (nodep->lhsp()->width() != nodep->rhsp()->width()) { // LCOV_EXCL_START - markReferenced(nodep); - ++m_ctx.m_nonRepWidth; - return; - } // LCOV_EXCL_START - - // Simple assignment with whole variable on left-hand side - if (AstVarRef* const vrefp = VN_CAST(nodep->lhsp(), VarRef)) { - UASSERT_OBJ(m_uncommittedVertices.empty(), nodep, "Should not nest"); - - // Build DFG vertices representing the two sides - { - m_foundUnhandled = false; - iterate(vrefp); - iterate(nodep->rhsp()); - // If this assignment contains an AstNode not representable by a DfgVertex, - // then revert the graph. - if (m_foundUnhandled) { - revertUncommittedVertices(); - markReferenced(nodep); - return; - } - } - - // Connect the vertices representing the 2 sides - DfgVar* const lVtxp = getVertex(vrefp)->as(); - DfgVertex* const rVtxp = getVertex(nodep->rhsp()); - lVtxp->driverp(rVtxp); - lVtxp->assignmentFileline(nodep->fileline()); - commitVertices(); - - // Remove assignment from Ast. Now represented by the Dfg. - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - - // - ++m_ctx.m_representable; - return; - } - - // TODO: handle complex left-hand sides - markReferenced(nodep); - ++m_ctx.m_nonRepLhs; + convertEquation(nodep, nodep->lhsp(), nodep->rhsp()); } void visit(AstVarRef* nodep) override { @@ -259,6 +308,71 @@ class AstToDfgVisitor final : public VNVisitor { // Build the DFG iterateChildren(&module); UASSERT_OBJ(m_uncommittedVertices.empty(), &module, "Uncommitted vertices remain"); + + // Canonicalize variable assignments + for (DfgVar* const varp : m_varps) { + // Gather (and unlink) all drivers + struct Driver { + FileLine* flp; + uint32_t lsb; + DfgVertex* vtxp; + Driver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp) + : flp{flp} + , lsb{lsb} + , vtxp{vtxp} {} + }; + std::vector drivers; + drivers.reserve(varp->arity()); + varp->forEachSourceEdge([varp, &drivers](DfgEdge& edge, size_t idx) { + UASSERT(edge.sourcep(), "Should not have created undriven sources"); + drivers.emplace_back(varp->driverFileLine(idx), varp->driverLsb(idx), + edge.sourcep()); + edge.unlinkSource(); + }); + + // Sort drivers by LSB + std::stable_sort(drivers.begin(), drivers.end(), + [](const Driver& a, const Driver& b) { return a.lsb < b.lsb; }); + + // TODO: bail on multidriver + + // Coalesce adjacent ranges + for (size_t i = 0, j = 1; j < drivers.size(); ++j) { + Driver& a = drivers[i]; + Driver& b = drivers[j]; + + // Coalesce adjacent range + const uint32_t aWidth = a.vtxp->width(); + const uint32_t bWidth = b.vtxp->width(); + if (a.lsb + aWidth == b.lsb) { + const auto dtypep = DfgVertex::dtypeForWidth(aWidth + bWidth); + DfgConcat* const concatp = new DfgConcat{*m_dfgp, a.flp, dtypep}; + concatp->rhsp(a.vtxp); + concatp->lhsp(b.vtxp); + a.vtxp = concatp; + b.vtxp = nullptr; // Mark as moved + ++m_ctx.m_coalescedAssignments; + continue; + } + + ++i; + + // Compact non-adjacent ranges within the vector + if (j != i) { + Driver& c = drivers[i]; + UASSERT_OBJ(!c.vtxp, c.flp, "Should have been marked moved"); + c = b; + b.vtxp = nullptr; // Mark as moved + } + } + + // Reinsert sources in order + varp->resetSources(); + for (const Driver& driver : drivers) { + if (!driver.vtxp) break; // Stop at end of cmpacted list + varp->addDriver(driver.flp, driver.lsb, driver.vtxp); + } + } } public: diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index 6084ba5be..8a0c998c9 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -127,19 +127,21 @@ class DfgToAstVisitor final : DfgVisitor { // Given a DfgVar, return the canonical AstVar that can be used for this DfgVar. // Also builds the m_canonVars map as a side effect. AstVar* getCanonicalVar(const DfgVar* vtxp) { - // Variable only read by DFG - if (!vtxp->driverp()) return vtxp->varp(); + // If variable driven (at least partially) outside the DFG, then we have no choice + if (!vtxp->isDrivenFullyByDfg()) return vtxp->varp(); // Look up map const auto it = m_canonVars.find(vtxp->varp()); if (it != m_canonVars.end()) return it->second; - // Not known yet, compute it (for all vars from the same driver) + // Not known yet, compute it (for all vars driven fully from the same driver) std::vector varps; - vtxp->driverp()->forEachSink([&](const DfgVertex& vtx) { - if (const DfgVar* const varVtxp = vtx.cast()) varps.push_back(varVtxp); + vtxp->source(0)->forEachSink([&](const DfgVertex& vtx) { + if (const DfgVar* const varVtxp = vtx.cast()) { + if (varVtxp->isDrivenFullyByDfg()) varps.push_back(varVtxp); + } }); - UASSERT_OBJ(!varps.empty(), vtxp, "The input vtxp->varp() is always available"); + UASSERT_OBJ(!varps.empty(), vtxp, "The input vtxp is always available"); std::stable_sort(varps.begin(), varps.end(), [](const DfgVar* ap, const DfgVar* bp) { if (ap->hasExtRefs() != bp->hasExtRefs()) return ap->hasExtRefs(); const FileLine& aFl = *(ap->fileline()); @@ -168,11 +170,13 @@ class DfgToAstVisitor final : DfgVisitor { if (const DfgVar* const thisDfgVarp = vtxp->cast()) { // This is a DfgVar varp = getCanonicalVar(thisDfgVarp); - } else if (const DfgVar* const sinkDfgVarp = vtxp->findSink()) { - // We found a DfgVar driven by this node + } else if (const DfgVar* const sinkDfgVarp = vtxp->findSink( + [](const DfgVar& var) { return var.isDrivenFullyByDfg(); })) { + // We found a DfgVar driven fully by this node varp = getCanonicalVar(sinkDfgVarp); } else { - // No DfgVar driven by this node. Create a temporary. + // No DfgVar driven fully by this node. Create a temporary. + // TODO: should we reuse parts when the AstVar is used as an rvalue? const string name = m_tmpNames.get(vtxp->hash(m_hashCache).toString()); // Note: It is ok for these temporary variables to be always unsigned. They are // read only by other expressions within the graph and all expressions interpret @@ -208,6 +212,62 @@ class DfgToAstVisitor final : DfgVisitor { } } + void convertCanonicalVarDriver(const DfgVar* dfgVarp) { + const auto wRef = [dfgVarp]() { + return new AstVarRef{dfgVarp->fileline(), dfgVarp->varp(), VAccess::WRITE}; + }; + if (dfgVarp->isDrivenFullyByDfg()) { + // Whole variable is driven. Render driver and assign directly to whole variable. + AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(dfgVarp->source(0)); + addResultEquation(dfgVarp->driverFileLine(0), wRef(), rhsp); + } else { + // Variable is driven partially. Render each driver as a separate assignment. + dfgVarp->forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { + UASSERT_OBJ(edge.sourcep(), dfgVarp, "Should have removed undriven sources"); + // Render the rhs expression + AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(edge.sourcep()); + // Create select LValue + FileLine* const flp = dfgVarp->driverFileLine(idx); + AstConst* const lsbp = new AstConst{flp, dfgVarp->driverLsb(idx)}; + AstConst* const widthp = new AstConst{flp, edge.sourcep()->width()}; + AstSel* const lhsp = new AstSel{flp, wRef(), lsbp, widthp}; + // Add assignment of the value to the selected bits + addResultEquation(flp, lhsp, rhsp); + }); + } + } + + void convertDuplicateVarDriver(const DfgVar* dfgVarp, AstVar* canonVarp) { + const auto rRef = [canonVarp]() { + return new AstVarRef{canonVarp->fileline(), canonVarp, VAccess::READ}; + }; + const auto wRef = [dfgVarp]() { + return new AstVarRef{dfgVarp->fileline(), dfgVarp->varp(), VAccess::WRITE}; + }; + if (dfgVarp->isDrivenFullyByDfg()) { + // Whole variable is driven. Just assign from the canonical variable. + addResultEquation(dfgVarp->driverFileLine(0), wRef(), rRef()); + } else { + // Variable is driven partially. Asign from parts of the canonical var. + dfgVarp->forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { + UASSERT_OBJ(edge.sourcep(), dfgVarp, "Should have removed undriven sources"); + // Create select LValue + FileLine* const flp = dfgVarp->driverFileLine(idx); + AstConst* const lsbp = new AstConst{flp, dfgVarp->driverLsb(idx)}; + AstConst* const widthp = new AstConst{flp, edge.sourcep()->width()}; + AstSel* const rhsp = new AstSel{flp, rRef(), lsbp, widthp->cloneTree(false)}; + AstSel* const lhsp = new AstSel{flp, wRef(), lsbp->cloneTree(false), widthp}; + // Add assignment of the value to the selected bits + addResultEquation(flp, lhsp, rhsp); + }); + } + } + + void addResultEquation(FileLine* flp, AstNode* lhsp, AstNode* rhsp) { + m_modp->addStmtsp(new AstAssignW{flp, lhsp, rhsp}); + ++m_ctx.m_resultEquations; + } + // VISITORS void visit(DfgVertex* vtxp) override { // LCOV_EXCL_START vtxp->v3fatal("Unhandled DfgVertex: " << vtxp->typeName()); @@ -231,57 +291,51 @@ class DfgToAstVisitor final : DfgVisitor { // We can eliminate some variables completely std::vector redundantVarps; - // Render the logic + // Convert vertices back to assignments dfg.forEachVertex([&](DfgVertex& vtx) { - // Compute the AstNodeMath expression representing this DfgVertex - AstNodeMath* rhsp = nullptr; - AstNodeMath* lhsp = nullptr; - FileLine* assignmentFlp = nullptr; + // Render variable assignments if (const DfgVar* const dfgVarp = vtx.cast()) { // DfgVar instances (these might be driving the given AstVar variable) - // If there is no driver (i.e.: this DfgVar is an input to the Dfg), then nothing - // to do - if (!dfgVarp->driverp()) return; + // If there is no driver (i.e.: this DfgVar is an input to the Dfg), then + // nothing to do + if (!dfgVarp->isDrivenByDfg()) return; // The driver of this DfgVar might drive multiple variables. Only emit one // assignment from the driver to an arbitrarily chosen canonical variable, and // assign the other variables from that canonical variable AstVar* const canonVarp = getCanonicalVar(dfgVarp); if (canonVarp == dfgVarp->varp()) { // This is the canonical variable, so render the driver - rhsp = convertDfgVertexToAstNodeMath(dfgVarp->driverp()); + convertCanonicalVarDriver(dfgVarp); } else if (dfgVarp->keep()) { - // Not the canonical variable but it must be kept, just assign from the - // canonical variable. - rhsp = new AstVarRef{canonVarp->fileline(), canonVarp, VAccess::READ}; + // Not the canonical variable but it must be kept + convertDuplicateVarDriver(dfgVarp, canonVarp); } else { // Not a canonical var, and it can be removed. We will replace all references // to it with the canonical variable, and hence this can be removed. redundantVarps.push_back(dfgVarp->varp()); ++m_ctx.m_replacedVars; - return; } - // The Lhs is the variable driven by this DfgVar - lhsp = new AstVarRef{vtx.fileline(), dfgVarp->varp(), VAccess::WRITE}; - // Set location to the location of the original assignment to this variable - assignmentFlp = dfgVarp->assignmentFileline(); - } else if (vtx.hasMultipleSinks() && !vtx.findSink()) { - // DfgVertex that has multiple sinks, but does not drive a DfgVar (needs temporary) - // Just render the logic - rhsp = convertDfgVertexToAstNodeMath(&vtx); - // The lhs is a temporary - lhsp = new AstVarRef{vtx.fileline(), getResultVar(&vtx), VAccess::WRITE}; - // Render vertex - assignmentFlp = vtx.fileline(); - // Stats - ++m_ctx.m_intermediateVars; - } else { - // Every other DfgVertex will be inlined by 'convertDfgVertexToAstNodeMath' as an - // AstNodeMath at use, and hence need not be converted. return; } - // Add assignment of the value to the variable - m_modp->addStmtsp(new AstAssignW{assignmentFlp, lhsp, rhsp}); - ++m_ctx.m_resultEquations; + + // Vertices driving a single vertex will be in-lined by 'convertDfgVertexToAstNodeMath' + if (!vtx.hasMultipleSinks()) return; + + // Vertices with multiple sinks needs a temporary if they do not fully drive a DfgVar + const bool needsTemporary = !vtx.findSink([](const DfgVar& var) { // + return var.isDrivenFullyByDfg(); + }); + if (needsTemporary) { + // DfgVertex that has multiple sinks, but does not drive a DfgVar (needs temporary) + ++m_ctx.m_intermediateVars; + // Just render the logic + AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(&vtx); + // The lhs is a temporary + AstNodeMath* const lhsp + = new AstVarRef{vtx.fileline(), getResultVar(&vtx), VAccess::WRITE}; + // Add assignment of the value to the variable + addResultEquation(vtx.fileline(), lhsp, rhsp); + } }); // Remap all references to point to the canonical variables, if one exists diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 2a164feb0..53c2efe98 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -53,6 +53,7 @@ V3DfgOptimizationContext::V3DfgOptimizationContext(const std::string& label) V3DfgOptimizationContext::~V3DfgOptimizationContext() { const string prefix = "Optimizations, DFG " + m_label + " "; V3Stats::addStat(prefix + "General, modules", m_modules); + V3Stats::addStat(prefix + "Ast2Dfg, coalesced assignments", m_coalescedAssignments); V3Stats::addStat(prefix + "Ast2Dfg, input equations", m_inputEquations); V3Stats::addStat(prefix + "Ast2Dfg, representable", m_representable); V3Stats::addStat(prefix + "Ast2Dfg, non-representable (dtype)", m_nonRepDType); @@ -79,8 +80,9 @@ void V3DfgPasses::inlineVars(DfgGraph& dfg) { dfg.forEachVertex([](DfgVertex& vtx) { // For each DfgVar that has a known driver if (DfgVar* const varVtxp = vtx.cast()) { - if (DfgVertex* const driverp = varVtxp->driverp()) { + if (varVtxp->isDrivenFullyByDfg()) { // Make consumers of the DfgVar consume the driver directly + DfgVertex* const driverp = varVtxp->source(0); varVtxp->forEachSinkEdge([=](DfgEdge& edge) { edge.relinkSource(driverp); }); } } @@ -123,7 +125,10 @@ void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { if (varp->hasSinks()) return; // Can't remove if read in the module and driven here (i.e.: it's an output of the DFG) - if (varp->hasModRefs() && varp->driverp()) return; + if (varp->hasModRefs() && varp->isDrivenByDfg()) return; + + // Can't remove if only partially driven by the DFG + if (varp->isDrivenByDfg() && !varp->isDrivenFullyByDfg()) return; // Can't remove if referenced externally, or other special reasons if (varp->keep()) return; @@ -131,7 +136,8 @@ void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { // If the driver of this variable has multiple non-variable sinks, then we would need // a temporary when rendering the graph. Instead of introducing a temporary, keep the // first variable that is driven by that driver - if (DfgVertex* const driverp = varp->driverp()) { + if (varp->isDrivenByDfg()) { + DfgVertex* const driverp = varp->source(0); unsigned nonVarSinks = 0; const DfgVar* firstSinkVarp = nullptr; const bool keepFirst = driverp->findSink([&](const DfgVertex& sink) { @@ -147,7 +153,7 @@ void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { if (keepFirst && firstSinkVarp == varp) return; } - // OK, we can delete this DfgVar! + // OK, we can delete this DfgVar ++ctx.m_removed; // If not referenced outside the DFG, then also delete the referenced AstVar, @@ -177,11 +183,13 @@ void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { // There is absolutely nothing useful we can do with a graph of size 2 or less if (dfg.size() <= 2) return; - // We consider a DFG trivial if it contains no more than 1 non-variable, non-constant vertex + // We consider a DFG trivial if it contains no more than 1 non-variable, non-constant vertex, + // or if if it contains a DfgConcat, which can be introduced through assinment coalescing. unsigned excitingVertices = 0; const bool isTrivial = !dfg.findVertex([&](const DfgVertex& vtx) { // if (vtx.is()) return false; if (vtx.is()) return false; + if (vtx.is()) return true; return ++excitingVertices >= 2; }); diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index 8ca377cc1..9e78f7e7c 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -54,6 +54,7 @@ class V3DfgOptimizationContext final { public: VDouble0 m_modules; // Number of modules optimized + VDouble0 m_coalescedAssignments; // Number of partial assignments coalesced VDouble0 m_inputEquations; // Number of input combinational equations VDouble0 m_representable; // Number of combinational equations representable VDouble0 m_nonRepDType; // Equations non-representable due to data type From 91823c41c556741df31a52e49e630b283e59a4f8 Mon Sep 17 00:00:00 2001 From: github action Date: Wed, 28 Sep 2022 02:22:05 +0000 Subject: [PATCH 052/177] Apply 'make format' --- include/verilated_timing.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/verilated_timing.h b/include/verilated_timing.h index 90d5f8665..45de319c4 100644 --- a/include/verilated_timing.h +++ b/include/verilated_timing.h @@ -76,22 +76,22 @@ public: // Construct VlCoroutineHandle(std::coroutine_handle<> coro = nullptr, const char* filename = nullptr, int linenum = 0) - : m_coro { - coro - } + : m_coro{coro} #ifdef VL_DEBUG - , m_filename{filename}, m_linenum { linenum } + , m_filename{filename} + , m_linenum{linenum} #endif - {} + { + } // Move the handle, leaving a nullptr VlCoroutineHandle(VlCoroutineHandle&& moved) - : m_coro { - std::exchange(moved.m_coro, nullptr) - } + : m_coro{std::exchange(moved.m_coro, nullptr)} #ifdef VL_DEBUG - , m_filename{moved.m_filename}, m_linenum { moved.m_linenum } + , m_filename{moved.m_filename} + , m_linenum{moved.m_linenum} #endif - {} + { + } // Destroy if the handle isn't null ~VlCoroutineHandle() { // Usually these coroutines should get resumed; we only need to clean up if we destroy a From b92173bf3d4025d214369c3773348622bd185c17 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Wed, 28 Sep 2022 09:04:14 -0400 Subject: [PATCH 053/177] Add --binary option as alias of --main --exe --build (#3625). --- Changes | 1 + Makefile.in | 3 ++ bin/verilator | 2 + docs/guide/example_binary.rst | 63 +++++++++++++++++++++++++++ docs/guide/example_cc.rst | 11 +++-- docs/guide/example_dist.rst | 2 + docs/guide/example_sc.rst | 2 +- docs/guide/examples.rst | 1 + docs/guide/exe_verilator.rst | 9 ++++ examples/make_hello_binary/.gitignore | 6 +++ examples/make_hello_binary/Makefile | 49 +++++++++++++++++++++ examples/make_hello_binary/top.v | 14 ++++++ src/V3Options.cpp | 7 ++- test_regress/t/t_flag_binary.pl | 30 +++++++++++++ test_regress/t/t_flag_noop_bad.out | 2 +- 15 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 docs/guide/example_binary.rst create mode 100644 examples/make_hello_binary/.gitignore create mode 100644 examples/make_hello_binary/Makefile create mode 100644 examples/make_hello_binary/top.v create mode 100755 test_regress/t/t_flag_binary.pl diff --git a/Changes b/Changes index e4bf5367b..d593a3074 100644 --- a/Changes +++ b/Changes @@ -19,6 +19,7 @@ Verilator 5.001 devel clocks are now simulated correctly (#3278, #3384). [Geza Lore, Shunyao CAD] * Support timing controls (delays, event controls in any location, wait statements) and forks. See docs for details. [Krzysztof Bieganski, Antmicro Ltd] +* Add --binary option as alias of --main --exe --build (#3625). Verilator 4.227 devel diff --git a/Makefile.in b/Makefile.in index 662a57361..a32d04619 100644 --- a/Makefile.in +++ b/Makefile.in @@ -107,6 +107,7 @@ SUBDIRS = docs src test_regress \ examples/cmake_tracing_c \ examples/cmake_tracing_sc \ examples/cmake_protect_lib \ + examples/make_hello_binary \ examples/make_hello_c \ examples/make_hello_sc \ examples/make_tracing_c \ @@ -243,6 +244,7 @@ installdata: ; for p in $(VL_INST_INC_SRCDIR_FILES) ; do \ $(INSTALL_DATA) $$p $(DESTDIR)$(pkgdatadir)/$$p; \ done + $(MKINSTALLDIRS) $(DESTDIR)$(pkgdatadir)/examples/make_hello_binary $(MKINSTALLDIRS) $(DESTDIR)$(pkgdatadir)/examples/make_hello_c $(MKINSTALLDIRS) $(DESTDIR)$(pkgdatadir)/examples/make_hello_sc $(MKINSTALLDIRS) $(DESTDIR)$(pkgdatadir)/examples/make_tracing_c @@ -278,6 +280,7 @@ uninstall: -rmdir $(DESTDIR)$(pkgdatadir)/include/gtkwave -rmdir $(DESTDIR)$(pkgdatadir)/include/vltstd -rmdir $(DESTDIR)$(pkgdatadir)/include + -rmdir $(DESTDIR)$(pkgdatadir)/examples/make_hello_binary -rmdir $(DESTDIR)$(pkgdatadir)/examples/make_hello_c -rmdir $(DESTDIR)$(pkgdatadir)/examples/make_hello_sc -rmdir $(DESTDIR)$(pkgdatadir)/examples/make_tracing_c diff --git a/bin/verilator b/bin/verilator index 247b4aae9..0e5a4bc7b 100755 --- a/bin/verilator +++ b/bin/verilator @@ -244,6 +244,7 @@ Verilator - Translate and simulate SystemVerilog code using C++/SystemC verilator --help verilator --version + verilator --binary -j 0 [options] [source_files.v]... [opt_c_files.cpp/c/cc/a/o/so] verilator --cc [options] [source_files.v]... [opt_c_files.cpp/c/cc/a/o/so] verilator --sc [options] [source_files.v]... [opt_c_files.cpp/c/cc/a/o/so] verilator --lint-only -Wall [source_files.v]... @@ -282,6 +283,7 @@ detailed descriptions of these arguments. --autoflush Flush streams after all $displays --bbox-sys Blackbox unknown $system calls --bbox-unsup Blackbox unsupported language features + --binary Build model binary --build Build model executable/library after Verilation --build-dep-bin Override build dependency Verilator binary --build-jobs Parallelism for --build diff --git a/docs/guide/example_binary.rst b/docs/guide/example_binary.rst new file mode 100644 index 000000000..ba88fc3ad --- /dev/null +++ b/docs/guide/example_binary.rst @@ -0,0 +1,63 @@ +.. Copyright 2003-2022 by Wilson Snyder. +.. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +.. _Example C++ Execution: + +Example C++ Execution +===================== + +We'll compile this SystemC example into a Verilated simulation binary. For +an example that discusses the next level of detail see :ref:`Example C++ +Execution`. + +.. include:: example_common_install.rst + +Now, let's create an example Verilog file: + +.. code-block:: bash + + mkdir test_our + cd test_our + + cat >our.v <<'EOF' + module our; + initial begin $display("Hello World"); $finish; end + endmodule + EOF + +Now we run Verilator on our little example. + +.. code-block:: bash + + verilator --binary -j 0 -Wall our.v + +Breaking this command down: + +#. :vlopt:`--binary` telling Verilator to do everything needed to create a + simulation executable. + +#. :vlopt:`-j` `0' to Verilate using use as many CPU threads as the machine + has. + +#. :vlopt:`-Wall` so Verilator has stronger lint warnings + enabled. + +#. An finally, :command:`our.v` which is our SystemVerilog design file. + +And now we run it: + +.. code-block:: bash + + obj_dir/Vour + +And we get as output: + +.. code-block:: bash + + Hello World + - our.v:2: Verilog $finish + +Really, you're better off using a Makefile to run the steps for you so when +your source changes it will automatically run all of the appropriate steps. +To aid this Verilator can create a makefile dependency file. For examples +that do this see the :file:`examples` directory in the distribution. diff --git a/docs/guide/example_cc.rst b/docs/guide/example_cc.rst index f5d55c8da..192704559 100644 --- a/docs/guide/example_cc.rst +++ b/docs/guide/example_cc.rst @@ -43,13 +43,10 @@ Now we run Verilator on our little example. .. code-block:: bash - verilator -Wall --cc --exe --build sim_main.cpp our.v + verilator --cc --exe --build -j 0 -Wall sim_main.cpp our.v Breaking this command down: -#. :vlopt:`-Wall` so Verilator has stronger lint warnings - enabled. - #. :vlopt:`--cc` to get C++ output (versus e.g. SystemC or only linting). @@ -61,6 +58,12 @@ Breaking this command down: own compile rules, and run make yourself as we show in :ref:`Example SystemC Execution`.) +#. :vlopt:`-j` `0' to Verilate using use as many CPU threads as the machine + has. + +#. :vlopt:`-Wall` so Verilator has stronger lint warnings + enabled. + #. An finally, :command:`our.v` which is our SystemVerilog design file. Once Verilator completes we can see the generated C++ code under the diff --git a/docs/guide/example_dist.rst b/docs/guide/example_dist.rst index 5b45ed1eb..6e6947071 100644 --- a/docs/guide/example_dist.rst +++ b/docs/guide/example_dist.rst @@ -10,6 +10,8 @@ See the ``examples/`` directory that is part of the distribution, and is installed (in a OS-specific place, often in e.g. ``/usr/local/share/verilator/examples``). These examples include: +examples/make_hello_binary + Example GNU-make simple Verilog->binary conversion examples/make_hello_c Example GNU-make simple Verilog->C++ conversion examples/make_hello_sc diff --git a/docs/guide/example_sc.rst b/docs/guide/example_sc.rst index ef8eb03ca..987554f81 100644 --- a/docs/guide/example_sc.rst +++ b/docs/guide/example_sc.rst @@ -43,7 +43,7 @@ Now we run Verilator on our little example: .. code-block:: bash - verilator -Wall --sc --exe sc_main.cpp our.v + verilator --sc --exe -Wall sc_main.cpp our.v This example does not use --build, therefore we need to explicitly compile it: diff --git a/docs/guide/examples.rst b/docs/guide/examples.rst index d57a82d57..b92fe9a78 100644 --- a/docs/guide/examples.rst +++ b/docs/guide/examples.rst @@ -17,6 +17,7 @@ This section covers the following examples: :maxdepth: 1 :hidden: + example_binary.rst example_cc.rst example_sc.rst example_dist.rst diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index aff59dd5b..7ced835e3 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -115,6 +115,13 @@ Summary: Using this argument will likely cause incorrect simulation. +.. option:: --binary + + Create a Verilated simulator binary. Alias for :vlopt:`--main` + :vlopt:`--exe` :vlopt:`--build`. + + See also :vlopt:`-j`. + .. option:: --build After generating the SystemC/C++ code, Verilator will invoke the @@ -780,6 +787,8 @@ Summary: Implies :vlopt:`--cc` if no other output mode was provided. + See also :vlopt:`--binary`. + .. option:: --max-num-width Set the maximum number literal width (e.g. in 1024'd22 this it the diff --git a/examples/make_hello_binary/.gitignore b/examples/make_hello_binary/.gitignore new file mode 100644 index 000000000..f68b202e1 --- /dev/null +++ b/examples/make_hello_binary/.gitignore @@ -0,0 +1,6 @@ +*.dmp +*.log +*.csrc +*.vcd +obj_* +logs diff --git a/examples/make_hello_binary/Makefile b/examples/make_hello_binary/Makefile new file mode 100644 index 000000000..19bec2dd0 --- /dev/null +++ b/examples/make_hello_binary/Makefile @@ -0,0 +1,49 @@ +###################################################################### +# +# DESCRIPTION: Verilator Example: Small Makefile +# +# This calls the object directory makefile. That allows the objects to +# be placed in the "current directory" which simplifies the Makefile. +# +# This file ONLY is placed under the Creative Commons Public Domain, for +# any use, without warranty, 2020 by Wilson Snyder. +# SPDX-License-Identifier: CC0-1.0 +# +###################################################################### +# Check for sanity to avoid later confusion + +ifneq ($(words $(CURDIR)),1) + $(error Unsupported: GNU Make cannot build in directories containing spaces, build elsewhere: '$(CURDIR)') +endif + +###################################################################### + +# This is intended to be a minimal example. Before copying this to start a +# real project, it is better to start with a more complete example, +# e.g. examples/make_tracing_c. + +# If $VERILATOR_ROOT isn't in the environment, we assume it is part of a +# package install, and verilator is in your path. Otherwise find the +# binary relative to $VERILATOR_ROOT (such as when inside the git sources). +ifeq ($(VERILATOR_ROOT),) +VERILATOR = verilator +else +export VERILATOR_ROOT +VERILATOR = $(VERILATOR_ROOT)/bin/verilator +endif + +default: + @echo "-- Verilator hello-world simple binary example" + @echo "-- VERILATE & BUILD --------" + $(VERILATOR) --binary -j 0 top.v + @echo "-- RUN ---------------------" + obj_dir/Vtop + @echo "-- DONE --------------------" + @echo "Note: Once this example is understood, see examples/make_hello_c." + @echo "Note: See also https://verilator.org/guide/latest/examples.html" + +###################################################################### + +maintainer-copy:: +clean mostlyclean distclean maintainer-clean:: + -rm -rf obj_dir *.log *.dmp *.vpd core diff --git a/examples/make_hello_binary/top.v b/examples/make_hello_binary/top.v new file mode 100644 index 000000000..3deb48f2d --- /dev/null +++ b/examples/make_hello_binary/top.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog example module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2017 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +// See also https://verilator.org/guide/latest/examples.html" + +module top; + initial begin + $display("Hello World!"); + $finish; + end +endmodule diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 3fd6efe76..a3486a397 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -741,7 +741,7 @@ void V3Options::notify() { if (!outFormatOk() && v3Global.opt.main()) ccSet(); // --main implies --cc if not provided if (!outFormatOk() && !cdc() && !dpiHdrOnly() && !lintOnly() && !preprocOnly() && !xmlOnly()) { - v3fatal("verilator: Need --cc, --sc, --cdc, --dpi-hdr-only, --lint-only, " + v3fatal("verilator: Need --binary, --cc, --sc, --cdc, --dpi-hdr-only, --lint-only, " "--xml-only or --E option"); } @@ -1016,6 +1016,11 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char m_bboxUnsup = flag; FileLine::globalWarnOff(V3ErrorCode::E_UNSUPPORTED, true); }); + DECL_OPTION("-binary", CbCall, [this]() { + m_build = true; + m_exe = true; + m_main = true; + }); DECL_OPTION("-build", Set, &m_build); DECL_OPTION("-build-dep-bin", Set, &m_buildDepBin); DECL_OPTION("-build-jobs", CbVal, [this, fl](const char* valp) { diff --git a/test_regress/t/t_flag_binary.pl b/test_regress/t/t_flag_binary.pl new file mode 100755 index 000000000..e13118b38 --- /dev/null +++ b/test_regress/t/t_flag_binary.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2019 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 + +top_filename("t/t_flag_main.v"); + +scenarios(simulator => 1); + +compile( + verilator_flags => [# Custom as don't want -cc + "-Mdir $Self->{obj_dir}", + "--debug-check", ], + verilator_flags2 => ['--binary'], + verilator_make_cmake => 0, + verilator_make_gmake => 0, + make_main => 0, + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_flag_noop_bad.out b/test_regress/t/t_flag_noop_bad.out index 3f89e26a3..d7bd1b71c 100644 --- a/test_regress/t/t_flag_noop_bad.out +++ b/test_regress/t/t_flag_noop_bad.out @@ -1 +1 @@ -%Error: verilator: Need --cc, --sc, --cdc, --dpi-hdr-only, --lint-only, --xml-only or --E option +%Error: verilator: Need --binary, --cc, --sc, --cdc, --dpi-hdr-only, --lint-only, --xml-only or --E option From 9c2ead90d5d820a15557e3151e0d5111e34a698e Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Thu, 29 Sep 2022 00:54:18 +0200 Subject: [PATCH 054/177] Add custom memory management for verilated classes (#3595) This change introduces a custom reference-counting pointer class that allows creating such pointers from 'this'. This lets us keep the receiver object around even if all references to it outside of a class method no longer exist. Useful for coroutine methods, which may outlive all external references to the object. The deletion of objects is deferred until the next time slot. This is to make clearing the triggered flag on named events in classes safe (otherwise freed memory could be accessed). --- include/verilated.cpp | 15 +++ include/verilated_types.h | 154 +++++++++++++++++++++++++- src/V3EmitCFunc.h | 4 +- src/V3EmitCHeaders.cpp | 4 +- src/V3EmitCImp.cpp | 2 + src/V3EmitCModel.cpp | 1 + src/V3EmitCSyms.cpp | 1 + src/V3Global.h | 3 + src/V3SchedTiming.cpp | 1 + src/V3Timing.cpp | 2 + src/verilog.y | 3 +- test_regress/t/t_class_member_sens.pl | 22 ++++ test_regress/t/t_class_member_sens.v | 30 +++++ test_regress/t/t_timing_class.v | 8 +- 14 files changed, 242 insertions(+), 8 deletions(-) create mode 100755 test_regress/t/t_class_member_sens.pl create mode 100644 test_regress/t/t_class_member_sens.v diff --git a/include/verilated.cpp b/include/verilated.cpp index 1b5dab84b..4370a5b75 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -3095,3 +3095,18 @@ void VerilatedAssertOneThread::fatal_different() VL_MT_SAFE { #endif //=========================================================================== +// VlDeleter:: Methods + +void VlDeleter::deleteAll() { + while (true) { + VerilatedLockGuard lock{m_mutex}; + if (m_newGarbage.empty()) break; + VerilatedLockGuard deleteLock{m_deleteMutex}; + std::swap(m_newGarbage, m_toDelete); + lock.unlock(); // So destuctors can enqueue new objects + for (VlClass* const objp : m_toDelete) delete objp; + m_toDelete.clear(); + } +} + +//=========================================================================== diff --git a/include/verilated_types.h b/include/verilated_types.h index bdcc0cdad..b39b3e2ae 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -1003,12 +1004,161 @@ std::string VL_TO_STRING(const VlUnpacked& obj) { return obj.to_string(); } +class VlClass; // See below + +//=================================================================== +// Class providing delayed deletion of garbage objects. Objects get deleted only when 'deleteAll()' +// is called, or the deleter itself is destroyed. + +class VlDeleter final { + // MEMBERS + // Queue of new objects that should be deleted + std::vector m_newGarbage VL_GUARDED_BY(m_mutex); + // Queue of objects currently being deleted (only for deleteAll()) + std::vector m_toDelete VL_GUARDED_BY(m_deleteMutex); + mutable VerilatedMutex m_mutex; // Mutex protecting the 'new garbage' queue + mutable VerilatedMutex m_deleteMutex; // Mutex protecting the delete queue + +public: + // CONSTRUCTOR + VlDeleter() = default; + ~VlDeleter() { deleteAll(); } + +private: + VL_UNCOPYABLE(VlDeleter); + +public: + // METHODS + // Adds a new object to the 'new garbage' queue. + void put(VlClass* const objp) VL_MT_SAFE { + const VerilatedLockGuard lock{m_mutex}; + m_newGarbage.push_back(objp); + } + + // Deletes all queued garbage objects. + void deleteAll() VL_MT_SAFE; +}; + +//=================================================================== +// Base class for all verilated classes. Includes a reference counter, and a pointer to the deleter +// object that should destroy it after the counter reaches 0. This allows for easy construction of +// VlClassRefs from 'this'. Also declares a virtual constructor, so that the object can be deleted +// using a base pointer. + +class VlClass VL_NOT_FINAL { + // TYPES + template + friend class VlClassRef; // Needed for access to the ref counter and deleter + + // MEMBERS + std::atomic m_counter{0}; // Reference count for this object + VlDeleter* m_deleter = nullptr; // The deleter that will delete this object + + // METHODS + // Atomically increments the reference counter + void refCountInc() VL_MT_SAFE { ++m_counter; } + // Atomically decrements the reference counter. Assuming VlClassRef semantics are sound, it + // should never get called at m_counter == 0. + void refCountDec() VL_MT_SAFE { + if (!--m_counter) m_deleter->put(this); + } + +public: + // CONSTRUCTORS + VlClass() = default; + VlClass(const VlClass& copied) {} + virtual ~VlClass() {} +}; + //=================================================================== // Verilog class reference container // There are no multithreaded locks on this; the base variable must // be protected by other means -#define VlClassRef std::shared_ptr +template +class VlClassRef final { +private: + // TYPES + template + friend class VlClassRef; // Needed for template copy/move assignments + + // MEMBERS + T_Class* m_objp = nullptr; // Object pointed to + + // METHODS + // Increase reference counter with null check + void refCountInc() const VL_MT_SAFE { + if (m_objp) m_objp->refCountInc(); + } + // Decrease reference counter with null check + void refCountDec() const VL_MT_SAFE { + if (m_objp) m_objp->refCountDec(); + } + +public: + // CONSTRUCTORS + VlClassRef() = default; + template + VlClassRef(VlDeleter& deleter, T_Args&&... args) + : m_objp{new T_Class{std::forward(args)...}} { + m_objp->m_deleter = &deleter; + refCountInc(); + } + VlClassRef(T_Class* objp) + : m_objp{objp} { + refCountInc(); + } + VlClassRef(const VlClassRef& copied) + : m_objp{copied.m_objp} { + refCountInc(); + } + VlClassRef(VlClassRef&& moved) + : m_objp{std::exchange(moved.m_objp, nullptr)} {} + ~VlClassRef() { refCountDec(); } + + // METHODS + // Copy and move assignments + VlClassRef& operator=(const VlClassRef& copied) { + refCountDec(); + m_objp = copied.m_objp; + refCountInc(); + return *this; + } + VlClassRef& operator=(VlClassRef&& moved) { + refCountDec(); + m_objp = std::exchange(moved.m_objp, nullptr); + return *this; + } + template + VlClassRef& operator=(const VlClassRef& copied) { + refCountDec(); + m_objp = copied.m_objp; + refCountInc(); + return *this; + } + template + VlClassRef& operator=(VlClassRef&& moved) { + refCountDec(); + m_objp = std::exchange(moved.m_objp, nullptr); + return *this; + } + // Dynamic caster + template + VlClassRef dynamicCast() const { + return dynamic_cast(m_objp); + } + // Dereference operators + T_Class& operator*() const { return *m_objp; } + T_Class* operator->() const { return m_objp; } + // For 'if (ptr)...' + operator bool() const { return m_objp; } +}; + +#define VL_NEW(Class, ...) \ + VlClassRef { vlSymsp->__Vm_deleter, __VA_ARGS__ } + +#define VL_KEEP_THIS \ + VlClassRef::type> __Vthisref { this } template // T typically of type VlClassRef inline T VL_NULL_CHECK(T t, const char* filename, int linenum) { @@ -1018,7 +1168,7 @@ inline T VL_NULL_CHECK(T t, const char* filename, int linenum) { template static inline bool VL_CAST_DYNAMIC(VlClassRef in, VlClassRef& outr) { - VlClassRef casted = std::dynamic_pointer_cast(in); + VlClassRef casted = in.template dynamicCast(); if (VL_LIKELY(casted)) { outr = casted; return true; diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index e9b63f591..8369386bd 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -419,7 +419,7 @@ public: } void visit(AstCNew* nodep) override { bool comma = false; - puts("std::make_shared<" + prefixNameProtect(nodep->dtypep()) + ">("); + puts("VL_NEW(" + prefixNameProtect(nodep->dtypep()) + ", "); puts("vlSymsp"); // TODO make this part of argsp, and eliminate when unnecessary if (nodep->argsp()) comma = true; for (AstNode* subnodep = nodep->argsp(); subnodep; subnodep = subnodep->nextp()) { @@ -1054,7 +1054,7 @@ public: puts(")"); } void visit(AstNewCopy* nodep) override { - puts("std::make_shared<" + prefixNameProtect(nodep->dtypep()) + ">("); + puts("VL_NEW(" + prefixNameProtect(nodep->dtypep()) + ", "); puts("*"); // i.e. make into a reference iterateAndNextNull(nodep->rhsp()); puts(")"); diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index f03598b58..d9197cd0c 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -256,9 +256,11 @@ class EmitCHeader final : public EmitCConstInit { puts("\nclass "); puts(prefixNameProtect(modp)); if (const AstClass* const classp = VN_CAST(modp, Class)) { + puts(" : public "); if (classp->extendsp()) { - puts(" : public "); puts(prefixNameProtect(classp->extendsp()->classp())); + } else { + puts("VlClass"); } } else { puts(" final : public VerilatedModule"); diff --git a/src/V3EmitCImp.cpp b/src/V3EmitCImp.cpp index 5e9c3367f..0c0cdd379 100644 --- a/src/V3EmitCImp.cpp +++ b/src/V3EmitCImp.cpp @@ -75,6 +75,7 @@ class EmitCGatherDependencies final : VNVisitor { iterateChildrenConst(nodep); } void visit(AstCNew* nodep) override { + addSymsDependency(); addDTypeDependency(nodep->dtypep()); iterateChildrenConst(nodep); } @@ -83,6 +84,7 @@ class EmitCGatherDependencies final : VNVisitor { iterateChildrenConst(nodep); } void visit(AstNewCopy* nodep) override { + addSymsDependency(); addDTypeDependency(nodep->dtypep()); iterateChildrenConst(nodep); } diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 5430337d2..0be57f9c9 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -387,6 +387,7 @@ class EmitCModel final : public EmitCFunc { if (v3Global.opt.trace()) puts("vlSymsp->__Vm_activity = true;\n"); if (v3Global.hasEvents()) puts("vlSymsp->clearTriggeredEvents();\n"); + if (v3Global.hasClasses()) puts("vlSymsp->__Vm_deleter.deleteAll();\n"); puts("if (VL_UNLIKELY(!vlSymsp->__Vm_didInit)) {\n"); puts("vlSymsp->__Vm_didInit = true;\n"); diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index 62f2ba8b4..752fe77c6 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -446,6 +446,7 @@ void EmitCSyms::emitSymHdr() { " ///< Used by trace routines when tracing multiple models\n"); } if (v3Global.hasEvents()) puts("std::vector __Vm_triggeredEvents;\n"); + if (v3Global.hasClasses()) puts("VlDeleter __Vm_deleter;\n"); puts("bool __Vm_didInit = false;\n"); if (v3Global.opt.mtasks()) { diff --git a/src/V3Global.h b/src/V3Global.h index 05f290c5f..0f1baaa3b 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -106,6 +106,7 @@ class V3Global final { bool m_needTraceDumper = false; // Need __Vm_dumperp in symbols bool m_dpi = false; // Need __Dpi include files bool m_hasEvents = false; // Design uses SystemVerilog named events + bool m_hasClasses = false; // Design uses SystemVerilog classes bool m_usesTiming = false; // Design uses timing constructs bool m_hasForceableSignals = false; // Need to apply V3Force pass bool m_hasSCTextSections = false; // Has `systemc_* sections that need to be emitted @@ -149,6 +150,8 @@ public: void dpi(bool flag) { m_dpi = flag; } bool hasEvents() const { return m_hasEvents; } void setHasEvents() { m_hasEvents = true; } + bool hasClasses() const { return m_hasClasses; } + void setHasClasses() { m_hasClasses = true; } bool usesTiming() const { return m_usesTiming; } void setUsesTiming() { m_usesTiming = true; } bool hasForceableSignals() const { return m_hasForceableSignals; } diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index cc426ddd2..5423ec629 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -347,6 +347,7 @@ void transformForks(AstNetlist* const netlistp) { nodep->replaceWith(callp); // If we're in a class, add a vlSymsp arg if (m_inClass) { + newfuncp->addInitsp(new AstCStmt{nodep->fileline(), "VL_KEEP_THIS;\n"}); newfuncp->argTypes(EmitCBaseVisitor::symClassVar()); callp->argTypes("vlSymsp"); } diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 8e5661ef9..5ebb7ea7b 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -393,6 +393,8 @@ private: } if (nodep->user2() && !nodep->isCoroutine()) { // If first marked as suspendable nodep->rtnType("VlCoroutine"); + // If in a class, create a shared pointer to 'this' + if (m_classp) nodep->addInitsp(new AstCStmt{nodep->fileline(), "VL_KEEP_THIS;\n"}); // Revisit dependent nodes if needed for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) { auto* const depVxp = static_cast(edgep->fromp()); diff --git a/src/verilog.y b/src/verilog.y index f98715141..86ecb7604 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -6202,7 +6202,8 @@ classFront: // IEEE: part of class_declaration { $$ = new AstClass($2, *$4); $$->isVirtual($1); $$->lifetime($3); - SYMP->pushNew($$); } + SYMP->pushNew($$); + v3Global.setHasClasses(); } // // IEEE: part of interface_class_declaration | yINTERFACE yCLASS lifetimeE idAny/*class_identifier*/ { $$ = new AstClass($2, *$4); diff --git a/test_regress/t/t_class_member_sens.pl b/test_regress/t/t_class_member_sens.pl new file mode 100755 index 000000000..cf6d969a8 --- /dev/null +++ b/test_regress/t/t_class_member_sens.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile( + sanitize => 1, + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_class_member_sens.v b/test_regress/t/t_class_member_sens.v new file mode 100644 index 000000000..f178f995b --- /dev/null +++ b/test_regress/t/t_class_member_sens.v @@ -0,0 +1,30 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + class EventClass; + event e; + endclass + + EventClass ec = new; + int cyc = 0; + + always @ec.e ec = new; + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) ->ec.e; + else if (cyc == 2) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_timing_class.v b/test_regress/t/t_timing_class.v index 4b8480ced..b79139153 100644 --- a/test_regress/t/t_timing_class.v +++ b/test_regress/t/t_timing_class.v @@ -84,6 +84,7 @@ module t; logic y; task do_assign; y = #10 x; + `WRITE_VERBOSE(("Did assignment with delay\n")); endtask endclass @@ -121,7 +122,10 @@ module t; if ($time != 80) $stop; if (event_trig_count != 2) $stop; if (dAsgn.y != 1) $stop; - $write("*-* All Finished *-*\n"); + // Test if the object is deleted before do_assign finishes: + fork dAsgn.do_assign; join_none + #5 dAsgn = null; + #15 $write("*-* All Finished *-*\n"); $finish; end @@ -162,5 +166,5 @@ module t; if (fc.done != 4 || $time != 70) $stop; end - initial #81 $stop; // timeout + initial #101 $stop; // timeout endmodule From cd2a5771b845bc82428511ded881210fcd6d2252 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Wed, 28 Sep 2022 19:02:07 -0400 Subject: [PATCH 055/177] Add --timing to --binary (#3625). --- Changes | 2 +- docs/guide/exe_verilator.rst | 2 +- src/V3Options.cpp | 1 + test_regress/t/t_flag_binary.pl | 33 +++++++++++++++++++-------------- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Changes b/Changes index d593a3074..64f4be8f0 100644 --- a/Changes +++ b/Changes @@ -19,7 +19,7 @@ Verilator 5.001 devel clocks are now simulated correctly (#3278, #3384). [Geza Lore, Shunyao CAD] * Support timing controls (delays, event controls in any location, wait statements) and forks. See docs for details. [Krzysztof Bieganski, Antmicro Ltd] -* Add --binary option as alias of --main --exe --build (#3625). +* Add --binary option as alias of --main --exe --build --timing (#3625). Verilator 4.227 devel diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 7ced835e3..e7a742363 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -118,7 +118,7 @@ Summary: .. option:: --binary Create a Verilated simulator binary. Alias for :vlopt:`--main` - :vlopt:`--exe` :vlopt:`--build`. + :vlopt:`--exe` :vlopt:`--build` :vlopt:`--timing`. See also :vlopt:`-j`. diff --git a/src/V3Options.cpp b/src/V3Options.cpp index a3486a397..c6d5c6c63 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1020,6 +1020,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char m_build = true; m_exe = true; m_main = true; + if (m_timing.isDefault()) m_timing = VOptionBool::OPT_TRUE; }); DECL_OPTION("-build", Set, &m_build); DECL_OPTION("-build-dep-bin", Set, &m_buildDepBin); diff --git a/test_regress/t/t_flag_binary.pl b/test_regress/t/t_flag_binary.pl index e13118b38..c8f265aa1 100755 --- a/test_regress/t/t_flag_binary.pl +++ b/test_regress/t/t_flag_binary.pl @@ -8,23 +8,28 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di # Version 2.0. # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -top_filename("t/t_flag_main.v"); - scenarios(simulator => 1); -compile( - verilator_flags => [# Custom as don't want -cc - "-Mdir $Self->{obj_dir}", - "--debug-check", ], - verilator_flags2 => ['--binary'], - verilator_make_cmake => 0, - verilator_make_gmake => 0, - make_main => 0, - ); +top_filename("t/t_flag_main.v"); -execute( - check_finished => 1, - ); +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags => [# Custom as don't want -cc + "-Mdir $Self->{obj_dir}", + "--debug-check", ], + verilator_flags2 => ['--binary'], + verilator_make_cmake => 0, + verilator_make_gmake => 0, + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} ok(1); 1; From 17976d7401347a7107c01032f8948895dd61ca4b Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 29 Sep 2022 18:17:58 +0100 Subject: [PATCH 056/177] DFG: fix REPLACE_EQ_OF_CONST_AND_CONST peephole pattern --- src/V3DfgPeephole.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index c27607e21..7ed5ec3c8 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -610,9 +610,7 @@ class V3DfgPeephole final : public DfgVisitor { if (DfgConst* const rhsConstp = rhsp->cast()) { APPLYING(REPLACE_EQ_OF_CONST_AND_CONST) { DfgConst* const replacementp = makeZero(vtxp->fileline(), 1); - if (lhsConstp->constp()->sameTree(rhsConstp->constp())) { - replacementp->num().setLong(1); - } + replacementp->num().opEq(lhsConstp->num(), rhsConstp->num()); vtxp->replaceWith(replacementp); return; } From 09e352ef6659638d28aeadd2d4ae9fe7cd0b9cd9 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 27 Sep 2022 13:50:37 +0100 Subject: [PATCH 057/177] DFG: support hashing of graphs circular through variables No functional change --- src/V3Dfg.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 5350be82d..56e0a8290 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -468,7 +468,11 @@ V3Hash DfgVertex::hash(HashCache& cache) const { V3Hash& result = pair.first->second; if (pair.second) { result += selfHash(); - forEachSource([&result, &cache](const DfgVertex& src) { result += src.hash(cache); }); + // Variables are defined by themselves, so there is no need to hash the sources. This + // enables sound hashing of graphs circular only through variables, which we rely on. + if (!is()) { + forEachSource([&result, &cache](const DfgVertex& src) { result += src.hash(cache); }); + } } return result; } @@ -503,9 +507,8 @@ void DfgVar::accept(DfgVisitor& visitor) { visitor.visit(this); } bool DfgVar::selfEquals(const DfgVertex& that) const { if (const DfgVar* otherp = that.cast()) { - UASSERT_OBJ(varp() != otherp->varp() || this == otherp, this, + UASSERT_OBJ(varp() != otherp->varp(), this, "There should only be one DfgVar for a given AstVar"); - return this == otherp; } return false; } From 4a1a2def95a394436aa5e3cfaaa4a6003e616e46 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 27 Sep 2022 13:53:07 +0100 Subject: [PATCH 058/177] DFG: make variable inlining part of the peephole optimizer This saves some traversals and prepares us to better handle cyclic DFGs. --- src/V3DfgPasses.cpp | 48 ++++++++----------------------------------- src/V3DfgPasses.h | 2 -- src/V3DfgPeephole.cpp | 35 +++++++++++++++++++++++-------- src/V3DfgPeephole.h | 3 ++- 4 files changed, 37 insertions(+), 51 deletions(-) diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 53c2efe98..77e8392ca 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -75,20 +75,6 @@ V3DfgOptimizationContext::~V3DfgOptimizationContext() { "Inconsistent statistics"); } -// 'Inline' DfgVar nodes with known drivers -void V3DfgPasses::inlineVars(DfgGraph& dfg) { - dfg.forEachVertex([](DfgVertex& vtx) { - // For each DfgVar that has a known driver - if (DfgVar* const varVtxp = vtx.cast()) { - if (varVtxp->isDrivenFullyByDfg()) { - // Make consumers of the DfgVar consume the driver directly - DfgVertex* const driverp = varVtxp->source(0); - varVtxp->forEachSinkEdge([=](DfgEdge& edge) { edge.relinkSource(driverp); }); - } - } - }); -} - // Common subexpression elimination void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) { DfgVertex::HashCache hashCache; @@ -183,16 +169,6 @@ void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { // There is absolutely nothing useful we can do with a graph of size 2 or less if (dfg.size() <= 2) return; - // We consider a DFG trivial if it contains no more than 1 non-variable, non-constant vertex, - // or if if it contains a DfgConcat, which can be introduced through assinment coalescing. - unsigned excitingVertices = 0; - const bool isTrivial = !dfg.findVertex([&](const DfgVertex& vtx) { // - if (vtx.is()) return false; - if (vtx.is()) return false; - if (vtx.is()) return true; - return ++excitingVertices >= 2; - }); - int passNumber = 0; const auto apply = [&](int dumpLevel, const string name, std::function pass) { @@ -206,23 +182,15 @@ void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { ++passNumber; }; - if (!isTrivial) { - // Optimize non-trivial graph - if (dumpDfg() >= 8) { dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "input"); } - apply(3, "input ", [&]() {}); - apply(4, "inlineVars ", [&]() { inlineVars(dfg); }); - apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext0); }); - if (v3Global.opt.fDfgPeephole()) { - apply(4, "peephole ", [&]() { peephole(dfg, ctx.m_peepholeContext); }); - } + if (dumpDfg() >= 8) dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "input"); + apply(3, "input ", [&]() {}); + apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext0); }); + if (v3Global.opt.fDfgPeephole()) { + apply(4, "peephole ", [&]() { peephole(dfg, ctx.m_peepholeContext); }); + // Without peephole no variables will be redundant, and we just did CSE, so skip these apply(4, "removeVars ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); }); apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext1); }); - apply(3, "optimized ", [&]() { removeUnused(dfg); }); - if (dumpDfg() >= 8) { dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "optimized"); } - } else { - // We can still eliminate redundancies from trivial graphs - apply(5, "trivial-input ", [&]() {}); - apply(6, "trivial-inlineVars ", [&]() { inlineVars(dfg); }); - apply(5, "trivial-optimized ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); }); } + apply(3, "optimized ", [&]() { removeUnused(dfg); }); + if (dumpDfg() >= 8) dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "optimized"); } diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index 9e78f7e7c..595f7ee8b 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -100,8 +100,6 @@ AstModule* dfgToAst(DfgGraph&, V3DfgOptimizationContext&); // Intermediate/internal operations //=========================================================================== -// Inline variables -void inlineVars(DfgGraph&); // Common subexpression elimination void cse(DfgGraph&, V3DfgCseContext&); // Peephole optimizations diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 7ed5ec3c8..12804f137 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -1102,19 +1102,30 @@ class V3DfgPeephole final : public DfgVisitor { } } + void visit(DfgVar* vtxp) override { + // Inline variables fully driven by the logic represented by the DFG + if (vtxp->hasSinks() && vtxp->isDrivenFullyByDfg()) { + APPLYING(INLINE_VAR) { + // Make consumers of the DfgVar consume the driver directly + DfgVertex* const driverp = vtxp->source(0); + vtxp->forEachSinkEdge([=](DfgEdge& edge) { edge.relinkSource(driverp); }); + } + } + } + #undef APPLYING // Process one vertex. Return true if graph changed bool processVertex(DfgVertex& vtx) { - // Keep DfgVars in this pass, we will remove them later if they become redundant - // Note: We want to keep the original variables for non-var vertices that drive multiple - // sinks (otherwise we would need to introduce a temporary, it is better for debugging to - // keep the original variable name, if one is available), so we can't remove redundant - // variables here. - if (vtx.is()) return false; + // Keep DfgVertexLValue vertices in this pass. We will remove them later if they become + // redundant. We want to keep the original variables for non-var vertices that drive + // multiple sinks (otherwise we would need to introduce a temporary, but it is better for + // debugging to keep the original variable name, if one is available), so we can't remove + // redundant variables here. + const bool keep = vtx.is(); // If it has no sinks (unused), we can remove it - if (!vtx.hasSinks()) { + if (!keep && !vtx.hasSinks()) { vtx.unlinkDelete(m_dfg); return true; } @@ -1122,7 +1133,15 @@ class V3DfgPeephole final : public DfgVisitor { // Transform node m_changed = false; iterate(&vtx); - if (!vtx.hasSinks()) vtx.unlinkDelete(m_dfg); // If it became unused, we can remove it + + // If it became unused, we can remove it + if (!keep && !vtx.hasSinks()) { + UASSERT_OBJ(m_changed, &vtx, "'m_changed' must be set if node became unused"); + vtx.unlinkDelete(m_dfg); + return true; + } + + // Return the changed status return m_changed; } diff --git a/src/V3DfgPeephole.h b/src/V3DfgPeephole.h index 1836341db..f89a6d1ee 100644 --- a/src/V3DfgPeephole.h +++ b/src/V3DfgPeephole.h @@ -82,7 +82,8 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ZERO) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ONES) + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ONES) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_VAR) // clang-format on From acebafcbc2f08379136f91980213fcf381f1d9fb Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 27 Sep 2022 00:06:50 +0100 Subject: [PATCH 059/177] DFG: Partial support for unpacked arrays Representation and Ast / Dfg conversions available, for element-wise access only. Not much optimization yet (only CSE). --- src/V3Dfg.cpp | 122 ++++++++++++++++++-------- src/V3Dfg.h | 95 +++++++++++++++++--- src/V3DfgAstToDfg.cpp | 195 ++++++++++++++++++++++++------------------ src/V3DfgDfgToAst.cpp | 160 ++++++++++++++++++++++------------ src/V3DfgPasses.cpp | 14 +-- src/V3DfgPeephole.cpp | 10 +-- 6 files changed, 400 insertions(+), 196 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 56e0a8290..184fdb874 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -187,32 +187,59 @@ static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) // Dump one DfgVertex in Graphviz format static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { os << toDotId(vtx); - if (const DfgVar* const varVtxp = vtx.cast()) { + + if (const DfgVarPacked* const varVtxp = vtx.cast()) { AstVar* const varp = varVtxp->varp(); os << " [label=\"" << varp->name() << "\nW" << varVtxp->width() << " / F" << varVtxp->fanout() << '"'; - if (varp->isIO()) { - if (varp->direction() == VDirection::INPUT) { - os << ", shape=house, orientation=270"; - } else if (varp->direction() == VDirection::OUTPUT) { - os << ", shape=house, orientation=90"; - } else { - os << ", shape=star"; - } + + if (varp->direction() == VDirection::INPUT) { + os << ", shape=box, style=filled, fillcolor=chartreuse2"; // Green + } else if (varp->direction() == VDirection::OUTPUT) { + os << ", shape=box, style=filled, fillcolor=cyan2"; // Cyan + } else if (varp->direction() == VDirection::INOUT) { + os << ", shape=box, style=filled, fillcolor=darkorchid2"; // Purple } else if (varVtxp->hasExtRefs()) { - os << ", shape=box, style=diagonals,filled, fillcolor=red"; + os << ", shape=box, style=filled, fillcolor=firebrick2"; // Red } else if (varVtxp->hasModRefs()) { - os << ", shape=box, style=diagonals"; + os << ", shape=box, style=filled, fillcolor=gold2"; // Yellow + } else if (varVtxp->keep()) { + os << ", shape=box, style=filled, fillcolor=grey"; } else { os << ", shape=box"; } - os << "]"; - } else if (const DfgConst* const constVtxp = vtx.cast()) { + os << "]" << endl; + return; + } + + if (const DfgVarArray* const arrVtxp = vtx.cast()) { + AstVar* const varp = arrVtxp->varp(); + os << " [label=\"" << varp->name() << "[]\""; + if (varp->direction() == VDirection::INPUT) { + os << ", shape=box3d, style=filled, fillcolor=chartreuse2"; // Green + } else if (varp->direction() == VDirection::OUTPUT) { + os << ", shape=box3d, style=filled, fillcolor=cyan2"; // Cyan + } else if (varp->direction() == VDirection::INOUT) { + os << ", shape=box3d, style=filled, fillcolor=darkorchid2"; // Purple + } else if (arrVtxp->hasExtRefs()) { + os << ", shape=box3d, style=filled, fillcolor=firebrick2"; // Red + } else if (arrVtxp->hasModRefs()) { + os << ", shape=box3d, style=filled, fillcolor=gold2"; // Yellow + } else if (arrVtxp->keep()) { + os << ", shape=box3d, style=filled, fillcolor=grey"; + } else { + os << ", shape=box3d"; + } + os << "]" << endl; + return; + } + + if (const DfgConst* const constVtxp = vtx.cast()) { const V3Number& num = constVtxp->constp()->num(); os << " [label=\""; if (num.width() <= 32 && !num.isSigned()) { const bool feedsSel = !constVtxp->findSink([](const DfgVertex& vtx) { // - return !vtx.is(); + return !vtx.is() && !vtx.is(); }); if (feedsSel) { os << num.toUInt(); @@ -225,17 +252,17 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { } os << '"'; os << ", shape=plain"; - os << "]"; - } else { - os << " [label=\"" << vtx.typeName() << "\nW" << vtx.width() << " / F" << vtx.fanout() - << '"'; - if (vtx.hasMultipleSinks()) - os << ", shape=doublecircle"; - else - os << ", shape=circle"; - os << "]"; + os << "]" << endl; + return; } - os << endl; + + os << " [label=\"" << vtx.typeName() << "\nW" << vtx.width() << " / F" << vtx.fanout() << '"'; + if (vtx.hasMultipleSinks()) { + os << ", shape=doublecircle"; + } else { + os << ", shape=circle"; + } + os << "]" << endl; } // Dump one DfgEdge in Graphviz format @@ -314,9 +341,9 @@ static void dumpDotUpstreamConeFromVertex(std::ostream& os, const DfgVertex& vtx dumpDotVertexAndSourceEdges(os, *itemp); } - // Emit all DfgVar vertices that have external references driven by this vertex + // Emit all DfgVarPacked vertices that have external references driven by this vertex vtx.forEachSink([&](const DfgVertex& dst) { - if (const DfgVar* const varVtxp = dst.cast()) { + if (const DfgVarPacked* const varVtxp = dst.cast()) { if (varVtxp->hasRefs()) dumpDotVertexAndSourceEdges(os, dst); } }); @@ -349,9 +376,10 @@ void DfgGraph::dumpDotAllVarConesPrefixed(const string& label) const { const string prefix = label.empty() ? name() + "-cone-" : name() + "-" + label + "-cone-"; forEachVertex([&](const DfgVertex& vtx) { // Check if this vertex drives a variable referenced outside the DFG. - const DfgVar* const sinkp = vtx.findSink([](const DfgVar& sink) { // - return sink.hasRefs(); - }); + const DfgVarPacked* const sinkp + = vtx.findSink([](const DfgVarPacked& sink) { // + return sink.hasRefs(); + }); // We only dump cones driving an externally referenced variable if (!sinkp) return; @@ -428,6 +456,11 @@ DfgVertex::DfgVertex(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType dfg.addVertex(*this); } +DfgVertex::~DfgVertex() { + // TODO: It would be best to intern these via AstTypeTable to save the effort + if (VN_IS(m_dtypep, UnpackArrayDType)) VL_DO_DANGLING(delete m_dtypep, m_dtypep); +} + bool DfgVertex::selfEquals(const DfgVertex& that) const { return this->m_type == that.m_type && this->dtypep() == that.dtypep(); } @@ -470,7 +503,7 @@ V3Hash DfgVertex::hash(HashCache& cache) const { result += selfHash(); // Variables are defined by themselves, so there is no need to hash the sources. This // enables sound hashing of graphs circular only through variables, which we rely on. - if (!is()) { + if (!is() && !is()) { forEachSource([&result, &cache](const DfgVertex& src) { result += src.hash(cache); }); } } @@ -502,18 +535,31 @@ void DfgVertex::replaceWith(DfgVertex* newSorucep) { // Vertex classes //------------------------------------------------------------------------------ -// DfgVar ---------- -void DfgVar::accept(DfgVisitor& visitor) { visitor.visit(this); } +// DfgVarPacked ---------- +void DfgVarPacked::accept(DfgVisitor& visitor) { visitor.visit(this); } -bool DfgVar::selfEquals(const DfgVertex& that) const { - if (const DfgVar* otherp = that.cast()) { +bool DfgVarPacked::selfEquals(const DfgVertex& that) const { + if (const DfgVarPacked* otherp = that.cast()) { UASSERT_OBJ(varp() != otherp->varp(), this, - "There should only be one DfgVar for a given AstVar"); + "There should only be one DfgVarPacked for a given AstVar"); } return false; } -V3Hash DfgVar::selfHash() const { return V3Hasher::uncachedHash(varp()); } +V3Hash DfgVarPacked::selfHash() const { return V3Hasher::uncachedHash(varp()); } + +// DfgVarPacked ---------- +void DfgVarArray::accept(DfgVisitor& visitor) { visitor.visit(this); } + +bool DfgVarArray::selfEquals(const DfgVertex& that) const { + if (const DfgVarArray* otherp = that.cast()) { + UASSERT_OBJ(varp() != otherp->varp(), this, + "There should only be one DfgVarArray for a given AstVar"); + } + return false; +} + +V3Hash DfgVarArray::selfHash() const { return V3Hasher::uncachedHash(varp()); } // DfgConst ---------- void DfgConst::accept(DfgVisitor& visitor) { visitor.visit(this); } @@ -531,8 +577,8 @@ V3Hash DfgConst::selfHash() const { return V3Hasher::uncachedHash(m_constp); } // DfgVisitor //------------------------------------------------------------------------------ -void DfgVisitor::visit(DfgVar* vtxp) { visit(static_cast(vtxp)); } - +void DfgVisitor::visit(DfgVarPacked* vtxp) { visit(static_cast(vtxp)); } +void DfgVisitor::visit(DfgVarArray* vtxp) { visit(static_cast(vtxp)); } void DfgVisitor::visit(DfgConst* vtxp) { visit(static_cast(vtxp)); } //------------------------------------------------------------------------------ diff --git a/src/V3Dfg.h b/src/V3Dfg.h index aedb1c838..e17dd1909 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -205,7 +205,7 @@ protected: DfgVertex(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type); public: - virtual ~DfgVertex() = default; + virtual ~DfgVertex(); // METHODS private: @@ -219,15 +219,14 @@ private: virtual V3Hash selfHash() const; public: - // Returns true if an AstNode with the given 'dtype' can be represented as a DfgVertex - static bool isSupportedDType(const AstNodeDType* dtypep) { - // Conservatively only support bit-vector like basic types and packed arrays of the same + // Supported packed types + static bool isSupportedPackedDType(const AstNodeDType* dtypep) { dtypep = dtypep->skipRefp(); if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) { return typep->keyword().isIntNumeric(); } if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) { - return isSupportedDType(typep->subDTypep()); + return isSupportedPackedDType(typep->subDTypep()); } if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) { return typep->packed(); @@ -235,6 +234,17 @@ public: return false; } + // Returns true if an AstNode with the given 'dtype' can be represented as a DfgVertex + static bool isSupportedDType(const AstNodeDType* dtypep) { + dtypep = dtypep->skipRefp(); + // Support unpacked arrays of packed types + if (const AstUnpackArrayDType* const typep = VN_CAST(dtypep, UnpackArrayDType)) { + return isSupportedPackedDType(typep->subDTypep()); + } + // Support packed types + return isSupportedPackedDType(dtypep); + } + // Return data type used to represent any packed value of the given 'width'. All packed types // of a given width use the same canonical data type, as the only interesting information is // the total width. @@ -245,7 +255,13 @@ public: // Return data type used to represent the type of 'nodep' when converted to a DfgVertex static AstNodeDType* dtypeFor(const AstNode* nodep) { UDEBUGONLY(UASSERT_OBJ(isSupportedDType(nodep->dtypep()), nodep, "Unsupported dtype");); - // Currently all supported types are packed, so this is simple + // For simplicity, all packed types are represented with a fixed type + if (AstUnpackArrayDType* const typep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) { + // TODO: these need interning via AstTypeTable otherwise they leak + return new AstUnpackArrayDType{typep->fileline(), + dtypeForWidth(typep->subDTypep()->width()), + typep->rangep()->cloneTree(false)}; + } return dtypeForWidth(nodep->width()); } @@ -257,6 +273,7 @@ public: // Width of result uint32_t width() const { // Everything supported is packed now, so we can just do this: + UASSERT_OBJ(VN_IS(dtypep(), BasicDType), this, "'width()' called on unpacked value"); return dtypep()->width(); } @@ -325,6 +342,8 @@ public: inline void forEachSourceEdge(std::function f) const; // Calls given function 'f' for each sink vertex of this vertex + // Unlinking/deleting the given sink during iteration is safe, but not other sinks of this + // vertex. inline void forEachSink(std::function f); // Calls given function 'f' for each sink vertex of this vertex @@ -338,6 +357,10 @@ public: // Calls given function 'f' for each sink edge of this vertex. inline void forEachSinkEdge(std::function f) const; + // Returns first source edge which satisfies the given predicate 'p', or nullptr if no such + // sink vertex exists + inline DfgEdge* findSourceEdge(std::function p); + // Returns first sink vertex of type 'Vertex' which satisfies the given predicate 'p', // or nullptr if no such sink vertex exists template @@ -600,7 +623,7 @@ public: } }; -class DfgVar final : public DfgVertexLValue { +class DfgVarPacked final : public DfgVertexLValue { friend class DfgVertex; friend class DfgVisitor; @@ -614,7 +637,7 @@ class DfgVar final : public DfgVertexLValue { static constexpr DfgType dfgType() { return DfgType::atVar; }; public: - DfgVar(DfgGraph& dfg, AstVar* varp) + DfgVarPacked(DfgGraph& dfg, AstVar* varp) : DfgVertexLValue{dfg, dfgType(), varp, 1u} {} bool isDrivenByDfg() const { return arity() > 0; } @@ -638,6 +661,43 @@ public: } }; +class DfgVarArray final : public DfgVertexLValue { + friend class DfgVertex; + friend class DfgVisitor; + + using DriverData = std::pair; + + std::vector m_driverData; // Additional data associate with each driver + + void accept(DfgVisitor& visitor) override; + bool selfEquals(const DfgVertex& that) const override; + V3Hash selfHash() const override; + static constexpr DfgType dfgType() { return DfgType::atUnpackArrayDType; }; // TODO: gross + +public: + DfgVarArray(DfgGraph& dfg, AstVar* varp) + : DfgVertexLValue{dfg, dfgType(), varp, 4u} { + UASSERT_OBJ(VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType), varp, "Non array DfgVarArray"); + } + + bool isDrivenByDfg() const { return arity() > 0; } + + void addDriver(FileLine* flp, uint32_t index, DfgVertex* vtxp) { + m_driverData.emplace_back(flp, index); + DfgVertexVariadic::addSource()->relinkSource(vtxp); + } + + void resetSources() { + m_driverData.clear(); + DfgVertexVariadic::resetSources(); + } + + FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; } + uint32_t driverIndex(size_t idx) const { return m_driverData[idx].second; } + + const string srcName(size_t idx) const override { return cvtToStr(driverIndex(idx)); } +}; + class DfgConst final : public DfgVertex { friend class DfgVertex; friend class DfgVisitor; @@ -685,7 +745,8 @@ public: // Dispatch to most specific 'visit' method on 'vtxp' void iterate(DfgVertex* vtxp) { vtxp->accept(*this); } - virtual void visit(DfgVar* vtxp); + virtual void visit(DfgVarPacked* vtxp); + virtual void visit(DfgVarArray* vtxp); virtual void visit(DfgConst* vtxp); #include "V3Dfg__gen_visitor_decls.h" }; @@ -746,7 +807,10 @@ void DfgVertex::forEachSource(std::function f) const { } void DfgVertex::forEachSink(std::function f) { - for (const DfgEdge* edgep = m_sinksp; edgep; edgep = edgep->m_nextp) f(*edgep->m_sinkp); + for (const DfgEdge *edgep = m_sinksp, *nextp; edgep; edgep = nextp) { + nextp = edgep->m_nextp; + f(*edgep->m_sinkp); + } } void DfgVertex::forEachSink(std::function f) const { @@ -781,6 +845,17 @@ void DfgVertex::forEachSinkEdge(std::function f) const { } } +DfgEdge* DfgVertex::findSourceEdge(std::function p) { + const auto pair = sourceEdges(); + DfgEdge* const edgesp = pair.first; + const size_t arity = pair.second; + for (size_t i = 0; i < arity; ++i) { + DfgEdge& edge = edgesp[i]; + if (p(edge, i)) return &edge; + } + return nullptr; +} + template Vertex* DfgVertex::findSink(std::function p) const { static_assert(std::is_base_of::value, diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index 798686581..763a1656d 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -35,8 +35,6 @@ #include "V3Error.h" #include "V3Global.h" -#include - VL_DEFINE_DEBUG_FUNCTIONS; namespace { @@ -88,7 +86,8 @@ class AstToDfgVisitor final : public VNVisitor { bool m_foundUnhandled = false; // Found node not implemented as DFG or not implemented 'visit' std::vector m_uncommittedVertices; // Vertices that we might decide to revert bool m_converting = false; // We are trying to convert some logic at the moment - std::vector m_varps; // All the DfgVar vertices we created. + std::vector m_varPackedps; // All the DfgVarPacked vertices we created. + std::vector m_varArrayps; // All the DfgVarArray vertices we created. // METHODS void markReferenced(AstNode* nodep) { @@ -106,18 +105,25 @@ class AstToDfgVisitor final : public VNVisitor { m_uncommittedVertices.clear(); } - DfgVar* getNet(AstVar* varp) { + DfgVertexLValue* getNet(AstVar* varp) { if (!varp->user1p()) { - // Note DfgVar vertices are not added to m_uncommittedVertices, because we want to - // hold onto them via AstVar::user1p, and the AstVar which might be referenced via - // multiple AstVarRef instances, so we will never revert a DfgVar once created. This - // means we can end up with DfgVar vertices in the graph which have no connections at - // all (which is fine for later processing). - DfgVar* const vtxp = new DfgVar{*m_dfgp, varp}; - m_varps.push_back(vtxp); - varp->user1p(vtxp); + // Note DfgVertexLValue vertices are not added to m_uncommittedVertices, because we + // want to hold onto them via AstVar::user1p, and the AstVar might be referenced via + // multiple AstVarRef instances, so we will never revert a DfgVertexLValue once + // created. This means we can end up with DfgVertexLValue vertices in the graph which + // have no connections at all (which is fine for later processing). + if (VN_IS(varp->dtypep()->skipRefp(), UnpackArrayDType)) { + DfgVarArray* const vtxp = new DfgVarArray{*m_dfgp, varp}; + varp->user1p(); + m_varArrayps.push_back(vtxp); + varp->user1p(vtxp); + } else { + DfgVarPacked* const vtxp = new DfgVarPacked{*m_dfgp, varp}; + m_varPackedps.push_back(vtxp); + varp->user1p(vtxp); + } } - return varp->user1u().to(); + return varp->user1u().to(); } DfgVertex* getVertex(AstNode* nodep) { @@ -150,12 +156,12 @@ class AstToDfgVisitor final : public VNVisitor { m_foundUnhandled = false; visit(vrefp); if (m_foundUnhandled) return false; - getVertex(vrefp)->as()->addDriver(flp, 0, vtxp); + getVertex(vrefp)->as()->addDriver(flp, 0, vtxp); return true; } if (AstSel* const selp = VN_CAST(nodep, Sel)) { AstVarRef* const vrefp = VN_CAST(selp->fromp(), VarRef); - AstConst* const lsbp = VN_CAST(selp->lsbp(), Const); + const AstConst* const lsbp = VN_CAST(selp->lsbp(), Const); if (!vrefp || !lsbp || !VN_IS(selp->widthp(), Const)) { ++m_ctx.m_nonRepLhs; return false; @@ -163,7 +169,20 @@ class AstToDfgVisitor final : public VNVisitor { m_foundUnhandled = false; visit(vrefp); if (m_foundUnhandled) return false; - getVertex(vrefp)->as()->addDriver(flp, lsbp->toUInt(), vtxp); + getVertex(vrefp)->as()->addDriver(flp, lsbp->toUInt(), vtxp); + return true; + } + if (AstArraySel* const selp = VN_CAST(nodep, ArraySel)) { + AstVarRef* const vrefp = VN_CAST(selp->fromp(), VarRef); + const AstConst* const idxp = VN_CAST(selp->bitp(), Const); + if (!vrefp || !idxp) { + ++m_ctx.m_nonRepLhs; + return false; + } + m_foundUnhandled = false; + visit(vrefp); + if (m_foundUnhandled) return false; + getVertex(vrefp)->as()->addDriver(flp, idxp->toUInt(), vtxp); return true; } if (AstConcat* const concatp = VN_CAST(nodep, Concat)) { @@ -197,6 +216,15 @@ class AstToDfgVisitor final : public VNVisitor { bool convertEquation(AstNode* nodep, AstNode* lhsp, AstNode* rhsp) { UASSERT_OBJ(m_uncommittedVertices.empty(), nodep, "Should not nest"); + // Currently cannot handle direct assignments between unpacked types. These arise e.g. + // when passing an unpacked array through a module port. + if (!DfgVertex::isSupportedPackedDType(lhsp->dtypep()) + || !DfgVertex::isSupportedPackedDType(rhsp->dtypep())) { + markReferenced(nodep); + ++m_ctx.m_nonRepDType; + return false; + } + // Cannot handle mismatched widths. Mismatched assignments should have been fixed up in // earlier passes anyway, so this should never be hit, but being paranoid just in case. if (lhsp->width() != rhsp->width()) { // LCOV_EXCL_START @@ -233,6 +261,73 @@ class AstToDfgVisitor final : public VNVisitor { return true; } + // Canonicalize packed variables + void canonicalizePacked() { + for (DfgVarPacked* const varp : m_varPackedps) { + // Gather (and unlink) all drivers + struct Driver { + FileLine* flp; + uint32_t lsb; + DfgVertex* vtxp; + Driver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp) + : flp{flp} + , lsb{lsb} + , vtxp{vtxp} {} + }; + std::vector drivers; + drivers.reserve(varp->arity()); + varp->forEachSourceEdge([varp, &drivers](DfgEdge& edge, size_t idx) { + UASSERT(edge.sourcep(), "Should not have created undriven sources"); + drivers.emplace_back(varp->driverFileLine(idx), varp->driverLsb(idx), + edge.sourcep()); + edge.unlinkSource(); + }); + + // Sort drivers by LSB + std::stable_sort(drivers.begin(), drivers.end(), + [](const Driver& a, const Driver& b) { return a.lsb < b.lsb; }); + + // TODO: bail on multidriver + + // Coalesce adjacent ranges + for (size_t i = 0, j = 1; j < drivers.size(); ++j) { + Driver& a = drivers[i]; + Driver& b = drivers[j]; + + // Coalesce adjacent range + const uint32_t aWidth = a.vtxp->width(); + const uint32_t bWidth = b.vtxp->width(); + if (a.lsb + aWidth == b.lsb) { + const auto dtypep = DfgVertex::dtypeForWidth(aWidth + bWidth); + DfgConcat* const concatp = new DfgConcat{*m_dfgp, a.flp, dtypep}; + concatp->rhsp(a.vtxp); + concatp->lhsp(b.vtxp); + a.vtxp = concatp; + b.vtxp = nullptr; // Mark as moved + ++m_ctx.m_coalescedAssignments; + continue; + } + + ++i; + + // Compact non-adjacent ranges within the vector + if (j != i) { + Driver& c = drivers[i]; + UASSERT_OBJ(!c.vtxp, c.flp, "Should have been marked moved"); + c = b; + b.vtxp = nullptr; // Mark as moved + } + } + + // Reinsert sources in order + varp->resetSources(); + for (const Driver& driver : drivers) { + if (!driver.vtxp) break; // Stop at end of cmpacted list + varp->addDriver(driver.flp, driver.lsb, driver.vtxp); + } + } + } + // VISITORS void visit(AstNode* nodep) override { // Conservatively treat this node as unhandled @@ -298,7 +393,7 @@ class AstToDfgVisitor final : public VNVisitor { nodep->user1p(vtxp); } - // The rest of the 'visit' methods are generated by 'astgen' +// The rest of the 'visit' methods are generated by 'astgen' #include "V3Dfg__gen_ast_to_dfg.h" // CONSTRUCTOR @@ -309,70 +404,8 @@ class AstToDfgVisitor final : public VNVisitor { iterateChildren(&module); UASSERT_OBJ(m_uncommittedVertices.empty(), &module, "Uncommitted vertices remain"); - // Canonicalize variable assignments - for (DfgVar* const varp : m_varps) { - // Gather (and unlink) all drivers - struct Driver { - FileLine* flp; - uint32_t lsb; - DfgVertex* vtxp; - Driver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp) - : flp{flp} - , lsb{lsb} - , vtxp{vtxp} {} - }; - std::vector drivers; - drivers.reserve(varp->arity()); - varp->forEachSourceEdge([varp, &drivers](DfgEdge& edge, size_t idx) { - UASSERT(edge.sourcep(), "Should not have created undriven sources"); - drivers.emplace_back(varp->driverFileLine(idx), varp->driverLsb(idx), - edge.sourcep()); - edge.unlinkSource(); - }); - - // Sort drivers by LSB - std::stable_sort(drivers.begin(), drivers.end(), - [](const Driver& a, const Driver& b) { return a.lsb < b.lsb; }); - - // TODO: bail on multidriver - - // Coalesce adjacent ranges - for (size_t i = 0, j = 1; j < drivers.size(); ++j) { - Driver& a = drivers[i]; - Driver& b = drivers[j]; - - // Coalesce adjacent range - const uint32_t aWidth = a.vtxp->width(); - const uint32_t bWidth = b.vtxp->width(); - if (a.lsb + aWidth == b.lsb) { - const auto dtypep = DfgVertex::dtypeForWidth(aWidth + bWidth); - DfgConcat* const concatp = new DfgConcat{*m_dfgp, a.flp, dtypep}; - concatp->rhsp(a.vtxp); - concatp->lhsp(b.vtxp); - a.vtxp = concatp; - b.vtxp = nullptr; // Mark as moved - ++m_ctx.m_coalescedAssignments; - continue; - } - - ++i; - - // Compact non-adjacent ranges within the vector - if (j != i) { - Driver& c = drivers[i]; - UASSERT_OBJ(!c.vtxp, c.flp, "Should have been marked moved"); - c = b; - b.vtxp = nullptr; // Mark as moved - } - } - - // Reinsert sources in order - varp->resetSources(); - for (const Driver& driver : drivers) { - if (!driver.vtxp) break; // Stop at end of cmpacted list - varp->addDriver(driver.flp, driver.lsb, driver.vtxp); - } - } + // Canonicalize variables + canonicalizePacked(); } public: diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index 8a0c998c9..8c9e13b91 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -15,14 +15,14 @@ //************************************************************************* // // Convert DfgGraph back to AstModule. We recursively construct AstNodeMath expressions for each -// DfgVertex which represents a storage location (e.g.: DfgVar), or has multiple sinks without -// driving a storage location (and hence needs a temporary variable to duplication). The recursion -// stops when we reach a DfgVertex representing a storage location (e.g.: DfgVar), or a vertex that -// that has multiple sinks (as these nodes will have a [potentially new temporary] corresponding -// storage location). Redundant variables (those whose source vertex drives multiple variables) are -// eliminated when possible. Vertices driving multiple variables are rendered once, driving an -// arbitrarily (but deterministically) chosen canonical variable, and the corresponding redundant -// variables are assigned from the canonical variable. +// DfgVertex which represents a storage location (e.g.: DfgVarPacked), or has multiple sinks +// without driving a storage location (and hence needs a temporary variable to duplication). The +// recursion stops when we reach a DfgVertex representing a storage location (e.g.: DfgVarPacked), +// or a vertex that that has multiple sinks (as these nodes will have a [potentially new temporary] +// corresponding// storage location). Redundant variables (those whose source vertex drives +// multiple variables) are eliminated when possible. Vertices driving multiple variables are +// rendered once, driving an arbitrarily (but deterministically) chosen canonical variable, and the +// corresponding redundant variables are assigned from the canonical variable. // //************************************************************************* @@ -110,6 +110,11 @@ AstSliceSel* makeNode( } // namespace class DfgToAstVisitor final : DfgVisitor { + // NODE STATE + // AstVar::user1() bool: this is a temporary we are introducing + + const VNUser1InUse m_inuser1; + // STATE AstModule* const m_modp; // The parent/result module @@ -124,9 +129,9 @@ class DfgToAstVisitor final : DfgVisitor { // METHODS - // Given a DfgVar, return the canonical AstVar that can be used for this DfgVar. + // Given a DfgVarPacked, return the canonical AstVar that can be used for this DfgVarPacked. // Also builds the m_canonVars map as a side effect. - AstVar* getCanonicalVar(const DfgVar* vtxp) { + AstVar* getCanonicalVar(const DfgVarPacked* vtxp) { // If variable driven (at least partially) outside the DFG, then we have no choice if (!vtxp->isDrivenFullyByDfg()) return vtxp->varp(); @@ -135,24 +140,25 @@ class DfgToAstVisitor final : DfgVisitor { if (it != m_canonVars.end()) return it->second; // Not known yet, compute it (for all vars driven fully from the same driver) - std::vector varps; + std::vector varps; vtxp->source(0)->forEachSink([&](const DfgVertex& vtx) { - if (const DfgVar* const varVtxp = vtx.cast()) { + if (const DfgVarPacked* const varVtxp = vtx.cast()) { if (varVtxp->isDrivenFullyByDfg()) varps.push_back(varVtxp); } }); UASSERT_OBJ(!varps.empty(), vtxp, "The input vtxp is always available"); - std::stable_sort(varps.begin(), varps.end(), [](const DfgVar* ap, const DfgVar* bp) { - if (ap->hasExtRefs() != bp->hasExtRefs()) return ap->hasExtRefs(); - const FileLine& aFl = *(ap->fileline()); - const FileLine& bFl = *(bp->fileline()); - if (const int cmp = aFl.operatorCompare(bFl)) return cmp < 0; - return ap->varp()->name() < bp->varp()->name(); - }); + std::stable_sort(varps.begin(), varps.end(), + [](const DfgVarPacked* ap, const DfgVarPacked* bp) { + if (ap->hasExtRefs() != bp->hasExtRefs()) return ap->hasExtRefs(); + const FileLine& aFl = *(ap->fileline()); + const FileLine& bFl = *(bp->fileline()); + if (const int cmp = aFl.operatorCompare(bFl)) return cmp < 0; + return ap->varp()->name() < bp->varp()->name(); + }); AstVar* const canonVarp = varps.front()->varp(); // Add results to map - for (const DfgVar* const varp : varps) m_canonVars.emplace(varp->varp(), canonVarp); + for (const DfgVarPacked* const varp : varps) m_canonVars.emplace(varp->varp(), canonVarp); // Return it return canonVarp; @@ -164,18 +170,21 @@ class DfgToAstVisitor final : DfgVisitor { const auto pair = m_resultVars.emplace(vtxp, nullptr); AstVar*& varp = pair.first->second; if (pair.second) { - // If this vertex is a DfgVar, then we know the variable. If this node is not a DfgVar, - // then first we try to find a DfgVar driven by this node, and use that, otherwise we - // create a temporary - if (const DfgVar* const thisDfgVarp = vtxp->cast()) { - // This is a DfgVar - varp = getCanonicalVar(thisDfgVarp); - } else if (const DfgVar* const sinkDfgVarp = vtxp->findSink( - [](const DfgVar& var) { return var.isDrivenFullyByDfg(); })) { - // We found a DfgVar driven fully by this node - varp = getCanonicalVar(sinkDfgVarp); + // If this vertex is a DfgVarPacked, then we know the variable. If this node is not a + // DfgVarPacked, then first we try to find a DfgVarPacked driven by this node, and use + // that, otherwise we create a temporary + if (const DfgVarPacked* const thisDfgVarPackedp = vtxp->cast()) { + // This is a DfgVarPacked + varp = getCanonicalVar(thisDfgVarPackedp); + } else if (const DfgVarArray* const thisDfgVarArrayp = vtxp->cast()) { + // This is a DfgVarArray + varp = thisDfgVarArrayp->varp(); + } else if (const DfgVarPacked* const sinkDfgVarPackedp = vtxp->findSink( + [](const DfgVarPacked& var) { return var.isDrivenFullyByDfg(); })) { + // We found a DfgVarPacked driven fully by this node + varp = getCanonicalVar(sinkDfgVarPackedp); } else { - // No DfgVar driven fully by this node. Create a temporary. + // No DfgVarPacked driven fully by this node. Create a temporary. // TODO: should we reuse parts when the AstVar is used as an rvalue? const string name = m_tmpNames.get(vtxp->hash(m_hashCache).toString()); // Note: It is ok for these temporary variables to be always unsigned. They are @@ -184,6 +193,7 @@ class DfgToAstVisitor final : DfgVisitor { AstNodeDType* const dtypep = v3Global.rootp()->findBitDType( vtxp->width(), vtxp->width(), VSigning::UNSIGNED); varp = new AstVar{vtxp->fileline(), VVarType::MODULETEMP, name, dtypep}; + varp->user1(true); // Mark as temporary // Add temporary AstVar to containing module m_modp->addStmtsp(varp); } @@ -201,18 +211,30 @@ class DfgToAstVisitor final : DfgVisitor { return resultp; } + bool inlineVertex(DfgVertex& vtx) { + // Inline vertices that drive only a single node, or are special + if (!vtx.hasMultipleSinks()) return true; + if (vtx.is()) return true; + if (vtx.is()) return true; + if (vtx.is()) return true; + if (const DfgArraySel* const selp = vtx.cast()) { + return selp->bitp()->is(); + } + return false; + } + AstNodeMath* convertSource(DfgVertex* vtxp) { - if (vtxp->hasMultipleSinks()) { - // Vertices with multiple sinks need a temporary variable, just return a reference - return new AstVarRef{vtxp->fileline(), getResultVar(vtxp), VAccess::READ}; - } else { - // Vertex with single sink is simply recursively converted + if (inlineVertex(*vtxp)) { + // Inlined vertices are simply recursively converted UASSERT_OBJ(vtxp->hasSinks(), vtxp, "Must have one sink: " << vtxp->typeName()); return convertDfgVertexToAstNodeMath(vtxp); + } else { + // Vertices that are not inlined need a variable, just return a reference + return new AstVarRef{vtxp->fileline(), getResultVar(vtxp), VAccess::READ}; } } - void convertCanonicalVarDriver(const DfgVar* dfgVarp) { + void convertCanonicalVarDriver(const DfgVarPacked* dfgVarp) { const auto wRef = [dfgVarp]() { return new AstVarRef{dfgVarp->fileline(), dfgVarp->varp(), VAccess::WRITE}; }; @@ -237,7 +259,7 @@ class DfgToAstVisitor final : DfgVisitor { } } - void convertDuplicateVarDriver(const DfgVar* dfgVarp, AstVar* canonVarp) { + void convertDuplicateVarDriver(const DfgVarPacked* dfgVarp, AstVar* canonVarp) { const auto rRef = [canonVarp]() { return new AstVarRef{canonVarp->fileline(), canonVarp, VAccess::READ}; }; @@ -263,6 +285,22 @@ class DfgToAstVisitor final : DfgVisitor { } } + void convertArrayDiver(const DfgVarArray* dfgVarp) { + // Variable is driven partially. Asign from parts of the canonical var. + dfgVarp->forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { + UASSERT_OBJ(edge.sourcep(), dfgVarp, "Should have removed undriven sources"); + // Render the rhs expression + AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(edge.sourcep()); + // Create select LValue + FileLine* const flp = dfgVarp->driverFileLine(idx); + AstVarRef* const refp = new AstVarRef{flp, dfgVarp->varp(), VAccess::WRITE}; + AstConst* const idxp = new AstConst{flp, dfgVarp->driverIndex(idx)}; + AstArraySel* const lhsp = new AstArraySel{flp, refp, idxp}; + // Add assignment of the value to the selected bits + addResultEquation(flp, lhsp, rhsp); + }); + } + void addResultEquation(FileLine* flp, AstNode* lhsp, AstNode* rhsp) { m_modp->addStmtsp(new AstAssignW{flp, lhsp, rhsp}); ++m_ctx.m_resultEquations; @@ -273,10 +311,14 @@ class DfgToAstVisitor final : DfgVisitor { vtxp->v3fatal("Unhandled DfgVertex: " << vtxp->typeName()); } // LCOV_EXCL_STOP - void visit(DfgVar* vtxp) override { + void visit(DfgVarPacked* vtxp) override { m_resultp = new AstVarRef{vtxp->fileline(), getCanonicalVar(vtxp), VAccess::READ}; } + void visit(DfgVarArray* vtxp) override { + m_resultp = new AstVarRef{vtxp->fileline(), vtxp->varp(), VAccess::READ}; + } + void visit(DfgConst* vtxp) override { // m_resultp = vtxp->constp()->cloneTree(false); } @@ -293,13 +335,13 @@ class DfgToAstVisitor final : DfgVisitor { // Convert vertices back to assignments dfg.forEachVertex([&](DfgVertex& vtx) { - // Render variable assignments - if (const DfgVar* const dfgVarp = vtx.cast()) { - // DfgVar instances (these might be driving the given AstVar variable) - // If there is no driver (i.e.: this DfgVar is an input to the Dfg), then + // Render packed variable assignments + if (const DfgVarPacked* const dfgVarp = vtx.cast()) { + // DfgVarPacked instances (these might be driving the given AstVar variable) + // If there is no driver (i.e.: this DfgVarPacked is an input to the Dfg), then // nothing to do if (!dfgVarp->isDrivenByDfg()) return; - // The driver of this DfgVar might drive multiple variables. Only emit one + // The driver of this DfgVarPacked might drive multiple variables. Only emit one // assignment from the driver to an arbitrarily chosen canonical variable, and // assign the other variables from that canonical variable AstVar* const canonVarp = getCanonicalVar(dfgVarp); @@ -318,23 +360,31 @@ class DfgToAstVisitor final : DfgVisitor { return; } - // Vertices driving a single vertex will be in-lined by 'convertDfgVertexToAstNodeMath' - if (!vtx.hasMultipleSinks()) return; + // Render array variable assignments + if (const DfgVarArray* dfgVarp = vtx.cast()) { + // If there is no driver, then there is nothing to do + if (!dfgVarp->isDrivenByDfg()) return; + // We don't canonicalize arrays, so just render the drivers + convertArrayDiver(dfgVarp); + // + return; + } - // Vertices with multiple sinks needs a temporary if they do not fully drive a DfgVar - const bool needsTemporary = !vtx.findSink([](const DfgVar& var) { // - return var.isDrivenFullyByDfg(); - }); - if (needsTemporary) { - // DfgVertex that has multiple sinks, but does not drive a DfgVar (needs temporary) + // If the vertex is known to be inlined, then nothing else to do + if (inlineVertex(vtx)) return; + + // Check if this uses a temporary, vs one of the vars rendered above + AstVar* const resultVarp = getResultVar(&vtx); + if (resultVarp->user1()) { + // We introduced a temporary for this DfgVertex ++m_ctx.m_intermediateVars; + FileLine* const flp = vtx.fileline(); // Just render the logic AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(&vtx); // The lhs is a temporary - AstNodeMath* const lhsp - = new AstVarRef{vtx.fileline(), getResultVar(&vtx), VAccess::WRITE}; + AstNodeMath* const lhsp = new AstVarRef{flp, resultVarp, VAccess::WRITE}; // Add assignment of the value to the variable - addResultEquation(vtx.fileline(), lhsp, rhsp); + addResultEquation(flp, lhsp, rhsp); } }); diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 77e8392ca..0c975fa98 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -103,8 +103,8 @@ void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) { void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { dfg.forEachVertex([&](DfgVertex& vtx) { - // We can eliminate certain redundant DfgVar vertices - DfgVar* const varp = vtx.cast(); + // We can eliminate certain redundant DfgVarPacked vertices + DfgVarPacked* const varp = vtx.cast(); if (!varp) return; // Can't remove if it has consumers @@ -125,9 +125,9 @@ void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { if (varp->isDrivenByDfg()) { DfgVertex* const driverp = varp->source(0); unsigned nonVarSinks = 0; - const DfgVar* firstSinkVarp = nullptr; + const DfgVarPacked* firstSinkVarp = nullptr; const bool keepFirst = driverp->findSink([&](const DfgVertex& sink) { - if (const DfgVar* const sinkVarp = sink.cast()) { + if (const DfgVarPacked* const sinkVarp = sink.cast()) { if (!firstSinkVarp) firstSinkVarp = sinkVarp; } else { ++nonVarSinks; @@ -135,11 +135,11 @@ void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { // We can stop as soon as we found the first var, and 2 non-var sinks return firstSinkVarp && nonVarSinks >= 2; }); - // Keep this DfgVar if needed + // Keep this DfgVarPacked if needed if (keepFirst && firstSinkVarp == varp) return; } - // OK, we can delete this DfgVar + // OK, we can delete this DfgVarPacked ++ctx.m_removed; // If not referenced outside the DFG, then also delete the referenced AstVar, @@ -154,7 +154,7 @@ void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { void V3DfgPasses::removeUnused(DfgGraph& dfg) { const auto processVertex = [&](DfgVertex& vtx) { // Keep variables - if (vtx.is()) return false; + if (vtx.is() || vtx.is()) return false; // Keep if it has sinks if (vtx.hasSinks()) return false; // Unlink and delete vertex diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 12804f137..19f98314c 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -120,9 +120,9 @@ class V3DfgPeephole final : public DfgVisitor { } // If both sides are variable references, order the side in some defined way. This allows // CSE to later merge 'a op b' with 'b op a'. - if (lhsp->is() && rhsp->is()) { - AstVar* const lVarp = lhsp->as()->varp(); - AstVar* const rVarp = rhsp->as()->varp(); + if (lhsp->is() && rhsp->is()) { + AstVar* const lVarp = lhsp->as()->varp(); + AstVar* const rVarp = rhsp->as()->varp(); if (lVarp->name() > rVarp->name()) { APPLYING(SWAP_VAR_IN_COMMUTATIVE_BINARY) { vtxp->lhsp(rhsp); @@ -1102,7 +1102,7 @@ class V3DfgPeephole final : public DfgVisitor { } } - void visit(DfgVar* vtxp) override { + void visit(DfgVarPacked* vtxp) override { // Inline variables fully driven by the logic represented by the DFG if (vtxp->hasSinks() && vtxp->isDrivenFullyByDfg()) { APPLYING(INLINE_VAR) { @@ -1122,7 +1122,7 @@ class V3DfgPeephole final : public DfgVisitor { // multiple sinks (otherwise we would need to introduce a temporary, but it is better for // debugging to keep the original variable name, if one is available), so we can't remove // redundant variables here. - const bool keep = vtx.is(); + const bool keep = vtx.is() || vtx.is(); // If it has no sinks (unused), we can remove it if (!keep && !vtx.hasSinks()) { From c9d6344f2f561a551d6dcf37f26c2756b15dbd82 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 28 Sep 2022 14:42:18 +0100 Subject: [PATCH 060/177] DFG: Extract cyclic components separately A lot of optimizations in DFG assume a DAG, but the more things are representable, the more likely it is that a small cyclic sub-graph is present in an otherwise very large graph that is mostly acyclic. In order to avoid loosing optimization opportunities, we explicitly extract the cyclic sub-graphs (which are the strongly connected components + anything feeing them, up to variable boundaries) and treat them separately. This enables optimization of the remaining input. --- src/V3Dfg.cpp | 358 +++++++++++++++++++++++++++++++- src/V3Dfg.h | 62 +++++- src/V3DfgOptimizer.cpp | 42 ++-- test_regress/t/t_dfg_circular.v | 37 +++- 4 files changed, 469 insertions(+), 30 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 184fdb874..a6bdb2411 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -22,8 +22,11 @@ #include "V3File.h" #include +#include #include +VL_DEFINE_DEBUG_FUNCTIONS; + //------------------------------------------------------------------------------ // DfgGraph //------------------------------------------------------------------------------ @@ -114,7 +117,7 @@ bool DfgGraph::sortTopologically(bool reverse) { return true; } -std::vector> DfgGraph::splitIntoComponents() { +std::vector> DfgGraph::splitIntoComponents(std::string label) { size_t componentNumber = 0; std::unordered_map vertex2component; @@ -149,8 +152,10 @@ std::vector> DfgGraph::splitIntoComponents() { // Create the component graphs std::vector> results{componentNumber}; + const std::string prefix{name() + (label.empty() ? "" : "-") + label + "-component-"}; + for (size_t i = 0; i < componentNumber; ++i) { - results[i].reset(new DfgGraph{*m_modulep, name() + "-component-" + cvtToStr(i)}); + results[i].reset(new DfgGraph{*m_modulep, prefix + cvtToStr(i)}); } // Move all vertices under the corresponding component graphs @@ -164,6 +169,351 @@ std::vector> DfgGraph::splitIntoComponents() { return results; } +class ExtractCyclicComponents final { + static constexpr size_t UNASSIGNED = std::numeric_limits::max(); + + // TYPES + struct VertexState { + size_t index; // Used by Pearce's algorithm for detecting SCCs + size_t component = UNASSIGNED; // Result component number (0 stays in input graph) + VertexState(size_t index) + : index{index} {} + }; + + // STATE + + //========================================================================== + // Shared state + + DfgGraph& m_dfg; // The input graph + const std::string m_prefix; // Component name prefix + std::unordered_map m_state; // Vertex state + size_t m_nonTrivialSCCs = 0; // Number of non-trivial SCCs in the graph + const bool m_doExpensiveChecks = v3Global.opt.debugCheck(); + + //========================================================================== + // State for Pearce's algorithm for detecting SCCs + + size_t m_index = 0; // Visitation index counter + std::vector m_stack; // The stack used by the algorithm + + //========================================================================== + // State for merging + + std::unordered_set m_merged; // Marks visited vertices + + //========================================================================== + // State for extraction + + // The extracted cyclic components + std::vector> m_components; + // Map from 'variable vertex' -> 'component index' -> 'clone in that component' + std::unordered_map> + m_clones; + + // METHODS + + //========================================================================== + // Methods for Pearce's algorithm to detect strongly connected components + + void visitColorSCCs(DfgVertex& vtx) { + const auto pair = m_state.emplace(std::piecewise_construct, // + std::forward_as_tuple(&vtx), // + std::forward_as_tuple(m_index)); + + // If already visited, then nothing to do + if (!pair.second) return; + + // Visiting node + const size_t rootIndex = m_index++; + + vtx.forEachSink([&](DfgVertex& child) { + // Visit child + visitColorSCCs(child); + auto& childSatate = m_state.at(&child); + // If the child is not in an SCC + if (childSatate.component == UNASSIGNED) { + auto& vtxState = m_state.at(&vtx); + if (vtxState.index > childSatate.index) vtxState.index = childSatate.index; + } + }); + + auto& vtxState = m_state.at(&vtx); + if (vtxState.index == rootIndex) { + // This is the 'root' of an SCC + + // A trivial SCC contains only a single vertex + const bool isTrivial = m_stack.empty() || m_state.at(m_stack.back()).index < rootIndex; + // We also need a separate component for vertices that drive themselves (which can + // happen for input like 'assign a = a'), as we want to extract them (they are cyclic). + const bool drivesSelf = vtx.findSink([&vtx](const DfgVertex& sink) { // + return &vtx == &sink; + }); + + if (!isTrivial || drivesSelf) { + // Allocate new component + ++m_nonTrivialSCCs; + vtxState.component = m_nonTrivialSCCs; + while (!m_stack.empty()) { + DfgVertex* const topp = m_stack.back(); + auto& topState = m_state.at(topp); + // Only higher nodes belong to the same SCC + if (topState.index < rootIndex) break; + m_stack.pop_back(); + topState.component = m_nonTrivialSCCs; + } + } else { + // Trivial SCC (and does not drive itself), so acyclic. Keep it in original graph. + vtxState.component = 0; + } + } else { + // Not the root of an SCC + m_stack.push_back(&vtx); + } + } + + void colorSCCs() { + // Implements Pearce's algorithm to color the strongly connected components. For reference + // see "An Improved Algorithm for Finding the Strongly Connected Components of a Directed + // Graph", David J.Pearce, 2005 + m_state.reserve(m_dfg.size()); + m_dfg.forEachVertex([&](DfgVertex& vtx) { visitColorSCCs(vtx); }); + } + + //========================================================================== + // Methods for merging + + void visitMergeSCCs(const DfgVertex& vtx, size_t targetComponent) { + // We stop at variable boundaries, which is where we will split the graphs + if (vtx.is() || vtx.is()) return; + + // Mark visited/move on if already visited + if (!m_merged.insert(&vtx).second) return; + + // Assign vertex to the target component + m_state.at(&vtx).component = targetComponent; + + // Visit all neighbours + vtx.forEachSource([=](const DfgVertex& other) { visitMergeSCCs(other, targetComponent); }); + vtx.forEachSink([=](const DfgVertex& other) { visitMergeSCCs(other, targetComponent); }); + } + + void mergeSCCs() { + // Ensure that component boundaries are always at variables, by merging SCCs + m_merged.reserve(m_dfg.size()); + m_dfg.forEachVertex([this](DfgVertex& vtx) { + // Start DFS from each vertex that is in a non-trivial SCC, and merge everything that + // is reachable from it into this component. + if (const size_t target = m_state.at(&vtx).component) visitMergeSCCs(vtx, target); + }); + } + + //========================================================================== + // Methods for extraction + + // Retrieve clone of vertex in the given component + DfgVertexLValue& getClone(DfgVertexLValue& vtx, size_t component) { + UASSERT_OBJ(m_state.at(&vtx).component != component, &vtx, "Vertex is in that component"); + DfgVertexLValue*& clonep = m_clones[&vtx][component]; + if (!clonep) { + DfgGraph& dfg = component == 0 ? m_dfg : *m_components[component - 1]; + if (DfgVarPacked* const pVtxp = vtx.cast()) { + clonep = new DfgVarPacked{dfg, pVtxp->varp()}; + } else if (DfgVarArray* const aVtxp = vtx.cast()) { + clonep = new DfgVarArray{dfg, aVtxp->varp()}; + } + UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexLValue' sub-type"); + if (VL_UNLIKELY(m_doExpensiveChecks)) { + // Assign component number of clone for later checks + m_state + .emplace(std::piecewise_construct, std::forward_as_tuple(clonep), + std::forward_as_tuple(0)) + .first->second.component + = component; + } + // We need to mark both the original and the clone as having additional references + vtx.setHasModRefs(); + clonep->setHasModRefs(); + } + return *clonep; + } + + // Fix up non-variable sources of a DfgVertexLValue that are in a different component, + // using the provided 'relink' callback + template + void fixSources(T_Vertex& vtx, std::function relink) { + static_assert(std::is_base_of::value, + "'Vertex' must be a 'DfgVertexLValue'"); + const size_t component = m_state.at(&vtx).component; + vtx.forEachSourceEdge([&](DfgEdge& edge, size_t idx) { + DfgVertex& source = *edge.sourcep(); + // DfgVertexLValue sources are fixed up by `fixSinks` on those sources + if (source.is() || source.is()) return; + const size_t sourceComponent = m_state.at(&source).component; + // Same component is OK + if (sourceComponent == component) return; + // Unlink the source edge (source is reconnected by 'relink' + edge.unlinkSource(); + // Apply the fixup + DfgVertexLValue& clone = getClone(vtx, sourceComponent); + relink(*(clone.as()), source, idx); + }); + } + + // Fix up sinks of given variable vertex that are in a different component + void fixSinks(DfgVertexLValue& vtx) { + const size_t component = m_state.at(&vtx).component; + vtx.forEachSinkEdge([&](DfgEdge& edge) { + const size_t sinkComponent = m_state.at(edge.sinkp()).component; + // Same component is OK + if (sinkComponent == component) return; + // Relink the sink to read the clone + edge.relinkSource(&getClone(vtx, sinkComponent)); + }); + } + + // Fix edges that cross components + void fixEdges(DfgVertex& vtx) { + if (DfgVarPacked* const vvtxp = vtx.cast()) { + fixSources( + *vvtxp, [&](DfgVarPacked& clone, DfgVertex& driver, size_t driverIdx) { + clone.addDriver(vvtxp->driverFileLine(driverIdx), // + vvtxp->driverLsb(driverIdx), &driver); + }); + fixSinks(*vvtxp); + return; + } + + if (DfgVarArray* const vvtxp = vtx.cast()) { + fixSources( // + *vvtxp, [&](DfgVarArray& clone, DfgVertex& driver, size_t driverIdx) { + clone.addDriver(vvtxp->driverFileLine(driverIdx), // + vvtxp->driverIndex(driverIdx), &driver); + }); + fixSinks(*vvtxp); + return; + } + + if (VL_UNLIKELY(m_doExpensiveChecks)) { + // Non-variable vertex. Just check that edges do not cross components + const size_t component = m_state.at(&vtx).component; + vtx.forEachSourceEdge([&](DfgEdge& edge, size_t) { + DfgVertex& source = *edge.sourcep(); + // OK to cross at variables + if (source.is() || source.is()) return; + UASSERT_OBJ(component == m_state.at(&source).component, &vtx, + "Component crossing edge without variable involvement"); + }); + } + } + + static void packSources(DfgGraph& dfg) { + // Remove undriven variable sources + dfg.forEachVertex([&](DfgVertex& vtx) { + if (DfgVarPacked* const vtxp = vtx.cast()) { + vtxp->packSources(); + return; + } + if (DfgVarArray* const vtxp = vtx.cast()) { + vtxp->packSources(); + return; + } + }); + } + + static void checkEdges(DfgGraph& dfg) { + // Check that each edge connects to a vertex that is within the same graph. + // Also check variable vertex sources are all connected. + std::unordered_set vertices{dfg.size()}; + dfg.forEachVertex([&](const DfgVertex& vtx) { vertices.insert(&vtx); }); + dfg.forEachVertex([&](const DfgVertex& vtx) { + vtx.forEachSource([&](const DfgVertex& src) { + UASSERT_OBJ(vertices.count(&src), &vtx, "Source vertex not in graph"); + }); + vtx.forEachSink([&](const DfgVertex& snk) { + UASSERT_OBJ(vertices.count(&snk), &snk, "Sink vertex not in graph"); + }); + if (const DfgVarPacked* const vtxp = vtx.cast()) { + vtxp->forEachSourceEdge([](const DfgEdge& edge, size_t) { + UASSERT_OBJ(edge.sourcep(), edge.sinkp(), "Missing source on variable vertex"); + }); + return; + } + if (const DfgVarArray* const vtxp = vtx.cast()) { + vtxp->forEachSourceEdge([](const DfgEdge& edge, size_t) { + UASSERT_OBJ(edge.sourcep(), edge.sinkp(), "Missing source on variable vertex"); + }); + return; + } + }); + } + + void extractComponents() { + // If the graph was acyclic (which should be the common case), there will be no non-trivial + // SCCs, so we are done. + if (!m_nonTrivialSCCs) return; + + // Allocate result graphs + m_components.resize(m_nonTrivialSCCs); + for (size_t i = 0; i < m_nonTrivialSCCs; ++i) { + m_components[i].reset(new DfgGraph{*m_dfg.modulep(), m_prefix + cvtToStr(i)}); + } + + // Fix up edges crossing components, and move vertices into their correct component. Note + // that fixing up the edges can create clones. Clones are added to the correct component, + // which also means that they might be added to the original DFG. Clones do not need + // fixing up, but also are not necessarily in the m_state map (in fact they are only there + // in debug mode), so we only iterate up to the original vertices. Because any new vertex + // is added at the end of the vertex list, we can just do this by iterating a fixed number + // of vertices. + size_t vertexCount = m_dfg.size(); + m_dfg.forEachVertex([&](DfgVertex& vtx) { + if (!vertexCount) return; + --vertexCount; + // Fix up the edges crossing components + fixEdges(vtx); + // Move the vertex to the component graph (leave component 0, which is the originally + // acyclic sub-graph, in the original graph) + if (const size_t component = m_state.at(&vtx).component) { + m_dfg.removeVertex(vtx); + m_components[component - 1]->addVertex(vtx); + } + }); + + // Pack sources of variables to remove the now undriven inputs + // (cloning might have unlinked some of the inputs), + packSources(m_dfg); + for (const auto& dfgp : m_components) packSources(*dfgp); + + if (VL_UNLIKELY(m_doExpensiveChecks)) { + // Check results for consistency + checkEdges(m_dfg); + for (const auto& dfgp : m_components) checkEdges(*dfgp); + } + } + + // CONSTRUCTOR - entry point + explicit ExtractCyclicComponents(DfgGraph& dfg, std::string label) + : m_dfg{dfg} + , m_prefix{dfg.name() + (label.empty() ? "" : "-") + label + "-component-"} { + // Find all the non-trivial SCCs (and trivial cycles) in the graph + colorSCCs(); + // Ensure that component boundaries are always at variables, by merging SCCs + mergeSCCs(); + // Extract the components + extractComponents(); + } + +public: + static std::vector> apply(DfgGraph& dfg, const std::string& label) { + return std::move(ExtractCyclicComponents{dfg, label}.m_components); + } +}; + +std::vector> DfgGraph::extractCyclicComponents(std::string label) { + return ExtractCyclicComponents::apply(*this, label); +} + void DfgGraph::runToFixedPoint(std::function f) { bool changed; const auto apply = [&](DfgVertex& vtx) -> void { @@ -278,7 +628,9 @@ static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { // if (edge.sourcep()) { string headLabel; - if (vtx.arity() > 1) headLabel = vtx.srcName(idx); + if (vtx.arity() > 1 || vtx.is() || vtx.is()) { + headLabel = vtx.srcName(idx); + } dumpDotEdge(os, edge, headLabel); } }); diff --git a/src/V3Dfg.h b/src/V3Dfg.h index e17dd1909..9ed089ea9 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -83,19 +83,15 @@ public: VL_UNCOPYABLE(DfgGraph); // METHODS -private: +public: // Add DfgVertex to this graph (assumes not yet contained). inline void addVertex(DfgVertex& vtx); // Remove DfgVertex form this graph (assumes it is contained). inline void removeVertex(DfgVertex& vtx); - -public: // Number of vertices in this graph size_t size() const { return m_size; } - // Parent module AstModule* modulep() const { return m_modulep; } - // Name of this graph const string& name() const { return m_name; } @@ -126,10 +122,20 @@ public: // Split this graph into individual components (unique sub-graphs with no edges between them). // Leaves 'this' graph empty. - std::vector> splitIntoComponents(); + std::vector> splitIntoComponents(std::string label); - // Apply the given function to all vertices in the graph. The function return value indicates - // that a change has been made to the graph. Repeat until no changes reported. + // Extract cyclic sub-graphs from 'this' graph. Cyclic sub-graphs are those that contain at + // least one strongly connected component (SCC) plus any other vertices that feed or sink from + // the SCCs, up to a variable boundary. This means that the returned graphs are guaranteed to + // be cyclic, but they are not guaranteed to be strongly connected (however, they are always + // at least weakly connected). Trivial SCCs that are acyclic (i.e.: vertices that are not part + // of a cycle) are left in 'this' graph. This means that at the end 'this' graph is guaranteed + // to be a DAG (acyclic). 'this' will not necessarily be a connected graph at the end, even if + // it was originally connected. + std::vector> extractCyclicComponents(std::string label); + + // Apply the given function to all vertices in the graph. The function return value + // indicates that a change has been made to the graph. Repeat until no changes reported. void runToFixedPoint(std::function f); // Dump graph in Graphviz format into the given stream 'os'. 'label' is added to the name of @@ -653,6 +659,26 @@ public: DfgVertexVariadic::resetSources(); } + // Remove undriven sources + void packSources() { + // Grab and reset the driver data + std::vector driverData{std::move(m_driverData)}; + + // Grab and unlink the sources + std::vector sources{arity()}; + forEachSourceEdge([&](DfgEdge& edge, size_t idx) { + sources[idx] = edge.sourcep(); + edge.unlinkSource(); + }); + DfgVertexVariadic::resetSources(); + + // Add back the driven sources + for (size_t i = 0; i < sources.size(); ++i) { + if (!sources[i]) continue; + addDriver(driverData[i].first, driverData[i].second, sources[i]); + } + } + FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; } uint32_t driverLsb(size_t idx) const { return m_driverData[idx].second; } @@ -692,6 +718,26 @@ public: DfgVertexVariadic::resetSources(); } + // Remove undriven sources + void packSources() { + // Grab and reset the driver data + std::vector driverData{std::move(m_driverData)}; + + // Grab and unlink the sources + std::vector sources{arity()}; + forEachSourceEdge([&](DfgEdge& edge, size_t idx) { + sources[idx] = edge.sourcep(); + edge.unlinkSource(); + }); + DfgVertexVariadic::resetSources(); + + // Add back the driven sources + for (size_t i = 0; i < sources.size(); ++i) { + if (!sources[i]) continue; + addDriver(driverData[i].first, driverData[i].second, sources[i]); + } + } + FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; } uint32_t driverIndex(size_t idx) const { return m_driverData[idx].second; } diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index d4491b5e0..a7cbde164 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -264,27 +264,43 @@ void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) { // Build the DFG of this module const std::unique_ptr dfg{V3DfgPasses::astToDfg(*modp, ctx)}; - if (dumpDfg() >= 9) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-input"); + if (dumpDfg() >= 8) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-input"); - // Split the DFG into independent components - const std::vector>& components = dfg->splitIntoComponents(); + // Extract the cyclic sub-graphs. We do this because a lot of the optimizations assume a + // DAG, and large, mostly acyclic graphs could not be optimized due to the presence of + // small cycles. + const std::vector>& cyclicComponents + = dfg->extractCyclicComponents("cyclic"); - // For each component - for (auto& component : components) { + // Split the remaining acyclic DFG into [weakly] connected components + const std::vector>& acyclicComponents + = dfg->splitIntoComponents("acyclic"); + + // Quick sanity check + UASSERT_OBJ(dfg->size() == 0, nodep, "DfgGraph should have become empty"); + + // For each cyclic component + for (auto& component : cyclicComponents) { + if (dumpDfg() >= 7) component->dumpDotFilePrefixed(ctx.prefix() + "source"); + // TODO: Apply optimizations safe for cyclic graphs + // Add back under the main DFG (we will convert everything back in one go) + dfg->addGraph(*component); + } + + // For each acyclic component + for (auto& component : acyclicComponents) { + if (dumpDfg() >= 7) component->dumpDotFilePrefixed(ctx.prefix() + "source"); // Reverse topologically sort the component const bool acyclic = component->sortTopologically(/* reverse: */ true); - // Optimize the component (iff it is not cyclic) - if (VL_LIKELY(acyclic)) { - V3DfgPasses::optimize(*component, ctx); - } else if (dumpDfg() >= 7) { - component->dumpDotFilePrefixed(ctx.prefix() + "cyclic"); - } - // Add back under the main DFG (we will convert back in one go) + UASSERT_OBJ(acyclic, nodep, "Supposedly acyclic graph is cyclic"); + // Optimize the component + V3DfgPasses::optimize(*component, ctx); + // Add back under the main DFG (we will convert everything back in one go) dfg->addGraph(*component); } // Convert back to Ast - if (dumpDfg() >= 9) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-optimized"); + if (dumpDfg() >= 8) dfg->dumpDotFilePrefixed(ctx.prefix() + "whole-optimized"); AstModule* const resultModp = V3DfgPasses::dfgToAst(*dfg, ctx); UASSERT_OBJ(resultModp == modp, modp, "Should be the same module"); } diff --git a/test_regress/t/t_dfg_circular.v b/test_regress/t/t_dfg_circular.v index 5eb9a7049..3d8e51f3a 100644 --- a/test_regress/t/t_dfg_circular.v +++ b/test_regress/t/t_dfg_circular.v @@ -4,16 +4,41 @@ // any use, without warranty, 2022 by Geza Lore. // SPDX-License-Identifier: CC0-1.0 -module t (/*AUTOARG*/ - // Inputs - clk - ); - input clk; +// verilator lint_off UNOPTFLAT +module t ( + input wire i, + output wire o +); wire a; wire b; + wire c; + wire d; - assign a = b + 1'b1; + assign c = i + 1'b1; + assign d = c + 1'b1; + assign a = b + d; assign b = a + 1'b1; + wire p; + wire q; + wire r; + wire s; + + assign p = i + 1'b1; + assign q = p + 1'b1; + assign r = s ^ q; + assign s = r + 1'b1; + + wire x; + wire y; + wire z; + wire w; + + assign x = y ^ i; + assign y = x; + assign z = w; + assign w = y & z; + + assign o = b | x; endmodule From cc51966ad1bb4acb3ce9e7024dd62b8bae3cf134 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 30 Sep 2022 11:35:03 +0100 Subject: [PATCH 061/177] DFG: Remove unconneced variables early --- src/V3Dfg.cpp | 14 ++++++++++---- src/V3DfgAstToDfg.cpp | 22 ++++++++++++++++++++-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index a6bdb2411..fb2c74d44 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -410,12 +410,18 @@ class ExtractCyclicComponents final { static void packSources(DfgGraph& dfg) { // Remove undriven variable sources dfg.forEachVertex([&](DfgVertex& vtx) { - if (DfgVarPacked* const vtxp = vtx.cast()) { - vtxp->packSources(); + if (DfgVarPacked* const varp = vtx.cast()) { + varp->packSources(); + if (!varp->hasSinks() && varp->arity() == 0) { + VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); + } return; } - if (DfgVarArray* const vtxp = vtx.cast()) { - vtxp->packSources(); + if (DfgVarArray* const varp = vtx.cast()) { + varp->packSources(); + if (!varp->hasSinks() && varp->arity() == 0) { + VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); + } return; } }); diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index 763a1656d..db81c7d11 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -110,8 +110,7 @@ class AstToDfgVisitor final : public VNVisitor { // Note DfgVertexLValue vertices are not added to m_uncommittedVertices, because we // want to hold onto them via AstVar::user1p, and the AstVar might be referenced via // multiple AstVarRef instances, so we will never revert a DfgVertexLValue once - // created. This means we can end up with DfgVertexLValue vertices in the graph which - // have no connections at all (which is fine for later processing). + // created. We will delete unconnected variable vertices at the end. if (VN_IS(varp->dtypep()->skipRefp(), UnpackArrayDType)) { DfgVarArray* const vtxp = new DfgVarArray{*m_dfgp, varp}; varp->user1p(); @@ -264,6 +263,13 @@ class AstToDfgVisitor final : public VNVisitor { // Canonicalize packed variables void canonicalizePacked() { for (DfgVarPacked* const varp : m_varPackedps) { + // Delete variables with no sinks nor sources (this can happen due to reverting + // uncommitted vertices, which does not remove variables) + if (!varp->hasSinks() && varp->arity() == 0) { + VL_DO_DANGLING(varp->unlinkDelete(*m_dfgp), varp); + continue; + } + // Gather (and unlink) all drivers struct Driver { FileLine* flp; @@ -328,6 +334,17 @@ class AstToDfgVisitor final : public VNVisitor { } } + // Canonicalize array variables + void canonicalizeArray() { + for (DfgVarArray* const varp : m_varArrayps) { + // Delete variables with no sinks nor sources (this can happen due to reverting + // uncommitted vertices, which does not remove variables) + if (!varp->hasSinks() && varp->arity() == 0) { + VL_DO_DANGLING(varp->unlinkDelete(*m_dfgp), varp); + } + } + } + // VISITORS void visit(AstNode* nodep) override { // Conservatively treat this node as unhandled @@ -406,6 +423,7 @@ class AstToDfgVisitor final : public VNVisitor { // Canonicalize variables canonicalizePacked(); + canonicalizeArray(); } public: From 0b843ada03f1d3d0ef3dec9dfa1ae2dd16fcffd5 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 1 Oct 2022 08:34:43 -0400 Subject: [PATCH 062/177] devel release --- Changes | 5 +++++ configure.ac | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 06ce093d1..e44303b8a 100644 --- a/Changes +++ b/Changes @@ -8,6 +8,11 @@ The changes in each Verilator version are described below. The contributors that suggested a given feature are shown in []. Thanks! +Verilator 4.229 devel +========================== + + + Verilator 4.228 2022-10-01 ========================== diff --git a/configure.ac b/configure.ac index 35b3fcd01..5e8505df9 100644 --- a/configure.ac +++ b/configure.ac @@ -10,7 +10,7 @@ # Then 'make maintainer-dist' #AC_INIT([Verilator],[#.### YYYY-MM-DD]) #AC_INIT([Verilator],[#.### devel]) -AC_INIT([Verilator],[4.228 2022-10-01], +AC_INIT([Verilator],[4.229 devel], [https://verilator.org], [verilator],[https://verilator.org]) From 4db998d357595f02c0503f8d166c6fb40b8f67d4 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 1 Oct 2022 10:09:14 -0400 Subject: [PATCH 063/177] CI: coverage on 22.04 --- .github/workflows/coverage.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 506dd98a1..b88e1f05f 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -24,10 +24,10 @@ jobs: Build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: CI_BUILD_STAGE_NAME: build - CI_RUNS_ON: ubuntu-20.04 + CI_RUNS_ON: ubuntu-22.04 steps: - name: Checkout @@ -73,11 +73,11 @@ jobs: - 9 include: - { test: dist, num: '' } - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 name: test-${{ matrix.test }}${{ matrix.num }} env: CI_BUILD_STAGE_NAME: test - CI_RUNS_ON: ubuntu-20.04 + CI_RUNS_ON: ubuntu-22.04 steps: - name: Download tar archive From 46b8dca3602111f464053a4ce97c84fbc830858a Mon Sep 17 00:00:00 2001 From: Ryszard Rozak Date: Sat, 1 Oct 2022 16:34:30 +0200 Subject: [PATCH 064/177] Add handling of tristate select/extend (#3604) --- src/V3Tristate.cpp | 75 +++++++++++++++++++++------ test_regress/t/t_tri_and_eqcase.out | 8 +++ test_regress/t/t_tri_and_eqcase.pl | 19 +++++++ test_regress/t/t_tri_and_eqcase.v | 17 ++++++ test_regress/t/t_tri_select_eqcase.pl | 21 ++++++++ test_regress/t/t_tri_select_eqcase.v | 26 ++++++++++ 6 files changed, 149 insertions(+), 17 deletions(-) create mode 100644 test_regress/t/t_tri_and_eqcase.out create mode 100755 test_regress/t/t_tri_and_eqcase.pl create mode 100644 test_regress/t/t_tri_and_eqcase.v create mode 100755 test_regress/t/t_tri_select_eqcase.pl create mode 100644 test_regress/t/t_tri_select_eqcase.v diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 55dddaacc..7e35067eb 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -493,6 +493,47 @@ class TristateVisitor final : public TristateBaseVisitor { } return VN_AS(invarp->user1p(), Var); } + AstConst* getNonZConstp(AstConst* const constp) { + FileLine* const fl = constp->fileline(); + V3Number numz{constp, constp->width()}; + numz.opBitsZ(constp->num()); // Z->1, else 0 + V3Number numz0{constp, constp->width()}; + numz0.opNot(numz); // Z->0, else 1 + return new AstConst{fl, numz0}; + } + AstNode* getEnExprBasedOnOriginalp(AstNode* const nodep) { + if (AstVarRef* const varrefp = VN_CAST(nodep, VarRef)) { + return new AstVarRef{varrefp->fileline(), getCreateEnVarp(varrefp->varp()), + VAccess::READ}; + } else if (AstConst* const constp = VN_CAST(nodep, Const)) { + return getNonZConstp(constp); + } else if (AstExtend* const extendp = VN_CAST(nodep, Extend)) { + // Extend inserts 0 at the beginning. 0 in __en variable means that this bit equals z, + // so in order to preserve the value of the original AstExtend node we should insert 1 + // instead of 0. To extend __en expression we have to negate its lhsp() and then negate + // whole extend. + + // Unlink lhsp before copying to save unnecessary copy of lhsp + AstNode* const lhsp = extendp->lhsp()->unlinkFrBack(); + AstExtend* const enExtendp = extendp->cloneTree(false); + extendp->lhsp(lhsp); + AstNode* const enLhsp = getEnExprBasedOnOriginalp(lhsp); + enExtendp->lhsp(new AstNot{enLhsp->fileline(), enLhsp}); + return new AstNot{enExtendp->fileline(), enExtendp}; + } else if (AstSel* const selp = VN_CAST(nodep, Sel)) { + AstNode* const fromp = selp->fromp()->unlinkFrBack(); + AstSel* const enSelp = selp->cloneTree(false); + selp->fromp(fromp); + AstNode* const enFromp = getEnExprBasedOnOriginalp(fromp); + enSelp->fromp(enFromp); + return enSelp; + } else { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported tristate construct: " << nodep->prettyTypeName() + << " in function " << __func__); + return nullptr; + } + } AstVar* getCreateOutVarp(AstVar* invarp) { // Return the master __out for the specified input variable if (!invarp->user4p()) { @@ -959,14 +1000,10 @@ class TristateVisitor final : public TristateBaseVisitor { } else if (m_tgraph.isTristate(nodep)) { m_tgraph.didProcess(nodep); FileLine* const fl = nodep->fileline(); - V3Number numz(nodep, nodep->width()); - numz.opBitsZ(nodep->num()); // Z->1, else 0 - V3Number numz0(nodep, nodep->width()); - numz0.opNot(numz); // Z->0, else 1 - V3Number num1(nodep, nodep->width()); - num1.opAnd(nodep->num(), numz0); // 01X->01X, Z->0 - AstConst* const newconstp = new AstConst(fl, num1); - AstConst* const enp = new AstConst(fl, numz0); + AstConst* const enp = getNonZConstp(nodep); + V3Number num1{nodep, nodep->width()}; + num1.opAnd(nodep->num(), enp->num()); // 01X->01X, Z->0 + AstConst* const newconstp = new AstConst{fl, num1}; nodep->replaceWith(newconstp); VL_DO_DANGLING(pushDeletep(nodep), nodep); newconstp->user1p(enp); // Propagate up constant with non-Z bits as 1 @@ -1263,23 +1300,27 @@ class TristateVisitor final : public TristateBaseVisitor { UINFO(9, dbgState() << nodep << endl); // Constification always moves const to LHS AstConst* const constp = VN_CAST(nodep->lhsp(), Const); - AstVarRef* const varrefp = VN_CAST(nodep->rhsp(), VarRef); // Input variable - if (constp && constp->user1p() && varrefp) { + if (constp && constp->user1p()) { // 3'b1z0 -> ((3'b101 == in__en) && (3'b100 == in)) - varrefp->unlinkFrBack(); + AstNode* const rhsp = nodep->rhsp(); + rhsp->unlinkFrBack(); FileLine* const fl = nodep->fileline(); + AstNode* enRhsp; + if (rhsp->user1p()) { + enRhsp = rhsp->user1p(); + rhsp->user1p(nullptr); + } else { + enRhsp = getEnExprBasedOnOriginalp(rhsp); + } const V3Number oneIfEn = VN_AS(constp->user1p(), Const) ->num(); // visit(AstConst) already split into en/ones const V3Number& oneIfEnOne = constp->num(); - AstVar* const envarp = getCreateEnVarp(varrefp->varp()); AstNode* newp - = new AstLogAnd(fl, - new AstEq(fl, new AstConst(fl, oneIfEn), - new AstVarRef(fl, envarp, VAccess::READ)), + = new AstLogAnd{fl, new AstEq{fl, new AstConst{fl, oneIfEn}, enRhsp}, // Keep the caseeq if there are X's present - new AstEqCase(fl, new AstConst(fl, oneIfEnOne), varrefp)); - if (neq) newp = new AstLogNot(fl, newp); + new AstEqCase{fl, new AstConst{fl, oneIfEnOne}, rhsp}}; + if (neq) newp = new AstLogNot{fl, newp}; UINFO(9, " newceq " << newp << endl); if (debug() >= 9) nodep->dumpTree(cout, "-caseeq-old: "); if (debug() >= 9) newp->dumpTree(cout, "-caseeq-new: "); diff --git a/test_regress/t/t_tri_and_eqcase.out b/test_regress/t/t_tri_and_eqcase.out new file mode 100644 index 000000000..315a41e1f --- /dev/null +++ b/test_regress/t/t_tri_and_eqcase.out @@ -0,0 +1,8 @@ +%Error-UNSUPPORTED: t/t_tri_and_eqcase.v:9:28: Unsupported tristate construct: AND in function getEnExprBasedOnOriginalp + 9 | logic b = 1'z === (clk1 & clk2); + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Internal Error: t/t_tri_and_eqcase.v:9:18: ../V3Ast.cpp:#: Null item passed to setOp2p + 9 | logic b = 1'z === (clk1 & clk2); + | ^~~ + ... See the manual at https://verilator.org/verilator_doc.html for more assistance. diff --git a/test_regress/t/t_tri_and_eqcase.pl b/test_regress/t/t_tri_and_eqcase.pl new file mode 100755 index 000000000..48bf31461 --- /dev/null +++ b/test_regress/t/t_tri_and_eqcase.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vlt => 1); + +lint( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_tri_and_eqcase.v b/test_regress/t/t_tri_and_eqcase.v new file mode 100644 index 000000000..204aab82d --- /dev/null +++ b/test_regress/t/t_tri_and_eqcase.v @@ -0,0 +1,17 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (clk1, clk2); + input wire clk1, clk2; + logic b = 1'z === (clk1 & clk2); + + always begin + if (!b) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_tri_select_eqcase.pl b/test_regress/t/t_tri_select_eqcase.pl new file mode 100755 index 000000000..f5e338520 --- /dev/null +++ b/test_regress/t/t_tri_select_eqcase.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_tri_select_eqcase.v b/test_regress/t/t_tri_select_eqcase.v new file mode 100644 index 000000000..c70688c0f --- /dev/null +++ b/test_regress/t/t_tri_select_eqcase.v @@ -0,0 +1,26 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + wire [3:0] a = 4'b11z1; + logic b = 1'bz === a[1]; + logic c = 1'bz === a[2]; + logic d = 2'bzz === 2'(a[1]); + logic e = 2'b0z === 2'(a[1]); + + + always begin + if (b && !c && !d && e) begin + $write("*-* All Finished *-*\n"); + $finish; + end + else begin + $write("Error: b = %b, c = %b, d = %b, e = %b ", b, c, d, e); + $write("expected: b = 1, c = 0, d = 0, e = 1\n"); + $stop; + end + end +endmodule From 159cf0429cce19ce85a219bd81370af5ef7ac81c Mon Sep 17 00:00:00 2001 From: Kanad Kanhere <44873394+kkanhere@users.noreply.github.com> Date: Sat, 1 Oct 2022 09:48:37 -0500 Subject: [PATCH 065/177] Support linting for top module interfaces (#3635) --- src/V3Dead.cpp | 48 +++++++- src/V3LinkDot.cpp | 51 +++++++++ src/V3LinkLevel.cpp | 104 ++++++++++++++++++ src/V3LinkParse.cpp | 2 +- src/V3Options.h | 1 + test_regress/t/t_interface_top_bad.pl | 2 +- .../t/t_lint_iface_array_topmodule1.pl | 16 +++ .../t/t_lint_iface_array_topmodule1.v | 48 ++++++++ .../t/t_lint_iface_array_topmodule2.pl | 16 +++ .../t/t_lint_iface_array_topmodule2.v | 39 +++++++ .../t/t_lint_iface_array_topmodule3.pl | 16 +++ .../t/t_lint_iface_array_topmodule3.v | 77 +++++++++++++ .../t/t_lint_iface_array_topmodule_bad.out | 5 + .../t/t_lint_iface_array_topmodule_bad.pl | 19 ++++ .../t/t_lint_iface_array_topmodule_bad.v | 50 +++++++++ test_regress/t/t_lint_iface_topmodule1.pl | 16 +++ test_regress/t/t_lint_iface_topmodule1.v | 42 +++++++ test_regress/t/t_lint_iface_topmodule2.pl | 16 +++ test_regress/t/t_lint_iface_topmodule2.v | 35 ++++++ test_regress/t/t_lint_iface_topmodule3.pl | 16 +++ test_regress/t/t_lint_iface_topmodule3.v | 67 +++++++++++ test_regress/t/t_lint_iface_topmodule_bad.out | 5 + test_regress/t/t_lint_iface_topmodule_bad.pl | 19 ++++ test_regress/t/t_lint_iface_topmodule_bad.v | 44 ++++++++ 24 files changed, 746 insertions(+), 8 deletions(-) create mode 100755 test_regress/t/t_lint_iface_array_topmodule1.pl create mode 100644 test_regress/t/t_lint_iface_array_topmodule1.v create mode 100755 test_regress/t/t_lint_iface_array_topmodule2.pl create mode 100644 test_regress/t/t_lint_iface_array_topmodule2.v create mode 100755 test_regress/t/t_lint_iface_array_topmodule3.pl create mode 100644 test_regress/t/t_lint_iface_array_topmodule3.v create mode 100644 test_regress/t/t_lint_iface_array_topmodule_bad.out create mode 100755 test_regress/t/t_lint_iface_array_topmodule_bad.pl create mode 100644 test_regress/t/t_lint_iface_array_topmodule_bad.v create mode 100755 test_regress/t/t_lint_iface_topmodule1.pl create mode 100644 test_regress/t/t_lint_iface_topmodule1.v create mode 100755 test_regress/t/t_lint_iface_topmodule2.pl create mode 100644 test_regress/t/t_lint_iface_topmodule2.v create mode 100755 test_regress/t/t_lint_iface_topmodule3.pl create mode 100644 test_regress/t/t_lint_iface_topmodule3.v create mode 100644 test_regress/t/t_lint_iface_topmodule_bad.out create mode 100755 test_regress/t/t_lint_iface_topmodule_bad.pl create mode 100644 test_regress/t/t_lint_iface_topmodule_bad.v diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index 83ecf5ffa..f3e8950f8 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -423,10 +423,45 @@ private: } } + void preserveTopIfaces(AstNetlist* rootp) { + for (AstNodeModule* modp = rootp->modulesp(); modp && modp->level() <= 2; + modp = VN_AS(modp->nextp(), NodeModule)) { + for (AstNode* subnodep = modp->stmtsp(); subnodep; subnodep = subnodep->nextp()) { + if (AstVar* const varp = VN_CAST(subnodep, Var)) { + if (varp->isIfaceRef()) { + const AstNodeDType* const subtypep = varp->subDTypep(); + const AstIfaceRefDType* ifacerefp = nullptr; + if (VN_IS(subtypep, IfaceRefDType)) { + ifacerefp = VN_AS(varp->subDTypep(), IfaceRefDType); + } + else if (VN_IS(subtypep, BracketArrayDType)) { + const AstBracketArrayDType* const arrp = VN_AS(subtypep, BracketArrayDType); + const AstNodeDType* const arrsubtypep = arrp->subDTypep(); + if (VN_IS(arrsubtypep, IfaceRefDType)) { + ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); + } + } + else if (VN_IS(subtypep, UnpackArrayDType)) { + const AstUnpackArrayDType* const arrp = VN_AS(subtypep, UnpackArrayDType); + const AstNodeDType* const arrsubtypep = arrp->subDTypep(); + if (VN_IS(arrsubtypep, IfaceRefDType)) { + ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); + } + } + + if (ifacerefp && !ifacerefp->cellp() && (ifacerefp->ifacep()->user1() == 0)) { + ifacerefp->ifacep()->user1(1); + } + } + } + } + } + } + public: // CONSTRUCTORS DeadVisitor(AstNetlist* nodep, bool elimUserVars, bool elimDTypes, bool elimScopes, - bool elimCells) + bool elimCells, bool elimTopIfaces) : m_elimUserVars{elimUserVars} , m_elimDTypes{elimDTypes} , m_elimCells{elimCells} { @@ -442,6 +477,7 @@ public: if (elimCells) deadCheckCells(); deadCheckClasses(); // Modules after vars, because might be vars we delete inside a mod we delete + if (!elimTopIfaces) preserveTopIfaces(nodep); deadCheckMod(); // We may have removed some datatypes, cleanup @@ -455,30 +491,30 @@ public: void V3Dead::deadifyModules(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); - { DeadVisitor{nodep, false, false, false, false}; } // Destruct before checking + { DeadVisitor{nodep, false, false, false, false, !v3Global.opt.topIfacesSupported()}; } // Destruct before checking V3Global::dumpCheckGlobalTree("deadModules", 0, dumpTree() >= 6); } void V3Dead::deadifyDTypes(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); - { DeadVisitor{nodep, false, true, false, false}; } // Destruct before checking + { DeadVisitor{nodep, false, true, false, false, false}; } // Destruct before checking V3Global::dumpCheckGlobalTree("deadDtypes", 0, dumpTree() >= 3); } void V3Dead::deadifyDTypesScoped(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); - { DeadVisitor{nodep, false, true, true, false}; } // Destruct before checking + { DeadVisitor{nodep, false, true, true, false, false}; } // Destruct before checking V3Global::dumpCheckGlobalTree("deadDtypesScoped", 0, dumpTree() >= 3); } void V3Dead::deadifyAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); - { DeadVisitor{nodep, true, true, false, true}; } // Destruct before checking + { DeadVisitor{nodep, true, true, false, true, false}; } // Destruct before checking V3Global::dumpCheckGlobalTree("deadAll", 0, dumpTree() >= 3); } void V3Dead::deadifyAllScoped(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); - { DeadVisitor{nodep, true, true, true, true}; } // Destruct before checking + { DeadVisitor{nodep, true, true, true, true, false}; } // Destruct before checking V3Global::dumpCheckGlobalTree("deadAllScoped", 0, dumpTree() >= 3); } diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 75681218f..f5d93991a 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -308,6 +308,19 @@ public: if (forScopeCreation()) m_nameScopeSymMap.emplace(scopename, symp); return symp; } + VSymEnt* insertTopIface(AstCell* nodep, const string& scopename) { + VSymEnt* const symp = new VSymEnt{&m_syms, nodep}; + UINFO(9, + " INSERTtopiface se" << cvtToHex(symp) << " " << scopename << " " << nodep << endl); + symp->parentp(rootEntp()); // Needed so backward search can find name of top module + symp->fallbackp(dunitEntp()); // Needed so can find $unit stuff + nodep->user1p(symp); + if (nodep->modp()) nodep->modp()->user1p(symp); + checkDuplicate(rootEntp(), nodep, nodep->origName()); + rootEntp()->insert(nodep->origName(), symp); + if (forScopeCreation()) m_nameScopeSymMap.emplace(scopename, symp); + return symp; + } VSymEnt* insertCell(VSymEnt* abovep, VSymEnt* modSymp, AstCell* nodep, const string& scopename) { UASSERT_OBJ(abovep, nodep, "Null symbol table inserting node"); @@ -764,8 +777,46 @@ class LinkDotFindVisitor final : public VNVisitor { modp = VN_AS(modp->nextp(), NodeModule)) { UINFO(8, "Top Module: " << modp << endl); m_scope = "TOP"; + + if (m_statep->forPrearray() && v3Global.opt.topIfacesSupported()) { + for (AstNode* subnodep = modp->stmtsp(); subnodep; subnodep = subnodep->nextp()) { + if (AstVar* const varp = VN_CAST(subnodep, Var)) { + if (varp->isIfaceRef()) { + const AstNodeDType* const subtypep = varp->subDTypep(); + const AstIfaceRefDType* ifacerefp = nullptr; + if (VN_IS(subtypep, IfaceRefDType)) { + ifacerefp = VN_AS(varp->subDTypep(), IfaceRefDType); + } + else if (VN_IS(subtypep, BracketArrayDType)) { + const AstBracketArrayDType* const arrp = VN_AS(subtypep, BracketArrayDType); + const AstNodeDType* const arrsubtypep = arrp->subDTypep(); + if (VN_IS(arrsubtypep, IfaceRefDType)) { + ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); + } + } + else if (VN_IS(subtypep, UnpackArrayDType)) { + const AstUnpackArrayDType* const arrp = VN_AS(subtypep, UnpackArrayDType); + const AstNodeDType* const arrsubtypep = arrp->subDTypep(); + if (VN_IS(arrsubtypep, IfaceRefDType)) { + ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); + } + } + + if (ifacerefp && !ifacerefp->cellp()) { + // A dummy cell to keep the top level interface alive and correctly optimized for default parameter values + AstCell* ifacecellp = new AstCell{nodep->fileline(), nodep->fileline(), modp->name() + "__02E" + varp->name(), ifacerefp->ifaceName(), nullptr, nullptr, nullptr}; + ifacecellp->modp(ifacerefp->ifacep()); + m_curSymp = m_modSymp = m_statep->insertTopIface(ifacecellp, m_scope); + { iterate(ifacecellp); } + } + } + } + } + } + m_curSymp = m_modSymp = m_statep->insertTopCell(modp, m_scope); { iterate(modp); } + m_scope = ""; m_curSymp = m_modSymp = nullptr; } diff --git a/src/V3LinkLevel.cpp b/src/V3LinkLevel.cpp index cdb9b19c8..fbc4d86bd 100644 --- a/src/V3LinkLevel.cpp +++ b/src/V3LinkLevel.cpp @@ -200,6 +200,35 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { ioNames.insert(oldvarp->name()); } } + else if (v3Global.opt.topIfacesSupported() && oldvarp->isIfaceRef()) { + const AstNodeDType* const subtypep = oldvarp->subDTypep(); + if (VN_IS(subtypep, IfaceRefDType)) { + const AstIfaceRefDType* const ifacerefp = VN_AS(subtypep, IfaceRefDType); + if (!ifacerefp->cellp()) { + if (ioNames.find(oldvarp->name()) != ioNames.end()) { + // UINFO(8, "Multitop dup interface found: " << oldvarp << endl); + dupNames.insert(oldvarp->name()); + } else { + ioNames.insert(oldvarp->name()); + } + } + } + if (VN_IS(subtypep, UnpackArrayDType)) { + const AstUnpackArrayDType* const arrp = VN_AS(subtypep, UnpackArrayDType); + const AstNodeDType* const arrsubtypep = arrp->subDTypep(); + if (VN_IS(arrsubtypep, IfaceRefDType)) { + const AstIfaceRefDType* const ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); + if (!ifacerefp->cellp()) { + if (ioNames.find(oldvarp->name()) != ioNames.end()) { + // UINFO(8, "Multitop dup interface array found: " << oldvarp << endl); + dupNames.insert(oldvarp->name()); + } else { + ioNames.insert(oldvarp->name()); + } + } + } + } + } } } } @@ -257,6 +286,81 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { pinp->modVarp(oldvarp); cellp->addPinsp(pinp); } + else if (v3Global.opt.topIfacesSupported() && oldvarp->isIfaceRef()) { + // for each interface port on oldmodp instantiate a corresponding interface cell in $root + const AstNodeDType* const subtypep = oldvarp->subDTypep(); + if (VN_IS(subtypep, IfaceRefDType)) { + const AstIfaceRefDType* const ifacerefp = VN_AS(subtypep, IfaceRefDType); + if (!ifacerefp->cellp()) { + string name = oldvarp->name(); + if (dupNames.find(name) != dupNames.end()) { + // __02E=. while __DOT__ looks nicer but will break V3LinkDot + name = oldmodp->name() + "__02E" + name; + } + + AstCell* ifacecellp = new AstCell{newmodp->fileline(), newmodp->fileline(), name, ifacerefp->ifaceName(), nullptr, nullptr, nullptr}; + ifacecellp->modp(ifacerefp->ifacep()); + newmodp->addStmtsp(ifacecellp); + + AstIfaceRefDType* const idtypep = new AstIfaceRefDType{newmodp->fileline(), name, ifacerefp->ifaceName()}; + idtypep->ifacep(nullptr); + idtypep->dtypep(idtypep); + idtypep->cellp(ifacecellp); + rootp->typeTablep()->addTypesp(idtypep); + + AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF, name + "__Viftop", idtypep}; + varp->isIfaceParent(true); + ifacecellp->addNextHere(varp); + ifacecellp->hasIfaceVar(true); + + AstPin* const pinp = new AstPin{ + oldvarp->fileline(), 0, varp->name(), + new AstVarRef{varp->fileline(), varp, + oldvarp->isWritable() ? VAccess::WRITE : VAccess::READ}}; + pinp->modVarp(oldvarp); + cellp->addPinsp(pinp); + } + } + else if (VN_IS(subtypep, UnpackArrayDType)) { + const AstUnpackArrayDType* const oldarrp = VN_AS(subtypep, UnpackArrayDType); + const AstNodeDType* const arrsubtypep = oldarrp->subDTypep(); + if (VN_IS(arrsubtypep, IfaceRefDType)) { + const AstIfaceRefDType* const ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); + if (!ifacerefp->cellp()) { + string name = oldvarp->name(); + if (dupNames.find(name) != dupNames.end()) { + // __02E=. while __DOT__ looks nicer but will break V3LinkDot + name = oldmodp->name() + "__02E" + name; + } + + AstUnpackArrayDType* arraydtypep = VN_AS(oldvarp->dtypep(), UnpackArrayDType); + AstCell* ifacearraycellp = new AstCell{newmodp->fileline(), newmodp->fileline(), name, ifacerefp->ifaceName(), nullptr, nullptr, arraydtypep->rangep()->cloneTree(true)}; + ifacearraycellp->modp(ifacerefp->ifacep()); + newmodp->addStmtsp(ifacearraycellp); + + AstIfaceRefDType* const idtypep = new AstIfaceRefDType{newmodp->fileline(), name, ifacerefp->ifaceName()}; + idtypep->ifacep(nullptr); + idtypep->dtypep(idtypep); + idtypep->cellp(ifacearraycellp); + rootp->typeTablep()->addTypesp(idtypep); + + AstNodeArrayDType* const arrp = new AstUnpackArrayDType{newmodp->fileline(), idtypep, arraydtypep->rangep()->cloneTree(true)}; + AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF, name + "__Viftop", arrp}; + varp->isIfaceParent(true); + ifacearraycellp->addNextHere(varp); + ifacearraycellp->hasIfaceVar(true); + rootp->typeTablep()->addTypesp(arrp); + + AstPin* const pinp = new AstPin{ + oldvarp->fileline(), 0, varp->name(), + new AstVarRef{varp->fileline(), varp, + oldvarp->isWritable() ? VAccess::WRITE : VAccess::READ}}; + pinp->modVarp(oldvarp); + cellp->addPinsp(pinp); + } + } + } + } } } } diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 43ade69e9..cfeb3d873 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -281,7 +281,7 @@ private: nodep->valuep()->unlinkFrBack())); } } - if (nodep->isIfaceRef() && !nodep->isIfaceParent()) { + if (nodep->isIfaceRef() && !nodep->isIfaceParent() && !v3Global.opt.topIfacesSupported()) { // Only AstIfaceRefDType's at this point correspond to ports; // haven't made additional ones for interconnect yet, so assert is simple // What breaks later is we don't have a Scope/Cell representing diff --git a/src/V3Options.h b/src/V3Options.h index 782987d46..69870a701 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -490,6 +490,7 @@ public: bool vpi() const { return m_vpi; } bool xInitialEdge() const { return m_xInitialEdge; } bool xmlOnly() const { return m_xmlOnly; } + bool topIfacesSupported() const { return lintOnly() && !hierarchical(); } int buildJobs() const { return m_buildJobs; } int convergeLimit() const { return m_convergeLimit; } diff --git a/test_regress/t/t_interface_top_bad.pl b/test_regress/t/t_interface_top_bad.pl index 07964a1b5..8eda3a219 100755 --- a/test_regress/t/t_interface_top_bad.pl +++ b/test_regress/t/t_interface_top_bad.pl @@ -10,7 +10,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); -lint( +compile( fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_lint_iface_array_topmodule1.pl b/test_regress/t/t_lint_iface_array_topmodule1.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule1.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_array_topmodule1.v b/test_regress/t/t_lint_iface_array_topmodule1.v new file mode 100644 index 000000000..de19c13a2 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule1.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if; + + logic valid; + logic [7:0] data ; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if [2], + my_if.master_mp out_if [2] + ); + + my_if my_i [2] (); + + always @(posedge clk) + begin + my_i[0].valid <= in_if[0].valid; + my_i[0].data <= in_if[0].data; + + my_i[1].valid <= in_if[1].valid; + my_i[1].data <= in_if[1].data; + end + + assign out_if[0].valid = my_i[0].valid; + assign out_if[0].data = my_i[0].data; + + assign out_if[1].valid = my_i[1].valid; + assign out_if[1].data = my_i[1].data; + +endmodule diff --git a/test_regress/t/t_lint_iface_array_topmodule2.pl b/test_regress/t/t_lint_iface_array_topmodule2.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule2.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_array_topmodule2.v b/test_regress/t/t_lint_iface_array_topmodule2.v new file mode 100644 index 000000000..078fb4495 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule2.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( + parameter DW = 8 + ) (); + + logic valid; + logic [DW-1:0] data ; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if [2], + my_if.master_mp out_if [2] + ); + + assign out_if[0].valid = in_if[0].valid; + assign out_if[0].data = in_if[0].data; + + assign out_if[1].valid = in_if[1].valid; + assign out_if[1].data = in_if[1].data; + +endmodule diff --git a/test_regress/t/t_lint_iface_array_topmodule3.pl b/test_regress/t/t_lint_iface_array_topmodule3.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule3.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_array_topmodule3.v b/test_regress/t/t_lint_iface_array_topmodule3.v new file mode 100644 index 000000000..50dae10f2 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule3.v @@ -0,0 +1,77 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( parameter integer DW = 8 ) (input clk); + + localparam DW_LOCAL = DW; + + logic valid; + logic [DW-1:0] data; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + + function automatic integer width(); + return $bits(data); + endfunction + + generate + if (DW < 4) + begin: dw_lt_4_G + function automatic integer min_width(); + return 4; + endfunction + end + else + begin: dw_ge_4_G + function automatic integer min_width(); + return 8; + endfunction + end + endgenerate + +endinterface + +module t + ( + input wire clk, + my_if in_if [2], + my_if out_if [2] + ); + + assign out_if[0].valid = in_if[0].valid; + assign out_if[0].data = in_if[0].data; + + assign out_if[1].valid = in_if[1].valid; + assign out_if[1].data = in_if[1].data; + + my_if my_i (.clk(clk)); + + initial + begin + $display(in_if[0].DW_LOCAL); + $display(in_if[0].width()); + $display(in_if[0].dw_ge_4_G.min_width()); + $display(out_if[0].DW_LOCAL); + $display(out_if[0].width()); + $display(out_if[0].dw_ge_4_G.min_width()); + + $display(in_if[1].DW_LOCAL); + $display(in_if[1].width()); + $display(in_if[1].dw_ge_4_G.min_width()); + $display(out_if[1].DW_LOCAL); + $display(out_if[1].width()); + $display(out_if[1].dw_ge_4_G.min_width()); + end + +endmodule diff --git a/test_regress/t/t_lint_iface_array_topmodule_bad.out b/test_regress/t/t_lint_iface_array_topmodule_bad.out new file mode 100644 index 000000000..6c0980c74 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_lint_iface_array_topmodule_bad.v:8:24: Parameter without initial value is never given value (IEEE 1800-2017 6.20.1): 'DW' + : ... In instance t + 8 | parameter integer DW + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_lint_iface_array_topmodule_bad.pl b/test_regress/t/t_lint_iface_array_topmodule_bad.pl new file mode 100755 index 000000000..a82cf66cb --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_array_topmodule_bad.v b/test_regress/t/t_lint_iface_array_topmodule_bad.v new file mode 100644 index 000000000..55b060591 --- /dev/null +++ b/test_regress/t/t_lint_iface_array_topmodule_bad.v @@ -0,0 +1,50 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( + parameter integer DW + ) (); + + logic valid; + logic [7:0] data ; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if [2], + my_if.master_mp out_if [2] + ); + + my_if my_i [2] (); + + always @(posedge clk) + begin + my_i[0].valid <= in_if[0].valid; + my_i[0].data <= in_if[0].data; + + my_i[1].valid <= in_if[1].valid; + my_i[1].data <= in_if[1].data; + end + + assign out_if[0].valid = my_i[0].valid; + assign out_if[0].data = my_i[0].data; + + assign out_if[1].valid = my_i[1].valid; + assign out_if[1].data = my_i[1].data; + +endmodule diff --git a/test_regress/t/t_lint_iface_topmodule1.pl b/test_regress/t/t_lint_iface_topmodule1.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule1.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_topmodule1.v b/test_regress/t/t_lint_iface_topmodule1.v new file mode 100644 index 000000000..4c4fa5b1f --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule1.v @@ -0,0 +1,42 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if; + + logic valid; + logic [7:0] data ; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if, + my_if.master_mp out_if + ); + + my_if my_i (); + + always @(posedge clk) + begin + my_i.valid <= in_if.valid; + my_i.data <= in_if.data; + end + + assign out_if.valid = my_i.valid; + assign out_if.data = my_i.data; + +endmodule diff --git a/test_regress/t/t_lint_iface_topmodule2.pl b/test_regress/t/t_lint_iface_topmodule2.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule2.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_topmodule2.v b/test_regress/t/t_lint_iface_topmodule2.v new file mode 100644 index 000000000..ec836205a --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule2.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( + parameter integer DW = 8 + ) (); + logic valid; + logic [DW-1:0] data; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if, + my_if.master_mp out_if + ); + + assign out_if.valid = in_if.valid; + assign out_if.data = in_if.data; + +endmodule diff --git a/test_regress/t/t_lint_iface_topmodule3.pl b/test_regress/t/t_lint_iface_topmodule3.pl new file mode 100755 index 000000000..7918d5f13 --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule3.pl @@ -0,0 +1,16 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(linter => 1); + +lint(); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_topmodule3.v b/test_regress/t/t_lint_iface_topmodule3.v new file mode 100644 index 000000000..a6084207b --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule3.v @@ -0,0 +1,67 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( parameter integer DW = 8 ) (input clk); + + localparam DW_LOCAL = DW; + + logic valid; + logic [DW-1:0] data; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + + function automatic integer width(); + return $bits(data); + endfunction + + generate + if (DW < 4) + begin: dw_lt_4_G + function automatic integer min_width(); + return 4; + endfunction + end + else + begin: dw_ge_4_G + function automatic integer min_width(); + return 8; + endfunction + end + endgenerate + +endinterface + +module t + ( + input wire clk, + my_if in_if, + my_if out_if + ); + + assign out_if.valid = in_if.valid; + assign out_if.data = in_if.data; + + my_if my_i (.clk(clk)); + + initial + begin + $display(in_if.DW_LOCAL); + $display(in_if.width()); + $display(in_if.dw_ge_4_G.min_width()); + $display(out_if.DW_LOCAL); + $display(out_if.width()); + $display(out_if.dw_ge_4_G.min_width()); + end + +endmodule diff --git a/test_regress/t/t_lint_iface_topmodule_bad.out b/test_regress/t/t_lint_iface_topmodule_bad.out new file mode 100644 index 000000000..b5263bb79 --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_lint_iface_topmodule_bad.v:8:23: Parameter without initial value is never given value (IEEE 1800-2017 6.20.1): 'DW' + : ... In instance t + 8 | parameter integer DW + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_lint_iface_topmodule_bad.pl b/test_regress/t/t_lint_iface_topmodule_bad.pl new file mode 100755 index 000000000..a82cf66cb --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_lint_iface_topmodule_bad.v b/test_regress/t/t_lint_iface_topmodule_bad.v new file mode 100644 index 000000000..04e51f7c3 --- /dev/null +++ b/test_regress/t/t_lint_iface_topmodule_bad.v @@ -0,0 +1,44 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2017 by Josh Redford. +// SPDX-License-Identifier: CC0-1.0 + +interface my_if #( + parameter integer DW + ) (); + + logic valid; + logic [DW-1:0] data ; + + modport slave_mp ( + input valid, + input data + ); + + modport master_mp ( + output valid, + output data + ); + +endinterface + +module t + ( + input wire clk, + my_if.slave_mp in_if, + my_if.master_mp out_if + ); + + my_if my_i (); + + always @(posedge clk) + begin + my_i.valid <= in_if.valid; + my_i.data <= in_if.data; + end + + assign out_if.valid = my_i.valid; + assign out_if.data = my_i.data; + +endmodule From f1ba6cb517d3aa74d37837bffe8eefa7e5307518 Mon Sep 17 00:00:00 2001 From: github action Date: Sat, 1 Oct 2022 14:53:40 +0000 Subject: [PATCH 066/177] Apply 'make format' --- src/V3Dead.cpp | 21 +++++++------ src/V3LinkDot.cpp | 31 ++++++++++++------- src/V3LinkLevel.cpp | 72 ++++++++++++++++++++++++++++++--------------- 3 files changed, 80 insertions(+), 44 deletions(-) diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index f3e8950f8..38800e6f9 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -425,7 +425,7 @@ private: void preserveTopIfaces(AstNetlist* rootp) { for (AstNodeModule* modp = rootp->modulesp(); modp && modp->level() <= 2; - modp = VN_AS(modp->nextp(), NodeModule)) { + modp = VN_AS(modp->nextp(), NodeModule)) { for (AstNode* subnodep = modp->stmtsp(); subnodep; subnodep = subnodep->nextp()) { if (AstVar* const varp = VN_CAST(subnodep, Var)) { if (varp->isIfaceRef()) { @@ -433,23 +433,24 @@ private: const AstIfaceRefDType* ifacerefp = nullptr; if (VN_IS(subtypep, IfaceRefDType)) { ifacerefp = VN_AS(varp->subDTypep(), IfaceRefDType); - } - else if (VN_IS(subtypep, BracketArrayDType)) { - const AstBracketArrayDType* const arrp = VN_AS(subtypep, BracketArrayDType); + } else if (VN_IS(subtypep, BracketArrayDType)) { + const AstBracketArrayDType* const arrp + = VN_AS(subtypep, BracketArrayDType); const AstNodeDType* const arrsubtypep = arrp->subDTypep(); if (VN_IS(arrsubtypep, IfaceRefDType)) { ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); } - } - else if (VN_IS(subtypep, UnpackArrayDType)) { - const AstUnpackArrayDType* const arrp = VN_AS(subtypep, UnpackArrayDType); + } else if (VN_IS(subtypep, UnpackArrayDType)) { + const AstUnpackArrayDType* const arrp + = VN_AS(subtypep, UnpackArrayDType); const AstNodeDType* const arrsubtypep = arrp->subDTypep(); if (VN_IS(arrsubtypep, IfaceRefDType)) { ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); } } - if (ifacerefp && !ifacerefp->cellp() && (ifacerefp->ifacep()->user1() == 0)) { + if (ifacerefp && !ifacerefp->cellp() + && (ifacerefp->ifacep()->user1() == 0)) { ifacerefp->ifacep()->user1(1); } } @@ -491,7 +492,9 @@ public: void V3Dead::deadifyModules(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); - { DeadVisitor{nodep, false, false, false, false, !v3Global.opt.topIfacesSupported()}; } // Destruct before checking + { + DeadVisitor{nodep, false, false, false, false, !v3Global.opt.topIfacesSupported()}; + } // Destruct before checking V3Global::dumpCheckGlobalTree("deadModules", 0, dumpTree() >= 6); } diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index f5d93991a..0611e6297 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -310,8 +310,8 @@ public: } VSymEnt* insertTopIface(AstCell* nodep, const string& scopename) { VSymEnt* const symp = new VSymEnt{&m_syms, nodep}; - UINFO(9, - " INSERTtopiface se" << cvtToHex(symp) << " " << scopename << " " << nodep << endl); + UINFO(9, " INSERTtopiface se" << cvtToHex(symp) << " " << scopename << " " << nodep + << endl); symp->parentp(rootEntp()); // Needed so backward search can find name of top module symp->fallbackp(dunitEntp()); // Needed so can find $unit stuff nodep->user1p(symp); @@ -786,16 +786,16 @@ class LinkDotFindVisitor final : public VNVisitor { const AstIfaceRefDType* ifacerefp = nullptr; if (VN_IS(subtypep, IfaceRefDType)) { ifacerefp = VN_AS(varp->subDTypep(), IfaceRefDType); - } - else if (VN_IS(subtypep, BracketArrayDType)) { - const AstBracketArrayDType* const arrp = VN_AS(subtypep, BracketArrayDType); + } else if (VN_IS(subtypep, BracketArrayDType)) { + const AstBracketArrayDType* const arrp + = VN_AS(subtypep, BracketArrayDType); const AstNodeDType* const arrsubtypep = arrp->subDTypep(); if (VN_IS(arrsubtypep, IfaceRefDType)) { ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); } - } - else if (VN_IS(subtypep, UnpackArrayDType)) { - const AstUnpackArrayDType* const arrp = VN_AS(subtypep, UnpackArrayDType); + } else if (VN_IS(subtypep, UnpackArrayDType)) { + const AstUnpackArrayDType* const arrp + = VN_AS(subtypep, UnpackArrayDType); const AstNodeDType* const arrsubtypep = arrp->subDTypep(); if (VN_IS(arrsubtypep, IfaceRefDType)) { ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); @@ -803,10 +803,19 @@ class LinkDotFindVisitor final : public VNVisitor { } if (ifacerefp && !ifacerefp->cellp()) { - // A dummy cell to keep the top level interface alive and correctly optimized for default parameter values - AstCell* ifacecellp = new AstCell{nodep->fileline(), nodep->fileline(), modp->name() + "__02E" + varp->name(), ifacerefp->ifaceName(), nullptr, nullptr, nullptr}; + // A dummy cell to keep the top level interface alive and correctly + // optimized for default parameter values + AstCell* ifacecellp + = new AstCell{nodep->fileline(), + nodep->fileline(), + modp->name() + "__02E" + varp->name(), + ifacerefp->ifaceName(), + nullptr, + nullptr, + nullptr}; ifacecellp->modp(ifacerefp->ifacep()); - m_curSymp = m_modSymp = m_statep->insertTopIface(ifacecellp, m_scope); + m_curSymp = m_modSymp + = m_statep->insertTopIface(ifacecellp, m_scope); { iterate(ifacecellp); } } } diff --git a/src/V3LinkLevel.cpp b/src/V3LinkLevel.cpp index fbc4d86bd..417cd08dc 100644 --- a/src/V3LinkLevel.cpp +++ b/src/V3LinkLevel.cpp @@ -199,8 +199,7 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { } else { ioNames.insert(oldvarp->name()); } - } - else if (v3Global.opt.topIfacesSupported() && oldvarp->isIfaceRef()) { + } else if (v3Global.opt.topIfacesSupported() && oldvarp->isIfaceRef()) { const AstNodeDType* const subtypep = oldvarp->subDTypep(); if (VN_IS(subtypep, IfaceRefDType)) { const AstIfaceRefDType* const ifacerefp = VN_AS(subtypep, IfaceRefDType); @@ -217,10 +216,12 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { const AstUnpackArrayDType* const arrp = VN_AS(subtypep, UnpackArrayDType); const AstNodeDType* const arrsubtypep = arrp->subDTypep(); if (VN_IS(arrsubtypep, IfaceRefDType)) { - const AstIfaceRefDType* const ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); + const AstIfaceRefDType* const ifacerefp + = VN_AS(arrsubtypep, IfaceRefDType); if (!ifacerefp->cellp()) { if (ioNames.find(oldvarp->name()) != ioNames.end()) { - // UINFO(8, "Multitop dup interface array found: " << oldvarp << endl); + // UINFO(8, "Multitop dup interface array found: " << oldvarp + // << endl); dupNames.insert(oldvarp->name()); } else { ioNames.insert(oldvarp->name()); @@ -285,9 +286,9 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { // Skip length and width comp; we know it's a direct assignment pinp->modVarp(oldvarp); cellp->addPinsp(pinp); - } - else if (v3Global.opt.topIfacesSupported() && oldvarp->isIfaceRef()) { - // for each interface port on oldmodp instantiate a corresponding interface cell in $root + } else if (v3Global.opt.topIfacesSupported() && oldvarp->isIfaceRef()) { + // for each interface port on oldmodp instantiate a corresponding interface + // cell in $root const AstNodeDType* const subtypep = oldvarp->subDTypep(); if (VN_IS(subtypep, IfaceRefDType)) { const AstIfaceRefDType* const ifacerefp = VN_AS(subtypep, IfaceRefDType); @@ -298,34 +299,44 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { name = oldmodp->name() + "__02E" + name; } - AstCell* ifacecellp = new AstCell{newmodp->fileline(), newmodp->fileline(), name, ifacerefp->ifaceName(), nullptr, nullptr, nullptr}; + AstCell* ifacecellp = new AstCell{newmodp->fileline(), + newmodp->fileline(), + name, + ifacerefp->ifaceName(), + nullptr, + nullptr, + nullptr}; ifacecellp->modp(ifacerefp->ifacep()); newmodp->addStmtsp(ifacecellp); - AstIfaceRefDType* const idtypep = new AstIfaceRefDType{newmodp->fileline(), name, ifacerefp->ifaceName()}; + AstIfaceRefDType* const idtypep = new AstIfaceRefDType{ + newmodp->fileline(), name, ifacerefp->ifaceName()}; idtypep->ifacep(nullptr); idtypep->dtypep(idtypep); idtypep->cellp(ifacecellp); rootp->typeTablep()->addTypesp(idtypep); - AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF, name + "__Viftop", idtypep}; + AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF, + name + "__Viftop", idtypep}; varp->isIfaceParent(true); ifacecellp->addNextHere(varp); ifacecellp->hasIfaceVar(true); - AstPin* const pinp = new AstPin{ - oldvarp->fileline(), 0, varp->name(), - new AstVarRef{varp->fileline(), varp, - oldvarp->isWritable() ? VAccess::WRITE : VAccess::READ}}; + AstPin* const pinp + = new AstPin{oldvarp->fileline(), 0, varp->name(), + new AstVarRef{varp->fileline(), varp, + oldvarp->isWritable() ? VAccess::WRITE + : VAccess::READ}}; pinp->modVarp(oldvarp); cellp->addPinsp(pinp); } - } - else if (VN_IS(subtypep, UnpackArrayDType)) { - const AstUnpackArrayDType* const oldarrp = VN_AS(subtypep, UnpackArrayDType); + } else if (VN_IS(subtypep, UnpackArrayDType)) { + const AstUnpackArrayDType* const oldarrp + = VN_AS(subtypep, UnpackArrayDType); const AstNodeDType* const arrsubtypep = oldarrp->subDTypep(); if (VN_IS(arrsubtypep, IfaceRefDType)) { - const AstIfaceRefDType* const ifacerefp = VN_AS(arrsubtypep, IfaceRefDType); + const AstIfaceRefDType* const ifacerefp + = VN_AS(arrsubtypep, IfaceRefDType); if (!ifacerefp->cellp()) { string name = oldvarp->name(); if (dupNames.find(name) != dupNames.end()) { @@ -333,19 +344,31 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { name = oldmodp->name() + "__02E" + name; } - AstUnpackArrayDType* arraydtypep = VN_AS(oldvarp->dtypep(), UnpackArrayDType); - AstCell* ifacearraycellp = new AstCell{newmodp->fileline(), newmodp->fileline(), name, ifacerefp->ifaceName(), nullptr, nullptr, arraydtypep->rangep()->cloneTree(true)}; + AstUnpackArrayDType* arraydtypep + = VN_AS(oldvarp->dtypep(), UnpackArrayDType); + AstCell* ifacearraycellp + = new AstCell{newmodp->fileline(), + newmodp->fileline(), + name, + ifacerefp->ifaceName(), + nullptr, + nullptr, + arraydtypep->rangep()->cloneTree(true)}; ifacearraycellp->modp(ifacerefp->ifacep()); newmodp->addStmtsp(ifacearraycellp); - AstIfaceRefDType* const idtypep = new AstIfaceRefDType{newmodp->fileline(), name, ifacerefp->ifaceName()}; + AstIfaceRefDType* const idtypep = new AstIfaceRefDType{ + newmodp->fileline(), name, ifacerefp->ifaceName()}; idtypep->ifacep(nullptr); idtypep->dtypep(idtypep); idtypep->cellp(ifacearraycellp); rootp->typeTablep()->addTypesp(idtypep); - AstNodeArrayDType* const arrp = new AstUnpackArrayDType{newmodp->fileline(), idtypep, arraydtypep->rangep()->cloneTree(true)}; - AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF, name + "__Viftop", arrp}; + AstNodeArrayDType* const arrp = new AstUnpackArrayDType{ + newmodp->fileline(), idtypep, + arraydtypep->rangep()->cloneTree(true)}; + AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF, + name + "__Viftop", arrp}; varp->isIfaceParent(true); ifacearraycellp->addNextHere(varp); ifacearraycellp->hasIfaceVar(true); @@ -354,7 +377,8 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { AstPin* const pinp = new AstPin{ oldvarp->fileline(), 0, varp->name(), new AstVarRef{varp->fileline(), varp, - oldvarp->isWritable() ? VAccess::WRITE : VAccess::READ}}; + oldvarp->isWritable() ? VAccess::WRITE + : VAccess::READ}}; pinp->modVarp(oldvarp); cellp->addPinsp(pinp); } From 526e6b9fc7142aa5dcf9f0cab0babdf41afbea46 Mon Sep 17 00:00:00 2001 From: Marcel Chang <112921644+marcelchangTW@users.noreply.github.com> Date: Sat, 1 Oct 2022 23:05:33 +0800 Subject: [PATCH 067/177] Add --dump-tree-dot to enable dumping Ast Tree .dot files (#3636) --- docs/CONTRIBUTORS | 1 + docs/guide/exe_verilator.rst | 6 +++++ docs/internals.rst | 10 ++++++++ src/V3Ast.cpp | 40 +++++++++++++++++++++++++++++++ src/V3Ast.h | 2 ++ src/V3Global.cpp | 7 ++++-- src/V3Options.cpp | 5 ++++ src/V3Options.h | 1 + test_regress/t/t_dump_tree_dot.pl | 20 ++++++++++++++++ 9 files changed, 90 insertions(+), 2 deletions(-) create mode 100755 test_regress/t/t_dump_tree_dot.pl diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 0c3be357f..df73f955e 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -71,6 +71,7 @@ Ludwig Rogiers Lukasz Dalek Maarten De Braekeleer Maciej Sobkowski +Marcel Chang Marco Widmer Mariusz Glebocki Markus Krause diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index a54510df4..b800261a2 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -373,6 +373,12 @@ Summary: <--dump-tree>` may be useful if the dump files are large and not desired. +.. option:: --dump-tree-dot + + Rarely needed. Enable dumping Ast .tree.dot debug files in Graphviz + Dot format. This option implies :vlopt:`--dump-tree`, unless + :vlopt:`--dumpi-tree` was passed explicitly. + .. option:: --dump-tree-addrids Rarely needed - for developer use. Replace AST node addresses with diff --git a/docs/internals.rst b/docs/internals.rst index c62881026..40cc6b3aa 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -1137,6 +1137,16 @@ Similarly the ``NETLIST`` has a list of modules referred to by its ``op1p()`` pointer. +.tree.dot Output +---------------- + +``*.tree.dot`` files are dumps of the AST Tree in `Graphviz +`__ dot format. This can be used to +visualize the AST Tree. The vertices correspond to ``AstNode`` +instances, and the edges represent the pointers (``op1p``, + ``op2p``, etc) between the nodes. + + Debugging with GDB ------------------ diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 8f7b102ef..c83af8820 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -1141,6 +1141,46 @@ void AstNode::dumpTreeFile(const string& filename, bool append, bool doDump, boo } } +static void drawChildren(std::ostream& os, const AstNode* thisp, const AstNode* childp, const std::string& childName) { + if (childp) { + os << "\tn" << cvtToHex(thisp) << " -> n" << cvtToHex(childp) << " [" + << "label=\"" << childName << "\" color=red];\n"; + for (const AstNode* nodep = childp; nodep; nodep = nodep->nextp()) { + nodep->dumpTreeDot(os); + if (nodep->nextp()) { + os << "\tn" << cvtToHex(nodep) << " -> n" << cvtToHex(nodep->nextp()) << " [" + << "label=\"next\" color=red];\n"; + os << "\t{rank=same; n" << cvtToHex(nodep) << ", n" << cvtToHex(nodep->nextp()) + << "}\n"; + } + } + } +} + +void AstNode::dumpTreeDot(std::ostream& os) const { + os << "\tn" << cvtToHex(this) << "\t[" + << "label=\"" << typeName() << "\\n" < treedotp{V3File::new_ofstream(filename, append)}; + if (treedotp->fail()) v3fatal("Can't write " << filename); + *treedotp << "digraph vTree{\n"; + *treedotp << "\tgraph\t[label=\"" << filename + ".dot" << "\",\n"; + *treedotp << "\t\t labelloc=t, labeljust=l,\n"; + *treedotp << "\t\t //size=\"7.5,10\",\n" << "];\n"; + dumpTreeDot(*treedotp); + *treedotp << "}\n"; + } +} + void AstNode::v3errorEndFatal(std::ostringstream& str) const { v3errorEnd(str); assert(0); // LCOV_EXCL_LINE diff --git a/src/V3Ast.h b/src/V3Ast.h index 01a6af34c..b00436897 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -1862,6 +1862,8 @@ public: void dumpTreeFile(const string& filename, bool append = false, bool doDump = true, bool doCheck = true); static void dumpTreeFileGdb(const AstNode* nodep, const char* filenamep = nullptr); + void dumpTreeDot(std::ostream& os = std::cout) const; + void dumpTreeDotFile(const string& filename, bool append = false, bool doDump = true); // METHODS - queries // Changes control flow, disable some optimizations diff --git a/src/V3Global.cpp b/src/V3Global.cpp index 5510386f0..34cbe343b 100644 --- a/src/V3Global.cpp +++ b/src/V3Global.cpp @@ -93,8 +93,11 @@ string V3Global::digitsFilename(int number) { } void V3Global::dumpCheckGlobalTree(const string& stagename, int newNumber, bool doDump) { - v3Global.rootp()->dumpTreeFile(v3Global.debugFilename(stagename + ".tree", newNumber), false, - doDump); + const string treeFilename = v3Global.debugFilename(stagename + ".tree", newNumber); + v3Global.rootp()->dumpTreeFile(treeFilename, false, doDump); + if (v3Global.opt.dumpTreeDot()) { + v3Global.rootp()->dumpTreeDotFile(treeFilename + ".dot", false, doDump); + } if (v3Global.opt.stats()) V3Stats::statsStage(stagename); } diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 4456f601e..235cef99f 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -820,6 +820,11 @@ void V3Options::notify() { // Mark options as available m_available = true; + + // --dump-tree-dot will turn on tree dumping. + if (!m_dumpLevel.count("tree") && m_dumpLevel.count("tree-dot")) { + m_dumpLevel["tree"] = m_dumpLevel["tree-dot"]; + } } //###################################################################### diff --git a/src/V3Options.h b/src/V3Options.h index 69870a701..4acf71308 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -454,6 +454,7 @@ public: bool decoration() const { return m_decoration; } bool dpiHdrOnly() const { return m_dpiHdrOnly; } bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); } + bool dumpTreeDot() const { return m_dumpLevel.count("tree-dot") && m_dumpLevel.at("tree-dot"); } bool exe() const { return m_exe; } bool flatten() const { return m_flatten; } bool gmake() const { return m_gmake; } diff --git a/test_regress/t/t_dump_tree_dot.pl b/test_regress/t/t_dump_tree_dot.pl new file mode 100755 index 000000000..8caebed51 --- /dev/null +++ b/test_regress/t/t_dump_tree_dot.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vlt => 1); + +top_filename("t/t_EXAMPLE.v"); + +lint( + v_flags => ["--lint-only --dump-tree-dot"], + ); + +ok(1); +1; From a204b24fcfd3f78035f6b7d81646d4bd209fe29a Mon Sep 17 00:00:00 2001 From: github action Date: Sat, 1 Oct 2022 15:06:12 +0000 Subject: [PATCH 068/177] Apply 'make format' --- src/V3Ast.cpp | 13 ++++++++----- src/V3Options.h | 4 +++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index c83af8820..1bd1ab6b9 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -1141,7 +1141,8 @@ void AstNode::dumpTreeFile(const string& filename, bool append, bool doDump, boo } } -static void drawChildren(std::ostream& os, const AstNode* thisp, const AstNode* childp, const std::string& childName) { +static void drawChildren(std::ostream& os, const AstNode* thisp, const AstNode* childp, + const std::string& childName) { if (childp) { os << "\tn" << cvtToHex(thisp) << " -> n" << cvtToHex(childp) << " [" << "label=\"" << childName << "\" color=red];\n"; @@ -1159,8 +1160,8 @@ static void drawChildren(std::ostream& os, const AstNode* thisp, const AstNode* void AstNode::dumpTreeDot(std::ostream& os) const { os << "\tn" << cvtToHex(this) << "\t[" - << "label=\"" << typeName() << "\\n" < treedotp{V3File::new_ofstream(filename, append)}; if (treedotp->fail()) v3fatal("Can't write " << filename); *treedotp << "digraph vTree{\n"; - *treedotp << "\tgraph\t[label=\"" << filename + ".dot" << "\",\n"; + *treedotp << "\tgraph\t[label=\"" << filename + ".dot" + << "\",\n"; *treedotp << "\t\t labelloc=t, labeljust=l,\n"; - *treedotp << "\t\t //size=\"7.5,10\",\n" << "];\n"; + *treedotp << "\t\t //size=\"7.5,10\",\n" + << "];\n"; dumpTreeDot(*treedotp); *treedotp << "}\n"; } diff --git a/src/V3Options.h b/src/V3Options.h index 4acf71308..9f1bd687c 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -454,7 +454,9 @@ public: bool decoration() const { return m_decoration; } bool dpiHdrOnly() const { return m_dpiHdrOnly; } bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); } - bool dumpTreeDot() const { return m_dumpLevel.count("tree-dot") && m_dumpLevel.at("tree-dot"); } + bool dumpTreeDot() const { + return m_dumpLevel.count("tree-dot") && m_dumpLevel.at("tree-dot"); + } bool exe() const { return m_exe; } bool flatten() const { return m_flatten; } bool gmake() const { return m_gmake; } From 694bdbc1305aed153cff04ea4febbf15c6d78b9c Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 30 Sep 2022 16:19:21 +0100 Subject: [PATCH 069/177] DFG: Improve .dot dumps slightly --- src/V3Dfg.cpp | 58 ++++++++++++++++++++++++++------ src/V3Dfg.h | 6 ++++ test_regress/t/t_dfg_peephole.pl | 1 + 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index fb2c74d44..8c86710b8 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -540,12 +540,18 @@ void DfgGraph::runToFixedPoint(std::function f) { static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; } +static bool isSimpleSel(const DfgSel* vtxp) { + const DfgConst* const lp = vtxp->lsbp()->cast(); + const DfgConst* const wp = vtxp->widthp()->cast(); + return lp && wp && !lp->hasMultipleSinks() && !wp->hasMultipleSinks(); +} + // Dump one DfgVertex in Graphviz format static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { - os << toDotId(vtx); if (const DfgVarPacked* const varVtxp = vtx.cast()) { AstVar* const varp = varVtxp->varp(); + os << toDotId(vtx); os << " [label=\"" << varp->name() << "\nW" << varVtxp->width() << " / F" << varVtxp->fanout() << '"'; @@ -570,7 +576,9 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { if (const DfgVarArray* const arrVtxp = vtx.cast()) { AstVar* const varp = arrVtxp->varp(); - os << " [label=\"" << varp->name() << "[]\""; + const int elements = VN_AS(arrVtxp->dtypep(), UnpackArrayDType)->elementsConst(); + os << toDotId(vtx); + os << " [label=\"" << varp->name() << "[" << elements << "]\""; if (varp->direction() == VDirection::INPUT) { os << ", shape=box3d, style=filled, fillcolor=chartreuse2"; // Green } else if (varp->direction() == VDirection::OUTPUT) { @@ -591,18 +599,18 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { } if (const DfgConst* const constVtxp = vtx.cast()) { + const bool feedsSimpleSel = !constVtxp->findSink([](const DfgVertex& v) { // + return !v.is() || !isSimpleSel(v.as()); + }); + if (feedsSimpleSel) return; // Will draw it in the sel node as it is very common + const V3Number& num = constVtxp->constp()->num(); + + os << toDotId(vtx); os << " [label=\""; if (num.width() <= 32 && !num.isSigned()) { - const bool feedsSel = !constVtxp->findSink([](const DfgVertex& vtx) { // - return !vtx.is() && !vtx.is(); - }); - if (feedsSel) { - os << num.toUInt(); - } else { - os << constVtxp->width() << "'d" << num.toUInt() << "\n"; - os << constVtxp->width() << "'h" << std::hex << num.toUInt() << std::dec; - } + os << constVtxp->width() << "'d" << num.toUInt() << "\n"; + os << constVtxp->width() << "'h" << std::hex << num.toUInt() << std::dec; } else { os << num.ascii(); } @@ -612,6 +620,24 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { return; } + if (const DfgSel* const selVtxp = vtx.cast()) { + if (isSimpleSel(selVtxp)) { + const uint32_t lsb = selVtxp->lsbp()->as()->toU32(); + const uint32_t msb = lsb + selVtxp->width() - 1; + os << toDotId(vtx); + os << " [label=\"SEL\n_[" << msb << ":" << lsb << "]\nW" << vtx.width() << " / F" + << vtx.fanout() << '"'; + if (vtx.hasMultipleSinks()) { + os << ", shape=doublecircle"; + } else { + os << ", shape=circle"; + } + os << "]" << endl; + return; + } + } + + os << toDotId(vtx); os << " [label=\"" << vtx.typeName() << "\nW" << vtx.width() << " / F" << vtx.fanout() << '"'; if (vtx.hasMultipleSinks()) { os << ", shape=doublecircle"; @@ -631,6 +657,16 @@ static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, const string& hea // Dump one DfgVertex and all of its source DfgEdges in Graphviz format static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) { dumpDotVertex(os, vtx); + + if (const DfgSel* const selVtxp = vtx.cast()) { + if (isSimpleSel(selVtxp)) { + UASSERT_OBJ(selVtxp->sourceEdge<0>()->sourcep() == selVtxp->fromp(), selVtxp, + "Operand ordering changed"); + dumpDotEdge(os, *selVtxp->sourceEdge<0>(), ""); + return; + } + } + vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { // if (edge.sourcep()) { string headLabel; diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 9ed089ea9..7f30ea94d 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -484,6 +484,12 @@ public: return &m_srcs[Index]; } + template + const DfgEdge* sourceEdge() const { + static_assert(Index < Arity, "Source index out of range"); + return &m_srcs[Index]; + } + template DfgVertex* source() const { static_assert(Index < Arity, "Source index out of range"); diff --git a/test_regress/t/t_dfg_peephole.pl b/test_regress/t/t_dfg_peephole.pl index ded92d5f5..2cd2887f2 100755 --- a/test_regress/t/t_dfg_peephole.pl +++ b/test_regress/t/t_dfg_peephole.pl @@ -42,6 +42,7 @@ close $wrFile; compile( verilator_flags2 => ["--stats", "--build", "--exe", "-Mdir", "$Self->{obj_dir}/obj_opt", "--prefix", "Vopt", + "--dump-dfg", # To fill code coverage "-CFLAGS \"-I .. -I ../obj_ref\"", "../obj_ref/Vref__ALL.a", "../../t/$Self->{name}.cpp"], From 84b9502af48ca255fbfdb1ef9ee4b3bc988147d6 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 30 Sep 2022 16:19:53 +0100 Subject: [PATCH 070/177] DFG: Add more peephole patterns --- src/V3Dfg.h | 18 +- src/V3DfgPeephole.cpp | 281 +++++++++++++++++++++++++---- src/V3DfgPeephole.h | 118 ++++++------ test_regress/t/t_dfg_peephole.cpp | 29 ++- test_regress/t/t_dfg_peephole.pl | 128 +++++-------- test_regress/t/t_dfg_peephole.v | 286 ++++++++++-------------------- 6 files changed, 493 insertions(+), 367 deletions(-) diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 7f30ea94d..1cfe8768e 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -39,6 +39,7 @@ #include "V3Hasher.h" #include "V3List.h" +#include #include #include #include @@ -275,6 +276,8 @@ public: FileLine* fileline() const { return m_filelinep; } // The data type of the result of the nodes AstNodeDType* dtypep() const { return m_dtypep; } + // The type of this vertex + DfgType type() const { return m_type; } // Width of result uint32_t width() const { @@ -365,7 +368,7 @@ public: // Returns first source edge which satisfies the given predicate 'p', or nullptr if no such // sink vertex exists - inline DfgEdge* findSourceEdge(std::function p); + inline const DfgEdge* findSourceEdge(std::function p) const; // Returns first sink vertex of type 'Vertex' which satisfies the given predicate 'p', // or nullptr if no such sink vertex exists @@ -747,6 +750,13 @@ public: FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; } uint32_t driverIndex(size_t idx) const { return m_driverData[idx].second; } + DfgVertex* driverAt(size_t idx) const { + const DfgEdge* const edgep = findSourceEdge([=](const DfgEdge&, size_t i) { // + return driverIndex(i) == idx; + }); + return edgep ? edgep->sourcep() : nullptr; + } + const string srcName(size_t idx) const override { return cvtToStr(driverIndex(idx)); } }; @@ -897,12 +907,12 @@ void DfgVertex::forEachSinkEdge(std::function f) const { } } -DfgEdge* DfgVertex::findSourceEdge(std::function p) { +const DfgEdge* DfgVertex::findSourceEdge(std::function p) const { const auto pair = sourceEdges(); - DfgEdge* const edgesp = pair.first; + const DfgEdge* const edgesp = pair.first; const size_t arity = pair.second; for (size_t i = 0; i < arity; ++i) { - DfgEdge& edge = edgesp[i]; + const DfgEdge& edge = edgesp[i]; if (p(edge, i)) return &edge; } return nullptr; diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 19f98314c..8ba02b5e2 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -62,6 +62,24 @@ V3DfgPeepholeContext::~V3DfgPeepholeContext() { #undef OPTIMIZATION_EMIT_STATS } +// clang-format off +template +struct ReductionToBitwiseImpl {}; +template <> struct ReductionToBitwiseImpl { using type = DfgAnd; }; +template <> struct ReductionToBitwiseImpl { using type = DfgOr; }; +template <> struct ReductionToBitwiseImpl { using type = DfgXor; }; +template +using ReductionToBitwise = typename ReductionToBitwiseImpl::type; + +template +struct BitwiseToReductionImpl {}; +template <> struct BitwiseToReductionImpl { using type = DfgRedAnd; }; +template <> struct BitwiseToReductionImpl { using type = DfgRedOr; }; +template <> struct BitwiseToReductionImpl { using type = DfgRedXor; }; +template +using BitwiseToReduction = typename BitwiseToReductionImpl::type; +// clang-format on + class V3DfgPeephole final : public DfgVisitor { // STATE @@ -133,6 +151,22 @@ class V3DfgPeephole final : public DfgVisitor { } } + // Transformations that apply to all associative binary vertices + void associativeBinary(DfgVertexWithArity<2>* vtxp) { + DfgVertex* const lhsp = vtxp->lhsp(); + + // Make associative trees right leaning (for better CSE opportunities) + if (lhsp->type() == vtxp->type() && !lhsp->hasMultipleSinks()) { + DfgVertexWithArity<2>* const lBinp = static_cast*>(lhsp); + APPLYING(RIGHT_LEANING_ASSOC) { + vtxp->replaceWith(lBinp); + vtxp->lhsp(lBinp->rhsp()); + lBinp->rhsp(vtxp); + return; + } + } + } + // Bitwise operation with one side Const, and the other side a Concat template bool tryPushBitwiseOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) { @@ -226,33 +260,56 @@ class V3DfgPeephole final : public DfgVisitor { return false; } - template - void optimizeReduction(Vertex* vtxp) { - static_assert(std::is_same::value - || std::is_same::value - || std::is_same::value, - "Invalid 'Vertex' type for this method"); + template + bool tryPushBitwiseOpThroughReductions(Bitwise* vtxp) { + using Reduction = BitwiseToReduction; + + if (Reduction* const lRedp = vtxp->lhsp()->template cast()) { + if (Reduction* const rRedp = vtxp->rhsp()->template cast()) { + DfgVertex* const lSrcp = lRedp->srcp(); + DfgVertex* const rSrcp = rRedp->srcp(); + if (lSrcp->dtypep() == rSrcp->dtypep()) { + APPLYING(PUSH_BITWISE_THROUGH_REDUCTION) { + FileLine* const flp = vtxp->fileline(); + Bitwise* const bwp = new Bitwise{m_dfg, flp, lSrcp->dtypep()}; + bwp->lhsp(lSrcp); + bwp->rhsp(rSrcp); + Reduction* const redp = new Reduction{m_dfg, flp, m_bitDType}; + redp->srcp(bwp); + vtxp->replaceWith(redp); + return true; + } + } + } + } + + return false; + } + + template + void optimizeReduction(Reduction* vtxp) { + using Bitwise = ReductionToBitwise; DfgVertex* const srcp = vtxp->srcp(); + FileLine* const flp = vtxp->fileline(); - // Reduction of 1-bit value -- Currently unreachable as V3Const can remove these, but - // in the future they will be created during this optimization. - if (srcp->width() == 1) { // LCOV_EXCL_START + // Reduction of 1-bit value + if (srcp->width() == 1) { APPLYING(REMOVE_WIDTH_ONE_REDUCTION) { vtxp->replaceWith(srcp); return; } - } // LCOV_EXCL_STOP + } if (DfgCond* const condp = srcp->cast()) { if (condp->thenp()->is() || condp->elsep()->is()) { APPLYING(PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) { // The new 'then' vertex - Vertex* const newThenp = new Vertex{m_dfg, vtxp->fileline(), m_bitDType}; + Reduction* const newThenp = new Reduction{m_dfg, flp, m_bitDType}; newThenp->srcp(condp->thenp()); // The new 'else' vertex - Vertex* const newElsep = new Vertex{m_dfg, vtxp->fileline(), m_bitDType}; + Reduction* const newElsep = new Reduction{m_dfg, flp, m_bitDType}; newElsep->srcp(condp->elsep()); // The replacement Cond vertex @@ -268,12 +325,33 @@ class V3DfgPeephole final : public DfgVisitor { } } + if (DfgConcat* const concatp = srcp->cast()) { + APPLYING(PUSH_REDUCTION_THROUGH_CONCAT) { + // Reduce the parts of the concatenation + Reduction* const lReducep = new Reduction{m_dfg, concatp->fileline(), m_bitDType}; + lReducep->srcp(concatp->lhsp()); + Reduction* const rReducep = new Reduction{m_dfg, concatp->fileline(), m_bitDType}; + rReducep->srcp(concatp->rhsp()); + + // Bitwise reduce the results + DfgVertexWithArity<2>* const replacementp = new Bitwise{m_dfg, flp, m_bitDType}; + replacementp->lhsp(lReducep); + replacementp->rhsp(rReducep); + vtxp->replaceWith(replacementp); + + // Optimize the new reductions + optimizeReduction(lReducep); + optimizeReduction(rReducep); + return; + } + } + if (DfgConst* const constp = srcp->cast()) { APPLYING(REPLACE_REDUCTION_OF_CONST) { - DfgConst* const replacementp = makeZero(vtxp->fileline(), 1); - if (std::is_same::value) { + DfgConst* const replacementp = makeZero(flp, 1); + if VL_CONSTEXPR_CXX17 (std::is_same::value) { replacementp->num().opRedAnd(constp->num()); - } else if (std::is_same::value) { + } else if VL_CONSTEXPR_CXX17 (std::is_same::value) { replacementp->num().opRedOr(constp->num()); } else { replacementp->num().opRedXor(constp->num()); @@ -304,8 +382,9 @@ class V3DfgPeephole final : public DfgVisitor { FileLine* const flp = vtxp->fileline(); - // Convert Extend into Concat with zeros. This simplifies other patterns as they only need - // to handle Concat, which is more generic, and don't need special cases for Extend. + // Convert Extend into Concat with zeros. This simplifies other patterns as they only + // need to handle Concat, which is more generic, and don't need special cases for + // Extend. APPLYING(REPLACE_EXTEND) { DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; replacementp->lhsp(makeZero(flp, extension)); @@ -354,6 +433,17 @@ class V3DfgPeephole final : public DfgVisitor { } } + // Not of Eq + if (DfgEq* const eqp = vtxp->srcp()->cast()) { + APPLYING(REPLACE_NOT_EQ) { + DfgNeq* const replacementp = new DfgNeq{m_dfg, eqp->fileline(), vtxp->dtypep()}; + replacementp->lhsp(eqp->lhsp()); + replacementp->rhsp(eqp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } + } + // Not of Neq if (DfgNeq* const neqp = vtxp->srcp()->cast()) { APPLYING(REPLACE_NOT_NEQ) { @@ -381,22 +471,38 @@ class V3DfgPeephole final : public DfgVisitor { UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); commutativeBinary(vtxp); + associativeBinary(vtxp); DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); - FileLine* const flp = vtxp->fileline(); // Bubble pushing - if (lhsp->is() && rhsp->is()) { - APPLYING(REPLACE_AND_OF_NOT_AND_NOT) { - DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()}; - orp->lhsp(lhsp->as()->srcp()); - orp->rhsp(rhsp->as()->srcp()); - DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; - notp->srcp(orp); - vtxp->replaceWith(notp); - return; + if (DfgNot* const lhsNotp = lhsp->cast()) { + if (DfgNot* const rhsNotp = rhsp->cast()) { + APPLYING(REPLACE_AND_OF_NOT_AND_NOT) { + DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()}; + orp->lhsp(lhsNotp->srcp()); + orp->rhsp(rhsNotp->srcp()); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(orp); + vtxp->replaceWith(notp); + return; + } + } + if (DfgNeq* const rhsNeqp = rhsp->cast()) { + APPLYING(REPLACE_AND_OF_NOT_AND_NEQ) { + DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()}; + orp->lhsp(lhsNotp->srcp()); + DfgEq* const newRhsp = new DfgEq{m_dfg, rhsp->fileline(), rhsp->dtypep()}; + newRhsp->lhsp(rhsNeqp->lhsp()); + newRhsp->rhsp(rhsNeqp->rhsp()); + orp->rhsp(newRhsp); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(orp); + vtxp->replaceWith(notp); + return; + } } } @@ -429,6 +535,8 @@ class V3DfgPeephole final : public DfgVisitor { } } + if (tryPushBitwiseOpThroughReductions(vtxp)) return; + if (DfgNot* const lhsNotp = lhsp->cast()) { // ~A & A is all zeroes if (lhsNotp->srcp() == rhsp) { @@ -446,10 +554,10 @@ class V3DfgPeephole final : public DfgVisitor { UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); commutativeBinary(vtxp); + associativeBinary(vtxp); DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); - FileLine* const flp = vtxp->fileline(); // Bubble pushing @@ -537,6 +645,8 @@ class V3DfgPeephole final : public DfgVisitor { } } + if (tryPushBitwiseOpThroughReductions(vtxp)) return; + if (DfgNot* const lhsNotp = lhsp->cast()) { // ~A | A is all ones if (lhsNotp->srcp() == rhsp) { @@ -555,15 +665,42 @@ class V3DfgPeephole final : public DfgVisitor { UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); commutativeBinary(vtxp); + associativeBinary(vtxp); DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); + FileLine* const flp = vtxp->fileline(); - if (DfgConst* const lhsConstp = lhsp->cast()) { - if (DfgConcat* const rhsConcatp = rhsp->cast()) { - if (tryPushBitwiseOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; + if (DfgConst* const lConstp = lhsp->cast()) { + if (lConstp->isZero()) { + APPLYING(REMOVE_XOR_WITH_ZERO) { + vtxp->replaceWith(rhsp); + return; + } + } + if (lConstp->isOnes()) { + APPLYING(REPLACE_XOR_WITH_ONES) { + DfgNot* const replacementp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + replacementp->srcp(rhsp); + vtxp->replaceWith(replacementp); + return; + } + } + if (DfgConcat* const rConcatp = rhsp->cast()) { + tryPushBitwiseOpThroughConcat(vtxp, lConstp, rConcatp); + return; + } + if (DfgConst* const rConstp = rhsp->cast()) { + APPLYING(REPLACE_XOR_OF_CONST_AND_CONST) { + DfgConst* const replacementp = makeZero(flp, vtxp->width()); + replacementp->num().opXor(lConstp->num(), rConstp->num()); + vtxp->replaceWith(replacementp); + return; + } } } + + if (tryPushBitwiseOpThroughReductions(vtxp)) return; } void visit(DfgAdd* vtxp) override { @@ -571,6 +708,7 @@ class V3DfgPeephole final : public DfgVisitor { UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); commutativeBinary(vtxp); + associativeBinary(vtxp); } void visit(DfgSub* vtxp) override { @@ -808,14 +946,32 @@ class V3DfgPeephole final : public DfgVisitor { void visit(DfgRedXor* vtxp) override { optimizeReduction(vtxp); } void visit(DfgConcat* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width() + vtxp->rhsp()->width(), vtxp, + "Incorrect Concat width: " << vtxp->width() << " != " << vtxp->lhsp()->width() + << " + " << vtxp->rhsp()->width()); + DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); - UASSERT_OBJ(vtxp->width() == lhsp->width() + rhsp->width(), vtxp, - "Incorrect Concat width: " << vtxp->width() << " != " << lhsp->width() << " + " - << rhsp->width()); FileLine* const flp = vtxp->fileline(); + // Make concat trees right leaning (for better CSE opportunities) + if (DfgConcat* const lConcatp = lhsp->cast()) { + if (!lConcatp->hasMultipleSinks()) { + APPLYING(RIGHT_LEANING_CONCAT) { + const uint32_t topWidth = lConcatp->rhsp()->width() + rhsp->width(); + DfgConcat* const topp = new DfgConcat{m_dfg, flp, dtypeForWidth(topWidth)}; + DfgConcat* const botp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + topp->rhsp(rhsp); + topp->lhsp(lConcatp->rhsp()); + botp->rhsp(topp); + botp->lhsp(lConcatp->lhsp()); + vtxp->replaceWith(botp); + return; + } + } + } + { const auto joinConsts = [this](DfgConst* lConstp, DfgConst* rConstp, FileLine* flp) -> DfgConst* { @@ -1057,6 +1213,49 @@ class V3DfgPeephole final : public DfgVisitor { } } + if (vtxp->width() > 1) { + // 'cond ? a + 1 : a' -> 'a + cond' + if (DfgAdd* const thenAddp = thenp->cast()) { + if (DfgConst* const constp = thenAddp->lhsp()->cast()) { + if (constp->toI32() == 1) { + if (thenAddp->rhsp() == elsep) { + APPLYING(REPLACE_COND_INC) { + DfgConcat* const extp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + extp->rhsp(condp); + extp->lhsp(makeZero(flp, vtxp->width() - 1)); + FileLine* const thenFlp = thenAddp->fileline(); + DfgAdd* const addp = new DfgAdd{m_dfg, thenFlp, vtxp->dtypep()}; + addp->lhsp(thenAddp->rhsp()); + addp->rhsp(extp); + vtxp->replaceWith(addp); + return; + } + } + } + } + } + // 'cond ? a - 1 : a' -> 'a - cond' + if (DfgSub* const thenSubp = thenp->cast()) { + if (DfgConst* const constp = thenSubp->rhsp()->cast()) { + if (constp->toI32() == 1) { + if (thenSubp->lhsp() == elsep) { + APPLYING(REPLACE_COND_DEC) { + DfgConcat* const extp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; + extp->rhsp(condp); + extp->lhsp(makeZero(flp, vtxp->width() - 1)); + FileLine* const thenFlp = thenSubp->fileline(); + DfgSub* const subp = new DfgSub{m_dfg, thenFlp, vtxp->dtypep()}; + subp->lhsp(thenSubp->lhsp()); + subp->rhsp(extp); + vtxp->replaceWith(subp); + return; + } + } + } + } + } + } + if (vtxp->width() == 1) { AstNodeDType* const dtypep = vtxp->dtypep(); if (thenp->isZero()) { // a ? 0 : b becomes ~a & b @@ -1102,6 +1301,20 @@ class V3DfgPeephole final : public DfgVisitor { } } + void visit(DfgArraySel* vtxp) override { + if (DfgConst* const idxp = vtxp->bitp()->cast()) { + if (DfgVarArray* const varp = vtxp->fromp()->cast()) { + const uint32_t idx = idxp->toU32(); + if (DfgVertex* const driverp = varp->driverAt(idx)) { + APPLYING(INLINE_ARRAYSEL) { + vtxp->replaceWith(driverp); + return; + } + } + } + } + } + void visit(DfgVarPacked* vtxp) override { // Inline variables fully driven by the logic represented by the DFG if (vtxp->hasSinks() && vtxp->isDrivenFullyByDfg()) { diff --git a/src/V3DfgPeephole.h b/src/V3DfgPeephole.h index f89a6d1ee..10fe177f6 100644 --- a/src/V3DfgPeephole.h +++ b/src/V3DfgPeephole.h @@ -23,68 +23,80 @@ #define _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, arg) macro(arg, #arg) +// Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests). // clang-format off #define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_CONST_IN_COMMUTATIVE_BINARY) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_NOT_IN_COMMUTATIVE_BINARY) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_VAR_IN_COMMUTATIVE_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_VAR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_REDUCTION) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_COMPARE_OP_THROUGH_CONCAT) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_WIDTH_ONE_REDUCTION) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_REDUCTION_OF_CONST) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EXTEND) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_NOT_THROUGH_COND) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_NOT_NOT) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_NEQ) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_OF_CONST) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_NOT_AND_NOT) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_CONST_AND_CONST) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_WITH_ZERO) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_AND_WITH_ONES) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONTRADICTORY_AND) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NOT) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NEQ) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONST_AND_CONST) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_OR_WITH_ZERO) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_WITH_ONES) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_TAUTOLOGICAL_OR) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SUB_ZERO) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SUB_WITH_NOT) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EQ_OF_CONST_AND_CONST) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_FULL_WIDTH_SEL) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_RHS_OF_CONCAT) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_LHS_OF_CONCAT) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_CONCAT) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_REPLICATE) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_NOT) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_SEL) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_COND) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SHIFTL) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_CONST) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_OF_CONSTS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_CONCAT_THROUGH_NOTS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_NOT_THROUGH_COND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_REDUCTION_THROUGH_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_COND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_REPLICATE) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_SHIFTL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_AND_WITH_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_CONCAT_OF_ADJOINING_SELS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_COND_WITH_FALSE_CONDITION) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_COND_WITH_TRUE_CONDITION) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NOT_CONDITION) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NEQ_CONDITION) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ZERO) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ONES) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_FULL_WIDTH_SEL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_NOT_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_OR_WITH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_LHS_OF_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_RHS_OF_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SUB_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_WIDTH_ONE_REDUCTION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_XOR_WITH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_CONST_AND_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_NOT_AND_NEQ) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_NOT_AND_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_WITH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_OF_CONSTS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INC) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ONES) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_VAR) - + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_ELSE_BRANCH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ONES) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONTRADICTORY_AND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EQ_OF_CONST_AND_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EXTEND) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_EQ) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_NEQ) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_OF_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONST_AND_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NEQ) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_WITH_ONES) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_REDUCTION_OF_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_SEL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SUB_WITH_NOT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_TAUTOLOGICAL_OR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_XOR_OF_CONST_AND_CONST) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_XOR_WITH_ONES) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, RIGHT_LEANING_ASSOC) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, RIGHT_LEANING_CONCAT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NEQ_CONDITION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NOT_CONDITION) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_CONST_IN_COMMUTATIVE_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_NOT_IN_COMMUTATIVE_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_VAR_IN_COMMUTATIVE_BINARY) // clang-format on class VDfgPeepholePattern final { diff --git a/test_regress/t/t_dfg_peephole.cpp b/test_regress/t/t_dfg_peephole.cpp index cd2c25318..7a9d9cac4 100644 --- a/test_regress/t/t_dfg_peephole.cpp +++ b/test_regress/t/t_dfg_peephole.cpp @@ -13,6 +13,12 @@ #include #include +void rngUpdate(uint64_t& x) { + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; +} + int main(int, char**) { // Create contexts VerilatedContext ctx; @@ -21,17 +27,28 @@ int main(int, char**) { Vref ref{&ctx}; Vopt opt{&ctx}; - ref.clk = 0; - opt.clk = 0; + uint64_t rand_a = 0x5aef0c8dd70a4497; + uint64_t rand_b = 0xf0c0a8dd75ae4497; - while (!ctx.gotFinish()) { + for (size_t n = 0; n < 200000; ++n) { + // Update rngs + rngUpdate(rand_a); + rngUpdate(rand_b); + + // Assign inputs + ref.rand_a = opt.rand_a = rand_a; + ref.rand_b = opt.rand_b = rand_b; + + // Evaluate both models ref.eval(); opt.eval(); + + // Check equivalence #include "checks.h" + // increment time ctx.timeInc(1); - - ref.clk = !ref.clk; - opt.clk = !opt.clk; } + + std::cout << "*-* All Finished *-*\n"; } diff --git a/test_regress/t/t_dfg_peephole.pl b/test_regress/t/t_dfg_peephole.pl index 2cd2887f2..c09227ce8 100755 --- a/test_regress/t/t_dfg_peephole.pl +++ b/test_regress/t/t_dfg_peephole.pl @@ -12,35 +12,60 @@ scenarios(vlt_all => 1); $Self->{sim_time} = 2000000; +# Read optimizations +my @optimizations = (); +{ + my $hdrFile = "../src/V3DfgPeephole.h"; + my $hdrFh = IO::File->new("<$hdrFile") or error("$! $hdrFile"); + my $prevOpt = ""; + my $lineno = 0; + while (defined(my $line = $hdrFh->getline)) { + $lineno = $lineno + 1; + next if $line !~ /^\s*_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY\(macro, (\w+)\)/; + my $opt = $1; + error("$hdrFile:$linenno: '$opt; is not in sorted order") if $prevOpt gt $opt; + $prevOpt = $opt; + push @optimizations, $opt; + } + error("no optimizations defined in $hdrFile") if scalar @optimizations == 0; +} + +# Generate the equivalence checks and declaration boilerplate +my $rdFile = "$Self->{top_filename}"; +my $plistFile = "$Self->{obj_dir}/portlist.vh"; +my $pdeclFile = "$Self->{obj_dir}/portdecl.vh"; +my $checkFile = "$Self->{obj_dir}/checks.h"; +my $rdFh = IO::File->new("<$rdFile") or error("$! $rdFile"); +my $plistFh = IO::File->new(">$plistFile") or error("$! $plistFile"); +my $pdeclFh = IO::File->new(">$pdeclFile") or error("$! $pdeclFile"); +my $checkFh = IO::File->new(">$checkFile") or error("$! $checkFile"); +while (defined(my $line = $rdFh->getline)) { + next if $line !~ /^\s*.*`signal\((\w+),/; + my $signal = $1; + print $plistFh "$signal,\n"; + print $pdeclFh "output $signal;\n"; + print $checkFh "if (ref.$signal != opt.$signal) {\n"; + print $checkFh " std::cout << \"Mismatched $signal\" << std::endl;\n"; + print $checkFh " std::cout << \"Ref: 0x\" << std::hex << (ref.$signal + 0) << std::endl;\n"; + print $checkFh " std::cout << \"Opt: 0x\" << std::hex << (opt.$signal + 0) << std::endl;\n"; + print $checkFh " std::exit(1);\n"; + print $checkFh "}\n"; +} +close $rdFile; +close $wrFile; + + # Compile un-optimized compile( - verilator_flags2 => ["--stats", "--build", "-fno-dfg", "+define+REF", + verilator_flags2 => ["--stats", "--build", "-fno-dfg", "+incdir+$Self->{obj_dir}", "-Mdir", "$Self->{obj_dir}/obj_ref", "--prefix", "Vref"], verilator_make_gmake => 0, verilator_make_cmake => 0 ); -# Generate the equivalence checks -my $rdFile = "$Self->{obj_dir}/obj_ref/Vref.h"; -my $wrFile = "$Self->{obj_dir}/checks.h"; -my $rfh = IO::File->new("<$rdFile") or error("$! $rdFile"); -my $wfh = IO::File->new(">$wrFile") or error("$! $wrFile"); -while (defined(my $line = $rfh->getline)) { - next if $line !~ /.*\b(dfg_[A-Z_]*)\b/; - my $signal = $1; - print $wfh "if (ref.$signal != opt.$signal) {\n"; - print $wfh " std::cout << \"Mismatched $signal\" << std::endl;\n"; - print $wfh " std::cout << \"Ref: 0x\" << std::hex << (ref.$signal + 0) << std::endl;\n"; - print $wfh " std::cout << \"Opt: 0x\" << std::hex << (opt.$signal + 0) << std::endl;\n"; - print $wfh " std::exit(1);\n"; - print $wfh "}\n"; -} -close $rdFile; -close $wrFile; - # Compile optimized - also builds executable compile( - verilator_flags2 => ["--stats", "--build", "--exe", + verilator_flags2 => ["--stats", "--build", "--exe", "+incdir+$Self->{obj_dir}", "-Mdir", "$Self->{obj_dir}/obj_opt", "--prefix", "Vopt", "--dump-dfg", # To fill code coverage "-CFLAGS \"-I .. -I ../obj_ref\"", @@ -63,65 +88,10 @@ sub check { file_grep("$Self->{obj_dir}/obj_opt/Vopt__stats.txt", qr/DFG\s+(pre|post) inline Peephole, ${name}\s+([1-9]\d*)/i); } -# Check optimizations -check("SWAP_CONST_IN_COMMUTATIVE_BINARY"); -check("SWAP_NOT_IN_COMMUTATIVE_BINARY"); -check("SWAP_VAR_IN_COMMUTATIVE_BINARY"); -check("PUSH_BITWISE_OP_THROUGH_CONCAT"); -check("PUSH_COMPARE_OP_THROUGH_CONCAT"); -#check("REMOVE_WIDTH_ONE_REDUCTION"); V3Const eats this -check("PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH"); -check("REPLACE_REDUCTION_OF_CONST"); -check("REPLACE_EXTEND"); -check("PUSH_NOT_THROUGH_COND"); -check("REMOVE_NOT_NOT"); -check("REPLACE_NOT_NEQ"); -check("REPLACE_NOT_OF_CONST"); -check("REPLACE_AND_OF_NOT_AND_NOT"); -check("REPLACE_AND_OF_CONST_AND_CONST"); -check("REPLACE_AND_WITH_ZERO"); -check("REMOVE_AND_WITH_ONES"); -check("REPLACE_CONTRADICTORY_AND"); -check("REPLACE_OR_OF_NOT_AND_NOT"); -check("REPLACE_OR_OF_NOT_AND_NEQ"); -check("REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO"); -check("REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS"); -check("REPLACE_OR_OF_CONST_AND_CONST"); -check("REMOVE_OR_WITH_ZERO"); -check("REPLACE_OR_WITH_ONES"); -check("REPLACE_TAUTOLOGICAL_OR"); -check("REMOVE_SUB_ZERO"); -check("REPLACE_SUB_WITH_NOT"); -check("REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT"); -check("REPLACE_EQ_OF_CONST_AND_CONST"); -check("REMOVE_FULL_WIDTH_SEL"); -check("REMOVE_SEL_FROM_RHS_OF_CONCAT"); -check("REMOVE_SEL_FROM_LHS_OF_CONCAT"); -check("PUSH_SEL_THROUGH_CONCAT"); -check("PUSH_SEL_THROUGH_REPLICATE"); -check("PUSH_SEL_THROUGH_NOT"); -check("REPLACE_SEL_FROM_SEL"); -check("PUSH_SEL_THROUGH_COND"); -check("PUSH_SEL_THROUGH_SHIFTL"); -check("REPLACE_SEL_FROM_CONST"); -check("REPLACE_CONCAT_OF_CONSTS"); -check("REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS"); -check("REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS"); -check("REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR"); -check("REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL"); -check("PUSH_CONCAT_THROUGH_NOTS"); -check("REMOVE_CONCAT_OF_ADJOINING_SELS"); -check("REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS"); -check("REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS"); -check("REMOVE_COND_WITH_FALSE_CONDITION"); -check("REMOVE_COND_WITH_TRUE_CONDITION"); -check("SWAP_COND_WITH_NOT_CONDITION"); -check("SWAP_COND_WITH_NEQ_CONDITION"); -check("PULL_NOTS_THROUGH_COND"); -check("REPLACE_COND_WITH_THEN_BRANCH_ZERO"); -check("REPLACE_COND_WITH_THEN_BRANCH_ONES"); -check("REPLACE_COND_WITH_ELSE_BRANCH_ZERO"); -check("REPLACE_COND_WITH_ELSE_BRANCH_ONES"); +# Check all optimizations defined in +foreach my $opt (@optimizations) { + check($opt); +} ok(1); 1; diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index e5609c9c4..f7defa9b4 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -4,216 +4,116 @@ // any use, without warranty, 2022 by Geza Lore. // SPDX-License-Identifier: CC0-1.0 -`define STRINGIFY(x) `"x`" +`define signal(name, expr) wire [$bits(expr)-1:0] ``name = expr; -`define signal(name, expr) wire [$bits(expr)-1:0] dfg_``name = expr; +module t ( +`include "portlist.vh" // Boilerplate generated by t_dfg_peephole.pl + rand_a, rand_b + ); +`include "portdecl.vh" // Boilerplate generated by t_dfg_peephole.pl -module t (/*AUTOARG*/ - // Outputs - dfg_SWAP_CONST_IN_COMMUTATIVE_BINARY, - dfg_SWAP_NOT_IN_COMMUTATIVE_BINARY, - dfg_SWAP_VAR_IN_COMMUTATIVE_BINARY, - dfg_PUSH_BITWISE_OP_THROUGH_CONCAT, - dfg_PUSH_BITWISE_OP_THROUGH_CONCAT_2, - dfg_PUSH_COMPARE_OP_THROUGH_CONCAT, dfg_REMOVE_WIDTH_ONE_REDUCTION, - dfg_PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH, - dfg_REPLACE_REDUCTION_OF_CONST_AND, - dfg_REPLACE_REDUCTION_OF_CONST_OR, - dfg_REPLACE_REDUCTION_OF_CONST_XOR, dfg_REPLACE_EXTEND, - dfg_PUSH_NOT_THROUGH_COND, dfg_REMOVE_NOT_NOT, dfg_REPLACE_NOT_NEQ, - dfg_REPLACE_NOT_OF_CONST, dfg_REPLACE_AND_OF_NOT_AND_NOT, - dfg_REPLACE_AND_OF_CONST_AND_CONST, dfg_REPLACE_AND_WITH_ZERO, - dfg_REMOVE_AND_WITH_ONES, dfg_REPLACE_CONTRADICTORY_AND, - dfg_REPLACE_OR_OF_NOT_AND_NOT, dfg_REPLACE_OR_OF_NOT_AND_NEQ, - dfg_REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO, - dfg_REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS, - dfg_REPLACE_OR_OF_CONST_AND_CONST, dfg_REMOVE_OR_WITH_ZERO, - dfg_REPLACE_OR_WITH_ONES, dfg_REPLACE_TAUTOLOGICAL_OR, - dfg_REMOVE_SUB_ZERO, dfg_REPLACE_SUB_WITH_NOT, - dfg_REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT, - dfg_REPLACE_EQ_OF_CONST_AND_CONST, dfg_REMOVE_FULL_WIDTH_SEL, - dfg_REMOVE_SEL_FROM_RHS_OF_CONCAT, - dfg_REMOVE_SEL_FROM_LHS_OF_CONCAT, dfg_PUSH_SEL_THROUGH_CONCAT, - dfg_PUSH_SEL_THROUGH_REPLICATE, dfg_REPLACE_SEL_FROM_CONST, - dfg_REPLACE_CONCAT_OF_CONSTS, - dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS, - dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS, - dfg_REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR, - dfg_REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL, - dfg_PUSH_CONCAT_THROUGH_NOTS, dfg_REMOVE_CONCAT_OF_ADJOINING_SELS, - dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS, - dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS, - dfg_REMOVE_COND_WITH_FALSE_CONDITION, - dfg_REMOVE_COND_WITH_TRUE_CONDITION, - dfg_SWAP_COND_WITH_NOT_CONDITION, dfg_SWAP_COND_WITH_NEQ_CONDITION, - dfg_PULL_NOTS_THROUGH_COND, dfg_REPLACE_COND_WITH_THEN_BRANCH_ZERO, - dfg_REPLACE_COND_WITH_THEN_BRANCH_ONES, - dfg_REPLACE_COND_WITH_ELSE_BRANCH_ZERO, - dfg_REPLACE_COND_WITH_ELSE_BRANCH_ONES, dfg_PUSH_SEL_THROUGH_COND, - dfg_PUSH_SEL_THROUGH_SHIFTL, dfg_REPLACE_SEL_FROM_SEL, - // Inputs - clk - ); - input clk; + input rand_a; + input rand_b; + wire [63:0] rand_a; + wire [63:0] rand_b; - // Sadly verilog-mode cannot look in macros so need to define these - // separately - output dfg_SWAP_CONST_IN_COMMUTATIVE_BINARY; - output dfg_SWAP_NOT_IN_COMMUTATIVE_BINARY; - output dfg_SWAP_VAR_IN_COMMUTATIVE_BINARY; - output dfg_PUSH_BITWISE_OP_THROUGH_CONCAT; - output dfg_PUSH_BITWISE_OP_THROUGH_CONCAT_2; - output dfg_PUSH_COMPARE_OP_THROUGH_CONCAT; - output dfg_REMOVE_WIDTH_ONE_REDUCTION; - output dfg_PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH; - output dfg_REPLACE_REDUCTION_OF_CONST_AND; - output dfg_REPLACE_REDUCTION_OF_CONST_OR; - output dfg_REPLACE_REDUCTION_OF_CONST_XOR; - output dfg_REPLACE_EXTEND; - output dfg_PUSH_NOT_THROUGH_COND; - output dfg_REMOVE_NOT_NOT; - output dfg_REPLACE_NOT_NEQ; - output dfg_REPLACE_NOT_OF_CONST; - output dfg_REPLACE_AND_OF_NOT_AND_NOT; - output dfg_REPLACE_AND_OF_CONST_AND_CONST; - output dfg_REPLACE_AND_WITH_ZERO; - output dfg_REMOVE_AND_WITH_ONES; - output dfg_REPLACE_CONTRADICTORY_AND; - output dfg_REPLACE_OR_OF_NOT_AND_NOT; - output dfg_REPLACE_OR_OF_NOT_AND_NEQ; - output dfg_REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO; - output dfg_REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS; - output dfg_REPLACE_OR_OF_CONST_AND_CONST; - output dfg_REMOVE_OR_WITH_ZERO; - output dfg_REPLACE_OR_WITH_ONES; - output dfg_REPLACE_TAUTOLOGICAL_OR; - output dfg_REMOVE_SUB_ZERO; - output dfg_REPLACE_SUB_WITH_NOT; - output dfg_REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT; - output dfg_REPLACE_EQ_OF_CONST_AND_CONST; - output dfg_REMOVE_FULL_WIDTH_SEL; - output dfg_REMOVE_SEL_FROM_RHS_OF_CONCAT; - output dfg_REMOVE_SEL_FROM_LHS_OF_CONCAT; - output dfg_PUSH_SEL_THROUGH_CONCAT; - output dfg_PUSH_SEL_THROUGH_REPLICATE; - output dfg_REPLACE_SEL_FROM_CONST; - output dfg_REPLACE_CONCAT_OF_CONSTS; - output dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS; - output dfg_REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS; - output dfg_REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR; - output dfg_REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL; - output dfg_PUSH_CONCAT_THROUGH_NOTS; - output dfg_REMOVE_CONCAT_OF_ADJOINING_SELS; - output dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS; - output dfg_REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS; - output dfg_REMOVE_COND_WITH_FALSE_CONDITION; - output dfg_REMOVE_COND_WITH_TRUE_CONDITION; - output dfg_SWAP_COND_WITH_NOT_CONDITION; - output dfg_SWAP_COND_WITH_NEQ_CONDITION; - output dfg_PULL_NOTS_THROUGH_COND; - output dfg_REPLACE_COND_WITH_THEN_BRANCH_ZERO; - output dfg_REPLACE_COND_WITH_THEN_BRANCH_ONES; - output dfg_REPLACE_COND_WITH_ELSE_BRANCH_ZERO; - output dfg_REPLACE_COND_WITH_ELSE_BRANCH_ONES; - output dfg_PUSH_SEL_THROUGH_COND; - output dfg_PUSH_SEL_THROUGH_SHIFTL; - output dfg_REPLACE_SEL_FROM_SEL; + wire logic randbit_a = rand_a[0]; + wire logic [127:0] rand_ba = {rand_b, rand_a}; + wire logic [127:0] rand_aa = {2{rand_a}}; + wire logic [63:0] const_a; + wire logic [63:0] const_b; + wire logic [63:0] array [3:0]; + assign array[0] = (rand_a << 32) | (rand_a >> 32); + assign array[1] = (rand_a << 16) | (rand_a >> 48); - integer cyc = 0; - - reg [63:0] crc = 64'h5aef0c8d_d70a4497; - reg [63:0] rcr; - wire logic [127:0] rcr_crc = {rcr, crc}; - wire logic [127:0] crc_rep = {2{crc}}; - wire logic [63:0] const_a; - wire logic [63:0] const_b; - - always @ (posedge clk) begin - cyc <= cyc + 1; - crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]}; - rcr <= ~crc; - -`ifdef REF - if (cyc >= 100_000) begin - $write("*-* All Finished *-*\n"); - $finish; - end -`endif - end - - // 64'0 but don't tell V3Const + // 64 bit all 0 but don't tell V3Const `define ZERO (const_a & ~const_a) - // 64'1 but don't tell V3Const + // 64 bit all 1 but don't tell V3Const `define ONES (const_a | ~const_a) // x, but in a way only DFG understands `define DFG(x) ((|`ONES) ? (x) : (~x)) - `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, crc + const_a); - `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, crc + ~crc); - `signal(SWAP_VAR_IN_COMMUTATIVE_BINARY, rcr + crc); - `signal(PUSH_BITWISE_OP_THROUGH_CONCAT, 32'h12345678 ^ {8'h0, crc[23:0]}); - `signal(PUSH_BITWISE_OP_THROUGH_CONCAT_2, 32'h12345678 ^ {rcr[7:0], crc[23:0]}); - `signal(PUSH_COMPARE_OP_THROUGH_CONCAT, 4'b1011 == {2'b10, crc[1:0]}); - `signal(REMOVE_WIDTH_ONE_REDUCTION, &`DFG(crc[0])); - `signal(PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH, |(crc[32] ? crc[3:0] : 4'h0)); + `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, rand_a + const_a); + `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, rand_a + ~rand_a); + `signal(SWAP_VAR_IN_COMMUTATIVE_BINARY, rand_b + rand_a); + `signal(PUSH_BITWISE_OP_THROUGH_CONCAT, 32'h12345678 ^ {8'h0, rand_a[23:0]}); + `signal(PUSH_BITWISE_OP_THROUGH_CONCAT_2, 32'h12345678 ^ {rand_b[7:0], rand_a[23:0]}); + `signal(PUSH_COMPARE_OP_THROUGH_CONCAT, 4'b1011 == {2'b10, rand_a[1:0]}); + `signal(REMOVE_WIDTH_ONE_REDUCTION, &`DFG(rand_a[0])); + `signal(PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH, |(rand_a[32] ? rand_a[3:0] : 4'h0)); `signal(REPLACE_REDUCTION_OF_CONST_AND, &const_a); `signal(REPLACE_REDUCTION_OF_CONST_OR, |const_a); `signal(REPLACE_REDUCTION_OF_CONST_XOR, ^const_a); - `signal(REPLACE_EXTEND, 4'(crc[0])); - `signal(PUSH_NOT_THROUGH_COND, ~(crc[0] ? crc[4:0] : 5'hb)); - `signal(REMOVE_NOT_NOT, ~`DFG(~`DFG(crc))); - `signal(REPLACE_NOT_NEQ, ~`DFG(crc != rcr)); + `signal(REPLACE_EXTEND, 4'(rand_a[0])); + `signal(PUSH_NOT_THROUGH_COND, ~(rand_a[0] ? rand_a[4:0] : 5'hb)); + `signal(REMOVE_NOT_NOT, ~`DFG(~`DFG(rand_a))); + `signal(REPLACE_NOT_NEQ, ~`DFG(rand_a != rand_b)); + `signal(REPLACE_NOT_EQ, ~`DFG(rand_a == rand_b)); `signal(REPLACE_NOT_OF_CONST, ~4'd0); - `signal(REPLACE_AND_OF_NOT_AND_NOT, ~crc[0] & ~rcr[0]); + `signal(REPLACE_AND_OF_NOT_AND_NOT, ~rand_a[0] & ~rand_b[0]); + `signal(REPLACE_AND_OF_NOT_AND_NEQ, ~rand_a[0] & (rand_b != 64'd2)); `signal(REPLACE_AND_OF_CONST_AND_CONST, const_a & const_b); - `signal(REPLACE_AND_WITH_ZERO, `ZERO & crc); - `signal(REMOVE_AND_WITH_ONES, `ONES & crc); - `signal(REPLACE_CONTRADICTORY_AND, crc & ~crc); - `signal(REPLACE_OR_OF_NOT_AND_NOT, ~crc[0] | ~rcr[0]); - `signal(REPLACE_OR_OF_NOT_AND_NEQ, ~crc[0] | (rcr != 64'd2)); - `signal(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO, {2'd0, crc[1:0]} | {rcr[1:0], 2'd0}); - `signal(REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS, {crc[1:0], 2'd0} | {2'd0, rcr[1:0]}); + `signal(REPLACE_AND_WITH_ZERO, `ZERO & rand_a); + `signal(REMOVE_AND_WITH_ONES, `ONES & rand_a); + `signal(REPLACE_CONTRADICTORY_AND, rand_a & ~rand_a); + `signal(REPLACE_OR_OF_NOT_AND_NOT, ~rand_a[0] | ~rand_b[0]); + `signal(REPLACE_OR_OF_NOT_AND_NEQ, ~rand_a[0] | (rand_b != 64'd2)); + `signal(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO, {2'd0, rand_a[1:0]} | {rand_b[1:0], 2'd0}); + `signal(REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS, {rand_a[1:0], 2'd0} | {2'd0, rand_b[1:0]}); `signal(REPLACE_OR_OF_CONST_AND_CONST, const_a | const_b); - `signal(REMOVE_OR_WITH_ZERO, `ZERO | crc); - `signal(REPLACE_OR_WITH_ONES, `ONES | crc); - `signal(REPLACE_TAUTOLOGICAL_OR, crc | ~crc); - `signal(REMOVE_SUB_ZERO, crc - `ZERO); - `signal(REPLACE_SUB_WITH_NOT, crc[0] - 1'b1); - `signal(REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT, crc << {2'b0, crc[2:0]}); + `signal(REMOVE_OR_WITH_ZERO, `ZERO | rand_a); + `signal(REPLACE_OR_WITH_ONES, `ONES | rand_a); + `signal(REPLACE_TAUTOLOGICAL_OR, rand_a | ~rand_a); + `signal(REMOVE_SUB_ZERO, rand_a - `ZERO); + `signal(REPLACE_SUB_WITH_NOT, rand_a[0] - 1'b1); + `signal(REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT, rand_a << {2'b0, rand_a[2:0]}); `signal(REPLACE_EQ_OF_CONST_AND_CONST, 4'd0 == 4'd1); - `signal(REMOVE_FULL_WIDTH_SEL, crc[63:0]); - `signal(REMOVE_SEL_FROM_RHS_OF_CONCAT, rcr_crc[63:0]); - `signal(REMOVE_SEL_FROM_LHS_OF_CONCAT, rcr_crc[127:64]); - `signal(PUSH_SEL_THROUGH_CONCAT, rcr_crc[120:0]); - `signal(PUSH_SEL_THROUGH_REPLICATE, crc_rep[0]); + `signal(REMOVE_FULL_WIDTH_SEL, rand_a[63:0]); + `signal(REMOVE_SEL_FROM_RHS_OF_CONCAT, rand_ba[63:0]); + `signal(REMOVE_SEL_FROM_LHS_OF_CONCAT, rand_ba[127:64]); + `signal(PUSH_SEL_THROUGH_CONCAT, rand_ba[120:0]); + `signal(PUSH_SEL_THROUGH_REPLICATE, rand_aa[0]); `signal(REPLACE_SEL_FROM_CONST, const_a[2]); `signal(REPLACE_CONCAT_OF_CONSTS, {const_a, const_b}); - `signal(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS, {`DFG({crc, const_a}), const_b}); - `signal(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS, {const_a, `DFG({const_b, crc})}); - `signal(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR, {62'd0, crc[63:62]}); - `signal(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL, {crc[1:0], 62'd0}); - `signal(PUSH_CONCAT_THROUGH_NOTS, {~crc, ~rcr} ); - `signal(REMOVE_CONCAT_OF_ADJOINING_SELS, {`DFG(crc[10:3]), `DFG(crc[2:1])}); - `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS, {crc[10:3], {crc[2:1], rcr}}); - `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS, {`DFG({rcr, crc[10:3]}), crc[2:1]}); - `signal(REMOVE_COND_WITH_FALSE_CONDITION, &`ZERO ? crc : rcr); - `signal(REMOVE_COND_WITH_TRUE_CONDITION, |`ONES ? crc : rcr); - `signal(SWAP_COND_WITH_NOT_CONDITION, (~crc[0] & |`ONES) ? crc : rcr); - `signal(SWAP_COND_WITH_NEQ_CONDITION, rcr != crc ? crc : rcr); - `signal(PULL_NOTS_THROUGH_COND, crc[0] ? ~crc[4:0] : ~rcr[4:0]); - `signal(REPLACE_COND_WITH_THEN_BRANCH_ZERO, crc[0] ? |`ZERO : crc[1]); - `signal(REPLACE_COND_WITH_THEN_BRANCH_ONES, crc[0] ? |`ONES : crc[1]); - `signal(REPLACE_COND_WITH_ELSE_BRANCH_ZERO, crc[0] ? crc[1] : |`ZERO); - `signal(REPLACE_COND_WITH_ELSE_BRANCH_ONES, crc[0] ? crc[1] : |`ONES); - - assign const_a = (crc | ~crc) & 64'h0123456789abcdef; - assign const_b = ~(crc & ~crc) & 64'h98badefc10325647; + `signal(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS, {`DFG({rand_a, const_a}), const_b}); + `signal(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS, {const_a, `DFG({const_b, rand_a})}); + `signal(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR, {62'd0, rand_a[63:62]}); + `signal(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL, {rand_a[1:0], 62'd0}); + `signal(PUSH_CONCAT_THROUGH_NOTS, {~rand_a, ~rand_b} ); + `signal(REMOVE_CONCAT_OF_ADJOINING_SELS, {`DFG(rand_a[10:3]), `DFG(rand_a[2:1])}); + `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS, {rand_a[10:3], {rand_a[2:1], rand_b}}); + `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS, {`DFG({rand_b, rand_a[10:3]}), rand_a[2:1]}); + `signal(REMOVE_COND_WITH_FALSE_CONDITION, &`ZERO ? rand_a : rand_b); + `signal(REMOVE_COND_WITH_TRUE_CONDITION, |`ONES ? rand_a : rand_b); + `signal(SWAP_COND_WITH_NOT_CONDITION, (~rand_a[0] & |`ONES) ? rand_a : rand_b); + `signal(SWAP_COND_WITH_NEQ_CONDITION, rand_b != rand_a ? rand_a : rand_b); + `signal(PULL_NOTS_THROUGH_COND, rand_a[0] ? ~rand_a[4:0] : ~rand_b[4:0]); + `signal(REPLACE_COND_WITH_THEN_BRANCH_ZERO, rand_a[0] ? |`ZERO : rand_a[1]); + `signal(REPLACE_COND_WITH_THEN_BRANCH_ONES, rand_a[0] ? |`ONES : rand_a[1]); + `signal(REPLACE_COND_WITH_ELSE_BRANCH_ZERO, rand_a[0] ? rand_a[1] : |`ZERO); + `signal(REPLACE_COND_WITH_ELSE_BRANCH_ONES, rand_a[0] ? rand_a[1] : |`ONES); + `signal(INLINE_ARRAYSEL, array[0]); + `signal(PUSH_BITWISE_THROUGH_REDUCTION_AND, (&rand_a) & (&rand_b)); + `signal(PUSH_BITWISE_THROUGH_REDUCTION_OR, (|rand_a) | (|rand_b)); + `signal(PUSH_BITWISE_THROUGH_REDUCTION_XOR, (^rand_a) ^ (^rand_b)); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_AND, &`DFG({rand_a, rand_b})); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_OR, |`DFG({rand_a, rand_b})); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_XOR, ^`DFG({rand_a, rand_b})); + `signal(REMOVE_WIDTH_ONE_REDUCTION_AND, &`DFG({randbit_a, rand_b})); + `signal(REMOVE_WIDTH_ONE_REDUCTION_OR, |`DFG({randbit_a, rand_b})); + `signal(REMOVE_WIDTH_ONE_REDUCTION_XOR, ^`DFG({randbit_a, rand_b})); + `signal(REMOVE_XOR_WITH_ZERO, `ZERO ^ rand_a); + `signal(REMOVE_XOR_WITH_ONES, `ONES ^ rand_a); + `signal(REPLACE_COND_DEC, randbit_a ? rand_b - 64'b1 : rand_b); + `signal(REPLACE_COND_INC, randbit_a ? rand_b + 64'b1 : rand_b); + `signal(RIGHT_LEANING_ASSOC, (((rand_a + rand_b) + rand_a) + rand_b)); + `signal(RIGHT_LEANING_CONCET, {{{rand_a, rand_b}, rand_a}, rand_b}); // Some selects need extra temporaries - wire [63:0] sel_from_cond = crc[0] ? crc : const_a; - wire [63:0] sel_from_shiftl = crc << 10; - wire [31:0] sel_from_sel = crc[10+:32]; + wire [63:0] sel_from_cond = rand_a[0] ? rand_a : const_a; + wire [63:0] sel_from_shiftl = rand_a << 10; + wire [31:0] sel_from_sel = rand_a[10+:32]; `signal(PUSH_SEL_THROUGH_COND, sel_from_cond[2]); `signal(PUSH_SEL_THROUGH_SHIFTL, sel_from_shiftl[20:0]); @@ -221,7 +121,11 @@ module t (/*AUTOARG*/ // Sel from not requires the operand to have a sinle sink, so can't use // the chekc due to the raw expression referencing the operand - wire [63:0] sel_from_not_tmp = ~(crc >> rcr[2:0] << crc[3:0]); + wire [63:0] sel_from_not_tmp = ~(rand_a >> rand_b[2:0] << rand_a[3:0]); wire sel_from_not = sel_from_not_tmp[2]; - always @(posedge clk) if ($c(0)) $display(sel_from_not); // Do not remove signal + always @(posedge randbit_a) if ($c(0)) $display(sel_from_not); // Do not remove signal + + // Assigned at the end to avoid inlining by other passes + assign const_a = (rand_a | ~rand_a) & 64'h0123456789abcdef; + assign const_b = ~(rand_a & ~rand_a) & 64'h98badefc10325647; endmodule From 2a12b052f25add7e355a904291c4ecf953132b40 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 1 Oct 2022 12:28:16 +0100 Subject: [PATCH 071/177] DFG: handle simple always blocks --- docs/gen/ex_DIDNOTCONVERGE_msg.rst | 2 +- src/V3Delayed.cpp | 11 +++- src/V3DfgAstToDfg.cpp | 63 ++++++++++++++++++- test_regress/t/t_lint_didnotconverge_bad.out | 2 +- test_regress/t/t_order_blkandnblk_bad.out | 8 +-- test_regress/t/t_unopt_converge_unopt_bad.out | 2 +- 6 files changed, 76 insertions(+), 12 deletions(-) diff --git a/docs/gen/ex_DIDNOTCONVERGE_msg.rst b/docs/gen/ex_DIDNOTCONVERGE_msg.rst index bd055184b..86af7245a 100644 --- a/docs/gen/ex_DIDNOTCONVERGE_msg.rst +++ b/docs/gen/ex_DIDNOTCONVERGE_msg.rst @@ -1,5 +1,5 @@ .. comment: generated by t_lint_didnotconverge_bad .. code-block:: - -V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] b) + -V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] a) %Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge. diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index dbff9c7dc..c7012c72b 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -109,6 +109,11 @@ private: // METHODS + const AstNode* containingAssignment(const AstNode* nodep) { + while (nodep && !VN_IS(nodep, NodeAssign)) nodep = nodep->backp(); + return nodep; + } + void markVarUsage(AstNodeVarRef* nodep, bool blocking) { // Ignore if warning is disabled on this reference (used by V3Force). if (nodep->fileline()->warnIsOff(V3ErrorCode::BLKANDNBLK)) return; @@ -122,8 +127,10 @@ private: } else { const bool last_was_blocking = lastrefp->user5(); if (last_was_blocking != blocking) { - const AstNode* const nonblockingp = blocking ? nodep : lastrefp; - const AstNode* const blockingp = blocking ? lastrefp : nodep; + const AstNode* nonblockingp = blocking ? nodep : lastrefp; + if (const AstNode* np = containingAssignment(nonblockingp)) nonblockingp = np; + const AstNode* blockingp = blocking ? lastrefp : nodep; + if (const AstNode* np = containingAssignment(blockingp)) blockingp = np; vscp->v3warn( BLKANDNBLK, "Unsupported: Blocked and non-blocking assignments to same variable: " diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index db81c7d11..c63814f90 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -212,7 +212,7 @@ class AstToDfgVisitor final : public VNVisitor { return false; } - bool convertEquation(AstNode* nodep, AstNode* lhsp, AstNode* rhsp) { + bool convertEquation(AstNode* nodep, FileLine* flp, AstNode* lhsp, AstNode* rhsp) { UASSERT_OBJ(m_uncommittedVertices.empty(), nodep, "Should not nest"); // Currently cannot handle direct assignments between unpacked types. These arise e.g. @@ -243,7 +243,7 @@ class AstToDfgVisitor final : public VNVisitor { return false; } - if (!convertAssignment(nodep->fileline(), lhsp, getVertex(rhsp))) { + if (!convertAssignment(flp, lhsp, getVertex(rhsp))) { revertUncommittedVertices(); markReferenced(nodep); return false; @@ -374,7 +374,64 @@ class AstToDfgVisitor final : public VNVisitor { return; } - convertEquation(nodep, nodep->lhsp(), nodep->rhsp()); + convertEquation(nodep, nodep->fileline(), nodep->lhsp(), nodep->rhsp()); + } + + void visit(AstAlways* nodep) override { + // Ignore sequential logic, or if there are multiple statements + const VAlwaysKwd kwd = nodep->keyword(); + if (nodep->sensesp() || !nodep->isJustOneBodyStmt() + || (kwd != VAlwaysKwd::ALWAYS && kwd != VAlwaysKwd::ALWAYS_COMB)) { + markReferenced(nodep); + return; + } + + AstNode* const stmtp = nodep->stmtsp(); + + if (AstAssign* const assignp = VN_CAST(stmtp, Assign)) { + ++m_ctx.m_inputEquations; + if (assignp->timingControlp()) { + markReferenced(stmtp); + ++m_ctx.m_nonRepTiming; + return; + } + convertEquation(nodep, assignp->fileline(), assignp->lhsp(), assignp->rhsp()); + } else if (AstIf* const ifp = VN_CAST(stmtp, If)) { + // Will only handle single assignments to the same LHS in both branches + AstAssign* const thenp = VN_CAST(ifp->thensp(), Assign); + AstAssign* const elsep = VN_CAST(ifp->elsesp(), Assign); + if (!thenp || !elsep || thenp->nextp() || elsep->nextp() + || !thenp->lhsp()->sameTree(elsep->lhsp())) { + markReferenced(stmtp); + return; + } + + ++m_ctx.m_inputEquations; + if (thenp->timingControlp() || elsep->timingControlp()) { + markReferenced(stmtp); + ++m_ctx.m_nonRepTiming; + return; + } + + // Create a conditional for the rhs by borrowing the components from the AstIf + AstCond* const rhsp = new AstCond{ifp->fileline(), // + ifp->condp()->unlinkFrBack(), // + thenp->rhsp()->unlinkFrBack(), // + elsep->rhsp()->unlinkFrBack()}; + + if (!convertEquation(nodep, ifp->fileline(), thenp->lhsp(), rhsp)) { + // Failed to convert. Mark 'rhsp', as 'convertEquation' only marks 'nodep'. + markReferenced(rhsp); + // Put the AstIf back together + ifp->condp(rhsp->condp()->unlinkFrBack()); + thenp->rhsp(rhsp->thenp()->unlinkFrBack()); + elsep->rhsp(rhsp->elsep()->unlinkFrBack()); + } + // Delete the auxiliary conditional + VL_DO_DANGLING(rhsp->deleteTree(), rhsp); + } else { + markReferenced(stmtp); + } } void visit(AstVarRef* nodep) override { diff --git a/test_regress/t/t_lint_didnotconverge_bad.out b/test_regress/t/t_lint_didnotconverge_bad.out index 526a34911..f72c08997 100644 --- a/test_regress/t/t_lint_didnotconverge_bad.out +++ b/test_regress/t/t_lint_didnotconverge_bad.out @@ -1,3 +1,3 @@ --V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] b) +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] a) %Error: t/t_lint_didnotconverge_bad.v:7: Settle region did not converge. Aborting... diff --git a/test_regress/t/t_order_blkandnblk_bad.out b/test_regress/t/t_order_blkandnblk_bad.out index 709176366..e2bcffd36 100644 --- a/test_regress/t/t_order_blkandnblk_bad.out +++ b/test_regress/t/t_order_blkandnblk_bad.out @@ -1,11 +1,11 @@ %Error-BLKANDNBLK: t/t_order_blkandnblk_bad.v:17:21: Unsupported: Blocked and non-blocking assignments to same variable: 't.array' 17 | logic [1:0][3:0] array; | ^~~~~ - t/t_order_blkandnblk_bad.v:19:16: ... Location of blocking assignment + t/t_order_blkandnblk_bad.v:19:25: ... Location of blocking assignment 19 | always_comb array[0] = i; - | ^~~~~ - t/t_order_blkandnblk_bad.v:22:6: ... Location of nonblocking assignment + | ^ + t/t_order_blkandnblk_bad.v:22:15: ... Location of nonblocking assignment 22 | array[1] <= array[0]; - | ^~~~~ + | ^~ ... For error description see https://verilator.org/warn/BLKANDNBLK?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_unopt_converge_unopt_bad.out b/test_regress/t/t_unopt_converge_unopt_bad.out index ad410cbb3..7aa0e9039 100644 --- a/test_regress/t/t_unopt_converge_unopt_bad.out +++ b/test_regress/t/t_unopt_converge_unopt_bad.out @@ -4,6 +4,6 @@ ... For warning description see https://verilator.org/warn/UNOPTFLAT?v=latest ... Use "/* verilator lint_off UNOPTFLAT */" and lint_on around source to disable this message. t/t_unopt_converge.v:19:11: Example path: x - t/t_unopt_converge.v:22:4: Example path: ALWAYS + t/t_unopt_converge.v:23:9: Example path: ASSIGNW t/t_unopt_converge.v:19:11: Example path: x %Error: Exiting due to From c9634695a70dc741a3b8c8210ebd244ff11baccc Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 2 Oct 2022 16:25:11 -0400 Subject: [PATCH 072/177] Fix std::exchange for C++11 compilers --- include/verilated_types.h | 6 +++--- include/verilatedos.h | 10 ++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/include/verilated_types.h b/include/verilated_types.h index b39b3e2ae..044848e5e 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -1113,7 +1113,7 @@ public: refCountInc(); } VlClassRef(VlClassRef&& moved) - : m_objp{std::exchange(moved.m_objp, nullptr)} {} + : m_objp{vlstd::exchange(moved.m_objp, nullptr)} {} ~VlClassRef() { refCountDec(); } // METHODS @@ -1126,7 +1126,7 @@ public: } VlClassRef& operator=(VlClassRef&& moved) { refCountDec(); - m_objp = std::exchange(moved.m_objp, nullptr); + m_objp = vlstd::exchange(moved.m_objp, nullptr); return *this; } template @@ -1139,7 +1139,7 @@ public: template VlClassRef& operator=(VlClassRef&& moved) { refCountDec(); - m_objp = std::exchange(moved.m_objp, nullptr); + m_objp = vlstd::exchange(moved.m_objp, nullptr); return *this; } // Dynamic caster diff --git a/include/verilatedos.h b/include/verilatedos.h index c5677b992..2e1456852 100644 --- a/include/verilatedos.h +++ b/include/verilatedos.h @@ -540,6 +540,8 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read() //========================================================================= // Conversions +#include + namespace vlstd { template @@ -564,6 +566,14 @@ T const& as_const(T& v) { return v; } +// C++14's std::exchange +template +T exchange(T& obj, U&& new_value) { + T old_value = std::move(obj); + obj = std::forward(new_value); + return old_value; +} + }; // namespace vlstd //========================================================================= From 4367e03e461b13846dfea99c4a1b08d173b026e2 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 2 Oct 2022 16:35:45 -0400 Subject: [PATCH 073/177] Internals: Make VL_UNREACHABLE similar to std::unreachable() --- include/verilated.cpp | 4 ++-- include/verilatedos.h | 6 +++--- src/V3Ast.cpp | 2 +- src/V3Error.h | 2 +- src/V3FileLine.h | 2 +- src/V3Graph.cpp | 2 +- src/V3Number.cpp | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/verilated.cpp b/include/verilated.cpp index 4370a5b75..38ecdf221 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -2836,7 +2836,7 @@ const char* Verilated::productVersion() VL_PURE { return VERILATOR_VERSION; } void Verilated::nullPointerError(const char* filename, int linenum) VL_MT_SAFE { // Slowpath - Called only on error VL_FATAL_MT(filename, linenum, "", "Null pointer dereferenced"); - VL_UNREACHABLE + VL_UNREACHABLE; } void Verilated::overWidthError(const char* signame) VL_MT_SAFE { @@ -2844,7 +2844,7 @@ void Verilated::overWidthError(const char* signame) VL_MT_SAFE { const std::string msg = (std::string{"Testbench C set input '"} + signame + "' to value that overflows what the signal's width can fit"); VL_FATAL_MT("unknown", 0, "", msg.c_str()); - VL_UNREACHABLE + VL_UNREACHABLE; } void Verilated::mkdir(const char* dirname) VL_MT_UNSAFE { diff --git a/include/verilatedos.h b/include/verilatedos.h index 2e1456852..d4d64ea96 100644 --- a/include/verilatedos.h +++ b/include/verilatedos.h @@ -69,9 +69,9 @@ # define VL_EXCLUDES(x) __attribute__((locks_excluded(x))) # define VL_SCOPED_CAPABILITY __attribute__((scoped_lockable)) # endif -# define VL_LIKELY(x) __builtin_expect(!!(x), 1) -# define VL_UNLIKELY(x) __builtin_expect(!!(x), 0) -# define VL_UNREACHABLE __builtin_unreachable(); +# define VL_LIKELY(x) __builtin_expect(!!(x), 1) // Prefer over C++20 [[likely]] +# define VL_UNLIKELY(x) __builtin_expect(!!(x), 0) // Prefer over C++20 [[unlikely]] +# define VL_UNREACHABLE __builtin_unreachable() // C++23 std::unreachable() # define VL_PREFETCH_RD(p) __builtin_prefetch((p), 0) # define VL_PREFETCH_RW(p) __builtin_prefetch((p), 1) #endif diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 1bd1ab6b9..27b52f353 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -1187,7 +1187,7 @@ void AstNode::dumpTreeDotFile(const string& filename, bool append, bool doDump) void AstNode::v3errorEndFatal(std::ostringstream& str) const { v3errorEnd(str); assert(0); // LCOV_EXCL_LINE - VL_UNREACHABLE + VL_UNREACHABLE; } string AstNode::instanceStr() const { diff --git a/src/V3Error.h b/src/V3Error.h index d5be7e005..d103866d6 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -330,7 +330,7 @@ inline void v3errorEnd(std::ostringstream& sstr) { V3Error::v3errorEnd(sstr); } inline void v3errorEndFatal(std::ostringstream& sstr) { V3Error::v3errorEnd(sstr); assert(0); // LCOV_EXCL_LINE - VL_UNREACHABLE + VL_UNREACHABLE; } // Theses allow errors using << operators: v3error("foo"<<"bar"); diff --git a/src/V3FileLine.h b/src/V3FileLine.h index 0737b0783..f6c53bfb1 100644 --- a/src/V3FileLine.h +++ b/src/V3FileLine.h @@ -307,7 +307,7 @@ public: void v3errorEndFatal(std::ostringstream& str) VL_ATTR_NORETURN { v3errorEnd(str); assert(0); // LCOV_EXCL_LINE - VL_UNREACHABLE + VL_UNREACHABLE; } /// When building an error, prefix for printing continuation lines /// e.g. information referring to the same FileLine as before diff --git a/src/V3Graph.cpp b/src/V3Graph.cpp index faffb7b41..4218394fa 100644 --- a/src/V3Graph.cpp +++ b/src/V3Graph.cpp @@ -144,7 +144,7 @@ void V3GraphVertex::v3errorEnd(std::ostringstream& str) const { void V3GraphVertex::v3errorEndFatal(std::ostringstream& str) const { v3errorEnd(str); assert(0); // LCOV_EXCL_LINE - VL_UNREACHABLE + VL_UNREACHABLE; } std::ostream& operator<<(std::ostream& os, V3GraphVertex* vertexp) { diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 3dcfc88e2..00e7b1524 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -91,7 +91,7 @@ void V3Number::v3errorEnd(std::ostringstream& str) const { void V3Number::v3errorEndFatal(std::ostringstream& str) const { v3errorEnd(str); assert(0); // LCOV_EXCL_LINE - VL_UNREACHABLE + VL_UNREACHABLE; } //====================================================================== From 90009b9ec717efbbad6e355433ac010fc91ca045 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 2 Oct 2022 16:47:32 -0400 Subject: [PATCH 074/177] Commentary: Fix sphinx doc warnings --- docs/guide/connecting.rst | 1 + docs/guide/example_binary.rst | 8 ++++---- docs/guide/exe_sim.rst | 2 +- docs/guide/exe_verilator.rst | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/guide/connecting.rst b/docs/guide/connecting.rst index beb3ad774..876f15487 100644 --- a/docs/guide/connecting.rst +++ b/docs/guide/connecting.rst @@ -451,6 +451,7 @@ the user should call: * :code:`designp->nextTimeSlot()`, which returns the simulation time of the next delayed event. This method can only be called if :code:`designp->nextTimeSlot()` returned :code:`true`. + Call :code:`eventsPending()` to check if you should continue with the simulation, and then :code:`nextTimeSlot()` to move simulation time forward. :vlopt:`--main` can be used with :vlopt:`--timing` to generate a basic example diff --git a/docs/guide/example_binary.rst b/docs/guide/example_binary.rst index ba88fc3ad..974471c7a 100644 --- a/docs/guide/example_binary.rst +++ b/docs/guide/example_binary.rst @@ -1,10 +1,10 @@ .. Copyright 2003-2022 by Wilson Snyder. .. SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -.. _Example C++ Execution: +.. _Example Create-Binary Execution: -Example C++ Execution -===================== +Example Create-Binary Execution +=============================== We'll compile this SystemC example into a Verilated simulation binary. For an example that discusses the next level of detail see :ref:`Example C++ @@ -36,7 +36,7 @@ Breaking this command down: #. :vlopt:`--binary` telling Verilator to do everything needed to create a simulation executable. -#. :vlopt:`-j` `0' to Verilate using use as many CPU threads as the machine +#. :vlopt:`-j` `0` to Verilate using use as many CPU threads as the machine has. #. :vlopt:`-Wall` so Verilator has stronger lint warnings diff --git a/docs/guide/exe_sim.rst b/docs/guide/exe_sim.rst index 364ac5fba..03dd1d76e 100644 --- a/docs/guide/exe_sim.rst +++ b/docs/guide/exe_sim.rst @@ -72,7 +72,7 @@ Summary: .. option:: +verilator+prof+threads+window+ - Deprecated. Alias for :vlopt:`+verilator+prof+exec+window+\` + Deprecated. Alias for :vlopt:`+verilator+prof+exec+window+\` .. option:: +verilator+prof+vlt+file+ diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 2a529ca53..d06d8ae12 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -421,8 +421,8 @@ Summary: Rarely needed - for developer use. Set the dumping level in the specified Verilator source file to the specified value (e.g. - :vlopt:`--dumpi-V3Order 9`). Level 0 disables dumps and is equivalent - to :vlopt:`--no-dump-`. Level 9 enables dumping of everything. + `--dumpi-V3Order 9`). Level 0 disables dumps and is equivalent to + `--no-dump-`. Level 9 enables dumping of everything. .. option:: -E From 10fc1f757cb2185981516fca6dbd4f0e48f9106d Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 2 Oct 2022 23:04:55 -0400 Subject: [PATCH 075/177] Internals: cppcheck cleanups. No functional change intended. --- include/verilated_timing.h | 6 +++--- include/verilated_types.h | 5 ++++- src/V3Ast.h | 2 +- src/V3AstNodeDType.h | 2 +- src/V3DfgDfgToAst.cpp | 8 ++++---- src/V3DfgPasses.h | 6 +++--- src/V3DfgPeephole.h | 2 +- src/V3Number.cpp | 4 ++-- src/V3Number.h | 4 ++-- src/V3Order.cpp | 2 +- src/V3Param.cpp | 6 +++--- src/V3Partition.cpp | 2 +- src/V3Sched.cpp | 2 +- src/V3Timing.cpp | 2 +- src/V3Width.cpp | 2 +- 15 files changed, 29 insertions(+), 26 deletions(-) diff --git a/include/verilated_timing.h b/include/verilated_timing.h index 45de319c4..d4f4f7cb3 100644 --- a/include/verilated_timing.h +++ b/include/verilated_timing.h @@ -137,7 +137,7 @@ class VlDelayScheduler final { public: // CONSTRUCTORS - VlDelayScheduler(VerilatedContext& context) + explicit VlDelayScheduler(VerilatedContext& context) : m_context{context} {} // METHODS // Resume coroutines waiting for the current simulation time @@ -329,8 +329,8 @@ public: // CONSTRUCTORS // Construct - VlCoroutine(VlPromise* p) - : m_promisep{p} { + VlCoroutine(VlPromise* promisep) + : m_promisep{promisep} { m_promisep->m_corop = this; } // Move. Update the pointers each time the return object is moved diff --git a/include/verilated_types.h b/include/verilated_types.h index 044848e5e..11132d570 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -109,7 +109,7 @@ public: // Set elements of 'this' to 'a & !b' element-wise void andNot(const VlTriggerVec& a, const VlTriggerVec& b) { - for (size_t i = 0; i < m_flags.size(); ++i) m_flags[i] = a.m_flags[i] & !b.m_flags[i]; + for (size_t i = 0; i < m_flags.size(); ++i) m_flags[i] = a.m_flags[i] && !b.m_flags[i]; } }; @@ -1104,14 +1104,17 @@ public: m_objp->m_deleter = &deleter; refCountInc(); } + // cppcheck-suppress noExplicitConstructor VlClassRef(T_Class* objp) : m_objp{objp} { refCountInc(); } + // cppcheck-suppress noExplicitConstructor VlClassRef(const VlClassRef& copied) : m_objp{copied.m_objp} { refCountInc(); } + // cppcheck-suppress noExplicitConstructor VlClassRef(VlClassRef&& moved) : m_objp{vlstd::exchange(moved.m_objp, nullptr)} {} ~VlClassRef() { refCountDec(); } diff --git a/src/V3Ast.h b/src/V3Ast.h index 612748513..71a86943c 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -2324,7 +2324,7 @@ template bool AstNode::predicateImpl(ConstCorrectAstNode* nodep, const std::function& p) { // Implementation similar to foreach, but abort traversal as soon as result is determined. - if (!p) { + if (VL_UNCOVERABLE(!p)) { nodep->v3fatal("AstNode::foreach called with unbound function"); // LCOV_EXCL_LINE } else { using T_Arg_NonConst = typename std::remove_const::type; diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 1c9e6c506..c30a18abf 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -97,7 +97,7 @@ public: m_widthMin = widthMin; } // For backward compatibility inherit width and signing from the subDType/base type - void widthFromSub(AstNodeDType* nodep) { + void widthFromSub(const AstNodeDType* nodep) { m_width = nodep->m_width; m_widthMin = nodep->m_widthMin; m_numeric = nodep->m_numeric; diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index 8c9e13b91..9666b203c 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -89,21 +89,21 @@ AstShiftRS* makeNode( // // LCOV_EXCL_START template <> AstCCast* makeNode(const DfgCCast* vtxp, AstNodeMath*) { - vtxp->v3fatal("not implemented"); + vtxp->v3fatalSrc("not implemented"); } template <> AstAtoN* makeNode(const DfgAtoN* vtxp, AstNodeMath*) { - vtxp->v3fatal("not implemented"); + vtxp->v3fatalSrc("not implemented"); } template <> AstCompareNN* makeNode(const DfgCompareNN* vtxp, AstNodeMath*, AstNodeMath*) { - vtxp->v3fatal("not implemented"); + vtxp->v3fatalSrc("not implemented"); } template <> AstSliceSel* makeNode( const DfgSliceSel* vtxp, AstNodeMath*, AstNodeMath*, AstNodeMath*) { - vtxp->v3fatal("not implemented"); + vtxp->v3fatalSrc("not implemented"); } // LCOV_EXCL_STOP diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index 595f7ee8b..2cfa69a24 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -33,7 +33,7 @@ class V3DfgCseContext final { public: VDouble0 m_eliminated; // Number of common sub-expressions eliminated - V3DfgCseContext(const std::string& label) + explicit V3DfgCseContext(const std::string& label) : m_label{label} {} ~V3DfgCseContext(); }; @@ -43,7 +43,7 @@ class DfgRemoveVarsContext final { public: VDouble0 m_removed; // Number of redundant variables removed - DfgRemoveVarsContext(const std::string& label) + explicit DfgRemoveVarsContext(const std::string& label) : m_label{label} {} ~DfgRemoveVarsContext(); }; @@ -73,7 +73,7 @@ public: V3DfgCseContext m_cseContext1{m_label + " 2nd"}; V3DfgPeepholeContext m_peepholeContext{m_label}; DfgRemoveVarsContext m_removeVarsContext{m_label}; - V3DfgOptimizationContext(const std::string& label); + explicit V3DfgOptimizationContext(const std::string& label); ~V3DfgOptimizationContext(); const std::string& prefix() const { return m_prefix; } diff --git a/src/V3DfgPeephole.h b/src/V3DfgPeephole.h index 10fe177f6..5d346456d 100644 --- a/src/V3DfgPeephole.h +++ b/src/V3DfgPeephole.h @@ -132,7 +132,7 @@ struct V3DfgPeepholeContext final { // Count of applications for each optimization (for statistics) VDouble0 m_count[VDfgPeepholePattern::_ENUM_END]; - V3DfgPeepholeContext(const std::string& label); + explicit V3DfgPeepholeContext(const std::string& label); ~V3DfgPeepholeContext(); }; diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 00e7b1524..8861cde38 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -76,7 +76,7 @@ constexpr int MAX_SPRINTF_DOUBLE_SIZE //====================================================================== // Errors -void V3Number::v3errorEnd(std::ostringstream& str) const { +void V3Number::v3errorEnd(const std::ostringstream& str) const { std::ostringstream nsstr; nsstr << str.str(); if (m_nodep) { @@ -88,7 +88,7 @@ void V3Number::v3errorEnd(std::ostringstream& str) const { } } -void V3Number::v3errorEndFatal(std::ostringstream& str) const { +void V3Number::v3errorEndFatal(const std::ostringstream& str) const { v3errorEnd(str); assert(0); // LCOV_EXCL_LINE VL_UNREACHABLE; diff --git a/src/V3Number.h b/src/V3Number.h index f149c6ae6..82bfeacdf 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -541,8 +541,8 @@ private: string displayed(const string& vformat) const { return displayed(m_fileline, vformat); } public: - void v3errorEnd(std::ostringstream& sstr) const; - void v3errorEndFatal(std::ostringstream& sstr) const VL_ATTR_NORETURN; + void v3errorEnd(const std::ostringstream& sstr) const; + void v3errorEndFatal(const std::ostringstream& sstr) const VL_ATTR_NORETURN; void width(int width, bool sized = true) { m_data.m_sized = sized; m_data.resize(width); diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 0a28b981f..ea2313c48 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -208,7 +208,7 @@ class OrderBuildVisitor final : public VNVisitor { "AstSenTrees should have been made global in V3ActiveTop"); UASSERT_OBJ(m_scopep, nodep, "AstActive not under AstScope"); UASSERT_OBJ(!m_logicVxp, nodep, "AstActive under logic"); - UASSERT_OBJ(!m_inClocked && !m_domainp & !m_hybridp, nodep, "Should not nest"); + UASSERT_OBJ(!m_inClocked && !m_domainp && !m_hybridp, nodep, "Should not nest"); // This is the original sensitivity of the block (i.e.: not the ref into the TRIGGERVEC) diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 18dfc06b6..3ff5e8b56 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -915,9 +915,9 @@ class ParamVisitor final : public VNVisitor { // Process interface cells, then non-interface cells, which may reference an interface // cell. while (!m_cellps.empty()) { - const auto itm = m_cellps.cbegin(); - AstNode* const cellp = itm->second; - m_cellps.erase(itm); + const auto itim = m_cellps.cbegin(); + AstNode* const cellp = itim->second; + m_cellps.erase(itim); AstNodeModule* srcModp = nullptr; if (const auto* modCellp = VN_CAST(cellp, Cell)) { diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index 348cbb4b9..94b48c99b 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -928,7 +928,7 @@ class PartPropagateCp final { public: // CONSTRUCTORS - PartPropagateCp(bool slowAsserts) + explicit PartPropagateCp(bool slowAsserts) : m_slowAsserts{slowAsserts} {} // METHODS diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index dc3ac1c2c..ebf0dfea9 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -97,7 +97,7 @@ AstAssign* setVar(AstVarScope* vscp, uint32_t val) { return new AstAssign{flp, refp, valp}; }; -void remapSensitivities(LogicByScope& lbs, +void remapSensitivities(const LogicByScope& lbs, std::unordered_map senTreeMap) { for (const auto& pair : lbs) { AstActive* const activep = pair.second; diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 5ebb7ea7b..6f60cd266 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -105,7 +105,7 @@ private: AstScope* m_scopep = nullptr; // Current scope AstActive* m_activep = nullptr; // Current active AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Fork we're under - double m_timescaleFactor; // Factor to scale delays by + double m_timescaleFactor = 1.0; // Factor to scale delays by // Unique names V3UniqueNames m_contAssignVarNames{"__VassignWtmp__"}; // Names for temp AssignW vars diff --git a/src/V3Width.cpp b/src/V3Width.cpp index eb249b88e..e7901340a 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -4363,7 +4363,7 @@ private: fmt = ch; } else if (inPct && (isdigit(ch) || ch == '.' || ch == '-')) { fmt += ch; - } else if (tolower(inPct)) { + } else if (inPct) { inPct = false; bool added = false; switch (tolower(ch)) { From 2fc1746ef5cd6bf7925de64b8d9e3245062239f5 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 3 Oct 2022 10:50:47 +0100 Subject: [PATCH 076/177] Tracing: Clear offload buffer pointers when no longer needed These are also used as a marker (when non-nullptr) when creating a buffer. Reset them when they are not valid to avoid invalid write if a buffer is created after a close (due to a subsequent re-open). Fixes #3651. --- include/verilated_trace_imp.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/verilated_trace_imp.h b/include/verilated_trace_imp.h index 90dcc2e70..21756dc77 100644 --- a/include/verilated_trace_imp.h +++ b/include/verilated_trace_imp.h @@ -644,6 +644,10 @@ void VerilatedTrace::dump(uint64_t timeui) VL_MT_SAFE_EXCLUD // Assert no buffer overflow assert(m_offloadBufferWritep - bufferp <= m_offloadBufferSize); + // Reset our pointers as we are giving up the buffer + m_offloadBufferWritep = nullptr; + m_offloadBufferEndp = nullptr; + // Pass it to the worker thread m_offloadBuffersToWorker.put(bufferp); } From ced82cbac41f1f75265a47c8b7e68312914db5ba Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Mon, 3 Oct 2022 10:57:37 -0400 Subject: [PATCH 077/177] Internals: Add some internal coverage exclusions etc. No functional change. --- Makefile.in | 3 +-- include/verilated_cov.cpp | 2 +- include/verilated_fst_sc.h | 2 ++ include/verilated_threads.cpp | 2 +- include/verilated_vcd_sc.h | 2 ++ src/astgen | 1 + 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Makefile.in b/Makefile.in index a32d04619..a8dd0a7fa 100644 --- a/Makefile.in +++ b/Makefile.in @@ -329,8 +329,7 @@ CLANGTIDY_FLAGS = -config='' \ -header-filter='.*' \ -checks='-fuchsia-*,-cppcoreguidelines-avoid-c-arrays,-cppcoreguidelines-init-variables,-cppcoreguidelines-avoid-goto,-modernize-avoid-c-arrays,-readability-magic-numbers,-readability-simplify-boolean-expr,-cppcoreguidelines-macro-usage' \ -CLANGTIDY_DEP = $(subst .h,.h.tidy,$(CPPCHECK_H)) \ - $(subst .cpp,.cpp.tidy,$(CPPCHECK_CPP)) +CLANGTIDY_DEP = $(subst .cpp,.cpp.tidy,$(CPPCHECK_CPP)) CLANGTIDY_DEFS = -DVL_DEBUG=1 -DVL_THREADED=1 -DVL_CPPCHECK=1 clang-tidy: $(CLANGTIDY_DEP) diff --git a/include/verilated_cov.cpp b/include/verilated_cov.cpp index 3932afef6..89b7fb86d 100644 --- a/include/verilated_cov.cpp +++ b/include/verilated_cov.cpp @@ -518,7 +518,7 @@ VerilatedCovContext* VerilatedContext::coveragep() VL_MT_SAFE { if (VL_UNLIKELY(!m_coveragep)) { const VerilatedLockGuard lock{s_mutex}; // cppcheck-suppress identicalInnerCondition - if (VL_LIKELY(!m_coveragep)) { // Not redundant, prevents race + if (VL_LIKELY(!m_coveragep)) { // LCOV_EXCL_LINE // Not redundant, prevents race m_coveragep.reset(new VerilatedCovImp); } } diff --git a/include/verilated_fst_sc.h b/include/verilated_fst_sc.h index f5aaa305c..a34d72d0b 100644 --- a/include/verilated_fst_sc.h +++ b/include/verilated_fst_sc.h @@ -84,6 +84,7 @@ private: // clang-format off // Formatting matches that of sc_trace.h + // LCOV_EXCL_START #if (SYSTEMC_VERSION >= 20171012) DECL_TRACE_METHOD_A( sc_event ) DECL_TRACE_METHOD_A( sc_time ) @@ -118,6 +119,7 @@ private: DECL_TRACE_METHOD_A( sc_dt::sc_bv_base ) DECL_TRACE_METHOD_A( sc_dt::sc_lv_base ) + // LCOV_EXCL_STOP // clang-format on #undef DECL_TRACE_METHOD_A diff --git a/include/verilated_threads.cpp b/include/verilated_threads.cpp index 9d6bdedc9..40adba4bc 100644 --- a/include/verilated_threads.cpp +++ b/include/verilated_threads.cpp @@ -58,7 +58,7 @@ VlWorkerThread::~VlWorkerThread() { m_cthread.join(); } -static void shutdownTask(void*, bool) { +static void shutdownTask(void*, bool) { // LCOV_EXCL_LINE // Deliberately empty, we use the address of this function as a magic number } diff --git a/include/verilated_vcd_sc.h b/include/verilated_vcd_sc.h index c0ba6e528..ad8de7133 100644 --- a/include/verilated_vcd_sc.h +++ b/include/verilated_vcd_sc.h @@ -86,6 +86,7 @@ private: // clang-format off // Formatting matches that of sc_trace.h + // LCOV_EXCL_START #if (SYSTEMC_VERSION >= 20171012) DECL_TRACE_METHOD_A( sc_event ) DECL_TRACE_METHOD_A( sc_time ) @@ -120,6 +121,7 @@ private: DECL_TRACE_METHOD_A( sc_dt::sc_bv_base ) DECL_TRACE_METHOD_A( sc_dt::sc_lv_base ) + // LCOV_EXCL_STOP // clang-format on #undef DECL_TRACE_METHOD_A diff --git a/src/astgen b/src/astgen index 1816ae88a..f0883dd08 100755 --- a/src/astgen +++ b/src/astgen @@ -964,6 +964,7 @@ def write_op_checks(filename): backp = tailp = opp; opp = {next}; }} while (opp); + if (headp && tailp) {{}} // Prevent unused UASSERT_OBJ(headp->m_headtailp == tailp, headp, "Tail in headtailp is inconsistent"); UASSERT_OBJ(tailp->m_headtailp == headp, tailp, "Head in headtailp is inconsistent"); }} From 56ac054fb2e9842cf88e0e08f9cfa1ea59c9ae38 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Mon, 3 Oct 2022 17:40:30 +0200 Subject: [PATCH 078/177] Internals: Refactor verilated_timing.* (#3653). * Put suspended coroutine source location in a separate struct, * Have `dump()` always print, wrap calls in `VL_DEBUG_IF`, * Improve const correctness. --- include/verilated_timing.cpp | 54 ++++++------- include/verilated_timing.h | 150 +++++++++++++++++------------------ 2 files changed, 99 insertions(+), 105 deletions(-) diff --git a/include/verilated_timing.cpp b/include/verilated_timing.cpp index 3282b6b3f..baef60230 100644 --- a/include/verilated_timing.cpp +++ b/include/verilated_timing.cpp @@ -36,8 +36,8 @@ void VlCoroutineHandle::resume() { } #ifdef VL_DEBUG -void VlCoroutineHandle::dump() { - VL_DEBUG_IF(VL_PRINTF("Process waiting at %s:%d\n", m_filename, m_linenum);); +void VlCoroutineHandle::dump() const { + VL_PRINTF("Process waiting at %s:%d\n", m_fileline.filename(), m_fileline.lineno()); } #endif @@ -45,16 +45,15 @@ void VlCoroutineHandle::dump() { // VlDelayScheduler:: Methods #ifdef VL_DEBUG -void VlDelayScheduler::VlDelayedCoroutine::dump() { - VL_DEBUG_IF(VL_DBG_MSGF(" Awaiting time %lu: ", m_timestep);); +void VlDelayScheduler::VlDelayedCoroutine::dump() const { + VL_DBG_MSGF(" Awaiting time %lu: ", m_timestep); m_handle.dump(); } #endif void VlDelayScheduler::resume() { #ifdef VL_DEBUG - dump(); - VL_DEBUG_IF(VL_DBG_MSGF(" Resuming delayed processes\n");); + VL_DEBUG_IF(dump(); VL_DBG_MSGF(" Resuming delayed processes\n");); #endif while (awaitingCurrentTime()) { if (m_queue.front().m_timestep != m_context.time()) { @@ -70,7 +69,7 @@ void VlDelayScheduler::resume() { } } -uint64_t VlDelayScheduler::nextTimeSlot() { +uint64_t VlDelayScheduler::nextTimeSlot() const { if (empty()) { VL_FATAL_MT(__FILE__, __LINE__, "", "%Error: There is no next time slot scheduled"); } @@ -78,12 +77,12 @@ uint64_t VlDelayScheduler::nextTimeSlot() { } #ifdef VL_DEBUG -void VlDelayScheduler::dump() { +void VlDelayScheduler::dump() const { if (m_queue.empty()) { - VL_DEBUG_IF(VL_DBG_MSGF(" No delayed processes:\n");); + VL_DBG_MSGF(" No delayed processes:\n"); } else { - VL_DEBUG_IF(VL_DBG_MSGF(" Delayed processes:\n");); - for (auto& susp : m_queue) susp.dump(); + VL_DBG_MSGF(" Delayed processes:\n"); + for (const auto& susp : m_queue) susp.dump(); } } #endif @@ -93,8 +92,8 @@ void VlDelayScheduler::dump() { void VlTriggerScheduler::resume(const char* eventDescription) { #ifdef VL_DEBUG - dump(eventDescription); - VL_DEBUG_IF(VL_DBG_MSGF(" Resuming processes waiting for %s\n", eventDescription);); + VL_DEBUG_IF(dump(eventDescription); + VL_DBG_MSGF(" Resuming processes waiting for %s\n", eventDescription);); #endif for (auto& susp : m_ready) susp.resume(); m_ready.clear(); @@ -106,7 +105,7 @@ void VlTriggerScheduler::commit(const char* eventDescription) { if (!m_uncommitted.empty()) { VL_DEBUG_IF( VL_DBG_MSGF(" Committing processes waiting for %s:\n", eventDescription); - for (auto& susp + for (const auto& susp : m_uncommitted) { VL_DBG_MSGF(" - "); susp.dump(); @@ -120,26 +119,22 @@ void VlTriggerScheduler::commit(const char* eventDescription) { } #ifdef VL_DEBUG -void VlTriggerScheduler::dump(const char* eventDescription) { +void VlTriggerScheduler::dump(const char* eventDescription) const { if (m_ready.empty()) { - VL_DEBUG_IF( - VL_DBG_MSGF(" No ready processes waiting for %s\n", eventDescription);); + VL_DBG_MSGF(" No ready processes waiting for %s\n", eventDescription); } else { - VL_DEBUG_IF(for (auto& susp - : m_ready) { + for (const auto& susp : m_ready) { VL_DBG_MSGF(" Ready processes waiting for %s:\n", eventDescription); VL_DBG_MSGF(" - "); susp.dump(); - }); + } } if (!m_uncommitted.empty()) { - VL_DEBUG_IF( - VL_DBG_MSGF(" Uncommitted processes waiting for %s:\n", eventDescription); - for (auto& susp - : m_uncommitted) { - VL_DBG_MSGF(" - "); - susp.dump(); - }); + VL_DBG_MSGF(" Uncommitted processes waiting for %s:\n", eventDescription); + for (const auto& susp : m_uncommitted) { + VL_DBG_MSGF(" - "); + susp.dump(); + } } } #endif @@ -147,9 +142,8 @@ void VlTriggerScheduler::dump(const char* eventDescription) { //====================================================================== // VlForkSync:: Methods -void VlForkSync::done(const char* filename, int linenum) { - VL_DEBUG_IF( - VL_DBG_MSGF(" Process forked at %s:%d finished\n", filename, linenum);); +void VlForkSync::done(const char* filename, int lineno) { + VL_DEBUG_IF(VL_DBG_MSGF(" Process forked at %s:%d finished\n", filename, lineno);); if (m_join->m_counter > 0) m_join->m_counter--; if (m_join->m_counter == 0) m_join->m_susp.resume(); } diff --git a/include/verilated_timing.h b/include/verilated_timing.h index d4f4f7cb3..c07daa7af 100644 --- a/include/verilated_timing.h +++ b/include/verilated_timing.h @@ -56,6 +56,36 @@ #endif // clang-format on +//============================================================================= +// VlFileLineDebug stores a SystemVerilog source code location. Used in VlCoroutineHandle for +// debugging purposes. + +class VlFileLineDebug final { + // MEMBERS +#ifdef VL_DEBUG + const char* m_filename = nullptr; + int m_lineno = 0; +#endif + +public: + // CONSTRUCTORS + // Construct + VlFileLineDebug() = default; + VlFileLineDebug(const char* filename, int lineno) +#ifdef VL_DEBUG + : m_filename{filename} + , m_lineno{lineno} +#endif + { + } + + // METHODS +#ifdef VL_DEBUG + const char* filename() const { return m_filename; } + int lineno() const { return m_lineno; } +#endif +}; + //============================================================================= // VlCoroutineHandle is a non-copyable (but movable) coroutine handle. On resume, the handle is // cleared, as we assume that either the coroutine has finished and deleted itself, or, if it got @@ -66,32 +96,20 @@ class VlCoroutineHandle final { // MEMBERS std::coroutine_handle<> m_coro; // The wrapped coroutine handle -#ifdef VL_DEBUG - const char* m_filename; - int m_linenum; -#endif + VlFileLineDebug m_fileline; public: // CONSTRUCTORS // Construct - VlCoroutineHandle(std::coroutine_handle<> coro = nullptr, const char* filename = nullptr, - int linenum = 0) + VlCoroutineHandle() + : m_coro{nullptr} {} + VlCoroutineHandle(std::coroutine_handle<> coro, VlFileLineDebug fileline) : m_coro{coro} -#ifdef VL_DEBUG - , m_filename{filename} - , m_linenum{linenum} -#endif - { - } + , m_fileline{fileline} {} // Move the handle, leaving a nullptr VlCoroutineHandle(VlCoroutineHandle&& moved) : m_coro{std::exchange(moved.m_coro, nullptr)} -#ifdef VL_DEBUG - , m_filename{moved.m_filename} - , m_linenum{moved.m_linenum} -#endif - { - } + , m_fileline{moved.m_fileline} {} // Destroy if the handle isn't null ~VlCoroutineHandle() { // Usually these coroutines should get resumed; we only need to clean up if we destroy a @@ -107,7 +125,7 @@ public: // Resume the coroutine if the handle isn't null void resume(); #ifdef VL_DEBUG - void dump(); + void dump() const; #endif }; @@ -126,7 +144,7 @@ class VlDelayScheduler final { return m_timestep > other.m_timestep; } #ifdef VL_DEBUG - void dump(); + void dump() const; #endif }; using VlDelayedCoroutineQueue = std::vector; @@ -144,42 +162,32 @@ public: void resume(); // Returns the simulation time of the next time slot (aborts if there are no delayed // coroutines) - uint64_t nextTimeSlot(); + uint64_t nextTimeSlot() const; // Are there no delayed coroutines awaiting? - bool empty() { return m_queue.empty(); } + bool empty() const { return m_queue.empty(); } // Are there coroutines to resume at the current simulation time? - bool awaitingCurrentTime() { + bool awaitingCurrentTime() const { return !empty() && m_queue.front().m_timestep <= m_context.time(); } #ifdef VL_DEBUG - void dump(); + void dump() const; #endif // Used by coroutines for co_awaiting a certain simulation time - auto delay(uint64_t delay, const char* filename, int linenum) { + auto delay(uint64_t delay, const char* filename, int lineno) { struct Awaitable { VlDelayedCoroutineQueue& queue; uint64_t delay; -#ifdef VL_DEBUG - const char* filename; - int linenum; -#endif - bool await_ready() { return false; } // Always suspend + VlFileLineDebug fileline; + + bool await_ready() const { return false; } // Always suspend void await_suspend(std::coroutine_handle<> coro) { -#ifdef VL_DEBUG - queue.push_back({delay, VlCoroutineHandle{coro, filename, linenum}}); -#else - queue.push_back({delay, coro}); -#endif + queue.push_back({delay, VlCoroutineHandle{coro, fileline}}); // Move last element to the proper place in the max-heap std::push_heap(queue.begin(), queue.end()); } - void await_resume() {} + void await_resume() const {} }; -#ifdef VL_DEBUG - return Awaitable{m_queue, m_context.time() + delay, filename, linenum}; -#else - return Awaitable{m_queue, m_context.time() + delay}; -#endif + return Awaitable{m_queue, m_context.time() + delay, VlFileLineDebug{filename, lineno}}; } }; @@ -207,35 +215,25 @@ public: // Moves all coroutines from m_uncommitted to m_ready void commit(const char* eventDescription); // Are there no coroutines awaiting? - bool empty() { return m_ready.empty() && m_uncommitted.empty(); } + bool empty() const { return m_ready.empty() && m_uncommitted.empty(); } #ifdef VL_DEBUG - void dump(const char* eventDescription); + void dump(const char* eventDescription) const; #endif // Used by coroutines for co_awaiting a certain trigger - auto trigger(const char* eventDescription, const char* filename, int linenum) { + auto trigger(const char* eventDescription, const char* filename, int lineno) { VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n", - eventDescription, filename, linenum);); + eventDescription, filename, lineno);); struct Awaitable { VlCoroutineVec& suspended; // Coros waiting on trigger -#ifdef VL_DEBUG - const char* filename; - int linenum; -#endif - bool await_ready() { return false; } // Always suspend + VlFileLineDebug fileline; + + bool await_ready() const { return false; } // Always suspend void await_suspend(std::coroutine_handle<> coro) { -#ifdef VL_DEBUG - suspended.emplace_back(coro, filename, linenum); -#else - suspended.emplace_back(coro); -#endif + suspended.emplace_back(coro, fileline); } - void await_resume() {} + void await_resume() const {} }; -#ifdef VL_DEBUG - return Awaitable{m_uncommitted, filename, linenum}; -#else - return Awaitable{m_uncommitted}; -#endif + return Awaitable{m_uncommitted, VlFileLineDebug{filename, lineno}}; } }; @@ -244,9 +242,9 @@ public: // Allows forcing the move of coroutine locals to the heap. struct VlNow { - bool await_ready() { return false; } // Always suspend - bool await_suspend(std::coroutine_handle<>) { return false; } // Resume immediately - void await_resume() {} + bool await_ready() const { return false; } // Always suspend + bool await_suspend(std::coroutine_handle<>) const { return false; } // Resume immediately + void await_resume() const {} }; //============================================================================= @@ -254,9 +252,9 @@ struct VlNow { // wait statements. struct VlForever { - bool await_ready() { return false; } // Always suspend - void await_suspend(std::coroutine_handle<> coro) { coro.destroy(); } - void await_resume() {} + bool await_ready() const { return false; } // Always suspend + void await_suspend(std::coroutine_handle<> coro) const { coro.destroy(); } + void await_resume() const {} }; //============================================================================= @@ -278,19 +276,21 @@ public: void init(size_t count) { m_join.reset(new VlJoin{count, {}}); } // Called whenever any of the forked processes finishes. If the join counter reaches 0, the // main process gets resumed - void done(const char* filename, int linenum); + void done(const char* filename, int lineno); // Used by coroutines for co_awaiting a join - auto join(const char* filename, int linenum) { + auto join(const char* filename, int lineno) { assert(m_join); VL_DEBUG_IF( - VL_DBG_MSGF(" Awaiting join of fork at: %s:%d\n", filename, linenum);); + VL_DBG_MSGF(" Awaiting join of fork at: %s:%d\n", filename, lineno);); struct Awaitable { const std::shared_ptr join; // Join to await on + VlFileLineDebug fileline; + bool await_ready() { return join->m_counter == 0; } // Suspend if join still exists - void await_suspend(std::coroutine_handle<> coro) { join->m_susp = coro; } - void await_resume() {} + void await_suspend(std::coroutine_handle<> coro) { join->m_susp = {coro, fileline}; } + void await_resume() const {} }; - return Awaitable{m_join}; + return Awaitable{m_join, VlFileLineDebug{filename, lineno}}; } }; @@ -310,13 +310,13 @@ private: VlCoroutine get_return_object() { return {this}; } // Never suspend at the start of the coroutine - std::suspend_never initial_suspend() { return {}; } + std::suspend_never initial_suspend() const { return {}; } // Never suspend at the end of the coroutine (thanks to this, the coroutine will clean up // after itself) std::suspend_never final_suspend() noexcept; - void unhandled_exception() { std::abort(); } + void unhandled_exception() const { std::abort(); } void return_void() const {} }; From 965d99f1bc68eb1d785f7cbea842fe871df1e11d Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 4 Oct 2022 11:03:41 +0100 Subject: [PATCH 079/177] DFG: Make implementation more similar to AST Use the same style, and reuse the bulk of astgen to generate DfgVertex related code. In particular allow for easier definition of custom DfgVertex sub-types that do not directly correspond to an AstNode sub-type. Also introduces specific names for the fixed arity vertices. No functional change intended. --- docs/internals.rst | 6 +- src/Makefile_obj.in | 22 +- src/V3Ast.h | 8 +- src/V3AstNodeDType.h | 52 ++-- src/V3AstNodeMath.h | 388 ++++++++++++++-------------- src/V3AstNodeOther.h | 374 +++++++++++++-------------- src/V3Dfg.cpp | 48 ++-- src/V3Dfg.h | 589 +++++++++++++++--------------------------- src/V3DfgAstToDfg.cpp | 20 +- src/V3DfgDfgToAst.cpp | 26 +- src/V3DfgPasses.cpp | 2 +- src/V3DfgPeephole.cpp | 22 +- src/V3DfgVertices.h | 229 ++++++++++++++++ src/astgen | 562 ++++++++++++++++++++++++---------------- 14 files changed, 1263 insertions(+), 1085 deletions(-) create mode 100644 src/V3DfgVertices.h diff --git a/docs/internals.rst b/docs/internals.rst index d2424c403..c2031c857 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -1047,9 +1047,9 @@ Generating ``AstNode`` members Some of the member s of ``AstNode`` sub-classes are generated by ``astgen``. These are emitted as pre-processor macro definitions, which then need to be added to the ``AstNode`` sub-classes they correspond to. Specifically ``class -AstFoo`` should contain an instance of ``ASTGEN_MEMBERS_Foo;`` at class scope. -The ``astgen`` script checks and errors if this is not present. The method -generated depends on whether the class is a concrete final class, or an +AstFoo`` should contain an instance of ``ASTGEN_MEMBERS_AstFoo;`` at class +scope. The ``astgen`` script checks and errors if this is not present. The +method generated depends on whether the class is a concrete final class, or an abstract ``AstNode*`` base-class, and on ``@astgen`` directives present in comment sections in the body of the ``AstNode`` sub-class definitions. diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index a6430e10c..503fb540f 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -289,6 +289,7 @@ NON_STANDALONE_HEADERS = \ V3AstNodeDType.h \ V3AstNodeMath.h \ V3AstNodeOther.h \ + V3DfgVertices.h \ V3WidthCommit.h \ AST_DEFS := \ @@ -296,10 +297,19 @@ AST_DEFS := \ V3AstNodeMath.h \ V3AstNodeOther.h \ +DFG_DEFS := \ + V3DfgVertices.h + +#### astgen common flags + +ASTGENFLAGS = -I $(srcdir) +ASTGENFLAGS += $(foreach f,$(AST_DEFS),--astdef $f) +ASTGENFLAGS += $(foreach f,$(DFG_DEFS),--dfgdef $f) + #### Linking ifeq ($(VL_VLCOV),) -PREDEP_H = V3Ast__gen_classes.h +PREDEP_H = V3Ast__gen_forward_class_decls.h OBJS += $(RAW_OBJS) $(NC_OBJS) else PREDEP_H = @@ -318,8 +328,8 @@ V3Number_test: V3Number_test.o #### Modules -%__gen.cpp: %.cpp $(ASTGEN) $(AST_DEFS) - $(PYTHON3) $(ASTGEN) -I $(srcdir) $(foreach f,$(AST_DEFS),--astdef $f) $*.cpp +%__gen.cpp: %.cpp $(ASTGEN) $(AST_DEFS) $(DFG_DEFS) + $(PYTHON3) $(ASTGEN) $(ASTGENFLAGS) $*.cpp %.o: %.cpp $(OBJCACHE) ${CXX} ${CXXFLAGS} ${CPPFLAGSWALL} -c $< -o $@ @@ -341,7 +351,7 @@ V3PreProc.o: V3PreProc.cpp V3PreLex.yy.cpp #### Generated files # Target rule called before parallel build to make generated files -serial:: V3Ast__gen_classes.h V3ParseBison.c +serial:: V3Ast__gen_forward_class_decls.h V3ParseBison.c serial_vlcov:: vlcovgen.d @@ -349,8 +359,8 @@ vlcovgen.d: $(VLCOVGEN) $(srcdir)/../include/verilated_cov_key.h $(PYTHON3) $(VLCOVGEN) --srcdir $(srcdir) touch $@ -V3Ast__gen_classes.h : $(ASTGEN) $(AST_DEFS) - $(PYTHON3) $(ASTGEN) -I $(srcdir) $(foreach f,$(AST_DEFS),--astdef $f) --classes +V3Ast__gen_forward_class_decls.h: $(ASTGEN) $(AST_DEFS) $(DFG_DEFS) + $(PYTHON3) $(ASTGEN) $(ASTGENFLAGS) --classes V3ParseBison.h: V3ParseBison.c diff --git a/src/V3Ast.h b/src/V3Ast.h index 71a86943c..fa33caca9 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -26,7 +26,7 @@ #include "V3Global.h" #include "V3Number.h" -#include "V3Ast__gen_classes.h" // From ./astgen +#include "V3Ast__gen_forward_class_decls.h" // From ./astgen #include #include @@ -87,7 +87,7 @@ using MTaskIdSet = std::set; // Set of mtaskIds for Var sorting class VNType final { public: -#include "V3Ast__gen_types.h" // From ./astgen +#include "V3Ast__gen_type_enum.h" // From ./astgen // Above include has: // enum en {...}; // const char* ascii() const {...}; @@ -2179,7 +2179,7 @@ void AstNode::addPrev(AstNode* newp) { } // Specialisations of privateTypeTest -#include "V3Ast__gen_impl.h" // From ./astgen +#include "V3Ast__gen_type_tests.h" // From ./astgen // Specializations of AstNode::mayBeUnder template <> @@ -2478,7 +2478,7 @@ AstNode* VNVisitor::iterateSubtreeReturnEdits(AstNode* nodep) { return nodep->iterateSubtreeReturnEdits(*this); } -// Include macros generated by 'astgen'. These include ASTGEN_MEMBERS_ +// Include macros generated by 'astgen'. These include ASTGEN_MEMBERS_Ast // for each AstNode sub-type, and ASTGEN_SUPER_ for concrete final // AstNode sub-types. The generated members include boilerplate methods related // to cloning, visitor dispatch, and other functionality. ASTGEN_SUPER_ diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index c30a18abf..c3f4d096e 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -50,7 +50,7 @@ protected: : AstNode{t, fl} {} public: - ASTGEN_MEMBERS_NodeDType; + ASTGEN_MEMBERS_AstNodeDType; // ACCESSORS void dump(std::ostream& str) const override; virtual void dumpSmall(std::ostream& str) const; @@ -144,7 +144,7 @@ protected: : AstNodeDType{t, fl} {} public: - ASTGEN_MEMBERS_NodeArrayDType; + ASTGEN_MEMBERS_AstNodeArrayDType; void dump(std::ostream& str) const override; void dumpSmall(std::ostream& str) const override; const char* broken() const override { @@ -212,7 +212,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeUOrStructDType; + ASTGEN_MEMBERS_AstNodeUOrStructDType; int uniqueNum() const { return m_uniqueNum; } const char* broken() const override; void dump(std::ostream& str) const override; @@ -270,7 +270,7 @@ public: this->rangep(rangep); this->valuep(valuep); } - ASTGEN_MEMBERS_EnumItem; + ASTGEN_MEMBERS_AstEnumItem; string name() const override { return m_name; } bool maybePointedTo() const override { return true; } bool hasDType() const override { return true; } @@ -300,7 +300,7 @@ public: keyDTypep(keyDtp); dtypep(dtp); } - ASTGEN_MEMBERS_AssocArrayDType; + ASTGEN_MEMBERS_AstAssocArrayDType; const char* broken() const override { BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists()) || (!m_refDTypep && childDTypep()))); @@ -385,7 +385,7 @@ private: AstRange* rangep); public: - ASTGEN_MEMBERS_BasicDType; + ASTGEN_MEMBERS_AstBasicDType; void dump(std::ostream& str) const override; // width/widthMin/numeric compared elsewhere bool same(const AstNode* samep) const override { @@ -467,7 +467,7 @@ public: this->childDTypep(childDTypep); this->elementsp(elementsp); } - ASTGEN_MEMBERS_BracketArrayDType; + ASTGEN_MEMBERS_AstBracketArrayDType; bool similarDType(AstNodeDType* samep) const override { V3ERROR_NA_RETURN(false); } AstNodeDType* subDTypep() const override { return childDTypep(); } // METHODS @@ -495,7 +495,7 @@ public: this->dtypep(this); this->addParamsp(paramsp); } - ASTGEN_MEMBERS_ClassRefDType; + ASTGEN_MEMBERS_AstClassRefDType; // METHODS const char* broken() const override; void cloneRelink() override; @@ -539,7 +539,7 @@ public: dtypep(nullptr); // V3Width will resolve widthFromSub(subDTypep()); } - ASTGEN_MEMBERS_ConstDType; + ASTGEN_MEMBERS_AstConstDType; const char* broken() const override { BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists()) || (!m_refDTypep && childDTypep()))); @@ -593,7 +593,7 @@ public: childDTypep(dtp); // Only for parser dtypep(nullptr); // V3Width will resolve } - ASTGEN_MEMBERS_DefImplicitDType; + ASTGEN_MEMBERS_AstDefImplicitDType; int uniqueNum() const { return m_uniqueNum; } bool same(const AstNode* samep) const override { const AstDefImplicitDType* const sp = static_cast(samep); @@ -635,7 +635,7 @@ public: refDTypep(dtp); dtypep(nullptr); // V3Width will resolve } - ASTGEN_MEMBERS_DynArrayDType; + ASTGEN_MEMBERS_AstDynArrayDType; const char* broken() const override { BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists()) || (!m_refDTypep && childDTypep()))); @@ -677,7 +677,7 @@ public: : ASTGEN_SUPER_EmptyQueueDType(fl) { dtypep(this); } - ASTGEN_MEMBERS_EmptyQueueDType; + ASTGEN_MEMBERS_AstEmptyQueueDType; void dumpSmall(std::ostream& str) const override; bool hasDType() const override { return true; } bool maybePointedTo() const override { return true; } @@ -716,7 +716,7 @@ public: dtypep(nullptr); // V3Width will resolve widthFromSub(subDTypep()); } - ASTGEN_MEMBERS_EnumDType; + ASTGEN_MEMBERS_AstEnumDType; const char* broken() const override { BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists()) || (!m_refDTypep && childDTypep()))); @@ -777,7 +777,7 @@ public: , m_cellName{cellName} , m_ifaceName{ifaceName} , m_modportName{modport} {} - ASTGEN_MEMBERS_IfaceRefDType; + ASTGEN_MEMBERS_AstIfaceRefDType; // METHODS const char* broken() const override; void dump(std::ostream& str = std::cout) const override; @@ -832,7 +832,7 @@ public: dtypep(this); widthFromSub(subDTypep()); } - ASTGEN_MEMBERS_MemberDType; + ASTGEN_MEMBERS_AstMemberDType; string name() const override { return m_name; } // * = Var name bool hasDType() const override { return true; } bool maybePointedTo() const override { return true; } @@ -889,7 +889,7 @@ public: childDTypep(dtp); // Only for parser dtypep(nullptr); // V3Width will resolve } - ASTGEN_MEMBERS_ParamTypeDType; + ASTGEN_MEMBERS_AstParamTypeDType; AstNodeDType* getChildDTypep() const override { return childDTypep(); } AstNodeDType* subDTypep() const override { return dtypep() ? dtypep() : childDTypep(); } AstBasicDType* basicp() const override { return subDTypep()->basicp(); } @@ -923,7 +923,7 @@ class AstParseTypeDType final : public AstNodeDType { public: explicit AstParseTypeDType(FileLine* fl) : ASTGEN_SUPER_ParseTypeDType(fl) {} - ASTGEN_MEMBERS_ParseTypeDType; + ASTGEN_MEMBERS_AstParseTypeDType; AstNodeDType* dtypep() const { return nullptr; } // METHODS bool similarDType(AstNodeDType* samep) const override { return this == samep; } @@ -960,7 +960,7 @@ public: refDTypep(dtp); dtypep(dtp); } - ASTGEN_MEMBERS_QueueDType; + ASTGEN_MEMBERS_AstQueueDType; const char* broken() const override { BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists()) || (!m_refDTypep && childDTypep()))); @@ -1026,7 +1026,7 @@ public: : ASTGEN_SUPER_RefDType(fl) { this->typeofp(typeofp); } - ASTGEN_MEMBERS_RefDType; + ASTGEN_MEMBERS_AstRefDType; // METHODS const char* broken() const override; void cloneRelink() override; @@ -1101,7 +1101,7 @@ public: refDTypep(nullptr); dtypep(nullptr); // V3Width will resolve } - ASTGEN_MEMBERS_UnsizedArrayDType; + ASTGEN_MEMBERS_AstUnsizedArrayDType; const char* broken() const override { BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists()) || (!m_refDTypep && childDTypep()))); @@ -1134,7 +1134,7 @@ public: : ASTGEN_SUPER_VoidDType(fl) { dtypep(this); } - ASTGEN_MEMBERS_VoidDType; + ASTGEN_MEMBERS_AstVoidDType; void dumpSmall(std::ostream& str) const override; bool hasDType() const override { return true; } bool maybePointedTo() const override { return true; } @@ -1166,7 +1166,7 @@ public: refDTypep(nullptr); dtypep(nullptr); // V3Width will resolve } - ASTGEN_MEMBERS_WildcardArrayDType; + ASTGEN_MEMBERS_AstWildcardArrayDType; const char* broken() const override { BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists()) || (!m_refDTypep && childDTypep()))); @@ -1199,7 +1199,7 @@ class AstPackArrayDType final : public AstNodeArrayDType { public: inline AstPackArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp, AstRange* rangep); inline AstPackArrayDType(FileLine* fl, AstNodeDType* dtp, AstRange* rangep); - ASTGEN_MEMBERS_PackArrayDType; + ASTGEN_MEMBERS_AstPackArrayDType; string prettyDTypeName() const override; bool isCompound() const override { return false; } }; @@ -1226,7 +1226,7 @@ public: // width and signing from the subDType/base type widthFromSub(subDTypep()); } - ASTGEN_MEMBERS_UnpackArrayDType; + ASTGEN_MEMBERS_AstUnpackArrayDType; string prettyDTypeName() const override; bool same(const AstNode* samep) const override { const AstUnpackArrayDType* const sp = static_cast(samep); @@ -1244,7 +1244,7 @@ public: // VSigning below is mispurposed to indicate if packed or not AstStructDType(FileLine* fl, VSigning numericUnpack) : ASTGEN_SUPER_StructDType(fl, numericUnpack) {} - ASTGEN_MEMBERS_StructDType; + ASTGEN_MEMBERS_AstStructDType; string verilogKwd() const override { return "struct"; } }; class AstUnionDType final : public AstNodeUOrStructDType { @@ -1253,7 +1253,7 @@ public: // VSigning below is mispurposed to indicate if packed or not AstUnionDType(FileLine* fl, VSigning numericUnpack) : ASTGEN_SUPER_UnionDType(fl, numericUnpack) {} - ASTGEN_MEMBERS_UnionDType; + ASTGEN_MEMBERS_AstUnionDType; string verilogKwd() const override { return "union"; } }; diff --git a/src/V3AstNodeMath.h b/src/V3AstNodeMath.h index 977a49b96..dc2d6f8ad 100644 --- a/src/V3AstNodeMath.h +++ b/src/V3AstNodeMath.h @@ -44,7 +44,7 @@ protected: : AstNode{t, fl} {} public: - ASTGEN_MEMBERS_NodeMath; + ASTGEN_MEMBERS_AstNodeMath; // METHODS void dump(std::ostream& str) const override; bool hasDType() const override { return true; } @@ -70,7 +70,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeBiop; + ASTGEN_MEMBERS_AstNodeBiop; // Clone single node, just get same type back. virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) = 0; // METHODS @@ -94,7 +94,7 @@ protected: : AstNodeBiop{t, fl, lhs, rhs} {} public: - ASTGEN_MEMBERS_NodeBiCom; + ASTGEN_MEMBERS_AstNodeBiCom; }; class AstNodeBiComAsv VL_NOT_FINAL : public AstNodeBiCom { // Binary math with commutative & associative properties @@ -103,7 +103,7 @@ protected: : AstNodeBiCom{t, fl, lhs, rhs} {} public: - ASTGEN_MEMBERS_NodeBiComAsv; + ASTGEN_MEMBERS_AstNodeBiComAsv; }; class AstNodeSel VL_NOT_FINAL : public AstNodeBiop { // Single bit range extraction, perhaps with non-constant selection or array selection @@ -114,7 +114,7 @@ protected: : AstNodeBiop{t, fl, fromp, bitp} {} public: - ASTGEN_MEMBERS_NodeSel; + ASTGEN_MEMBERS_AstNodeSel; int bitConst() const; bool hasDType() const override { return true; } }; @@ -127,7 +127,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeStream; + ASTGEN_MEMBERS_AstNodeStream; }; class AstNodeSystemBiop VL_NOT_FINAL : public AstNodeBiop { public: @@ -135,7 +135,7 @@ public: : AstNodeBiop(t, fl, lhsp, rhsp) { dtypeSetDouble(); } - ASTGEN_MEMBERS_NodeSystemBiop; + ASTGEN_MEMBERS_AstNodeSystemBiop; bool cleanOut() const override { return false; } bool cleanLhs() const override { return false; } bool cleanRhs() const override { return false; } @@ -161,7 +161,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeQuadop; + ASTGEN_MEMBERS_AstNodeQuadop; // METHODS // Set out to evaluation of a AstConst'ed virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs, @@ -185,7 +185,7 @@ protected: : AstNodeMath{t, fl} {} public: - ASTGEN_MEMBERS_NodeTermop; + ASTGEN_MEMBERS_AstNodeTermop; // Know no children, and hot function, so skip iterator for speed // cppcheck-suppress functionConst void iterateChildren(VNVisitor& v) {} @@ -205,7 +205,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeTriop; + ASTGEN_MEMBERS_AstNodeTriop; // METHODS void dump(std::ostream& str) const override; // Set out to evaluation of a AstConst'ed @@ -236,7 +236,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeCond; + ASTGEN_MEMBERS_AstNodeCond; void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs, const V3Number& ths) override; string emitVerilog() override { return "%k(%l %f? %r %k: %t)"; } @@ -262,7 +262,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeUniop; + ASTGEN_MEMBERS_AstNodeUniop; // METHODS void dump(std::ostream& str) const override; // Set out to evaluation of a AstConst'ed lhs @@ -282,7 +282,7 @@ public: : AstNodeUniop(t, fl, lhsp) { dtypeSetDouble(); } - ASTGEN_MEMBERS_NodeSystemUniop; + ASTGEN_MEMBERS_AstNodeSystemUniop; bool cleanOut() const override { return true; } bool cleanLhs() const override { return false; } bool sizeMattersLhs() const override { return false; } @@ -315,7 +315,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeVarRef; + ASTGEN_MEMBERS_AstNodeVarRef; void dump(std::ostream& str) const override; bool hasDType() const override { return true; } const char* broken() const override; @@ -358,7 +358,7 @@ public: } public: - ASTGEN_MEMBERS_AddrOfCFunc; + ASTGEN_MEMBERS_AstAddrOfCFunc; void cloneRelink() override; const char* broken() const override; string emitVerilog() override { V3ERROR_NA_RETURN(""); } @@ -381,7 +381,7 @@ public: dtypeFrom(exprsp); } inline AstCMath(FileLine* fl, const string& textStmt, int setwidth, bool cleanOut = true); - ASTGEN_MEMBERS_CMath; + ASTGEN_MEMBERS_AstCMath; bool isGateOptimizable() const override { return m_pure; } bool isPredictOptimizable() const override { return m_pure; } bool cleanOut() const override { return m_cleanOut; } @@ -399,7 +399,7 @@ public: : ASTGEN_SUPER_ConsAssoc(fl) { this->defaultp(defaultp); } - ASTGEN_MEMBERS_ConsAssoc; + ASTGEN_MEMBERS_AstConsAssoc; string emitVerilog() override { return "'{}"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -417,7 +417,7 @@ public: this->lhsp(lhsp); this->rhsp(rhsp); } - ASTGEN_MEMBERS_ConsDynArray; + ASTGEN_MEMBERS_AstConsDynArray; string emitVerilog() override { return "'{%l, %r}"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -435,7 +435,7 @@ public: this->lhsp(lhsp); this->rhsp(rhsp); } - ASTGEN_MEMBERS_ConsQueue; + ASTGEN_MEMBERS_AstConsQueue; string emitVerilog() override { return "'{%l, %r}"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -451,7 +451,7 @@ public: : ASTGEN_SUPER_ConsWildcard(fl) { this->defaultp(defaultp); } - ASTGEN_MEMBERS_ConsWildcard; + ASTGEN_MEMBERS_AstConsWildcard; string emitVerilog() override { return "'{}"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -572,7 +572,7 @@ public: dtypeSetBit(); // Events 1 bit, objects 64 bits, so autoExtend=1 and use bit here initWithNumber(); } - ASTGEN_MEMBERS_Const; + ASTGEN_MEMBERS_AstConst; string name() const override { return num().ascii(); } // * = Value const V3Number& num() const { return m_num; } // * = Value V3Number& num() { return m_num; } // * = Value @@ -597,7 +597,7 @@ class AstEmptyQueue final : public AstNodeMath { public: explicit AstEmptyQueue(FileLine* fl) : ASTGEN_SUPER_EmptyQueue(fl) {} - ASTGEN_MEMBERS_EmptyQueue; + ASTGEN_MEMBERS_AstEmptyQueue; string emitC() override { V3ERROR_NA_RETURN(""); } string emitVerilog() override { return "{}"; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -614,7 +614,7 @@ public: , m_classOrPackagep{classOrPackagep} { dtypeFrom(m_itemp); } - ASTGEN_MEMBERS_EnumItemRef; + ASTGEN_MEMBERS_AstEnumItemRef; void dump(std::ostream& str) const override; string name() const override { return itemp()->name(); } int instrCount() const override { return 0; } @@ -646,7 +646,7 @@ public: this->resultp(resultp); dtypeFrom(resultp); } - ASTGEN_MEMBERS_ExprStmt; + ASTGEN_MEMBERS_AstExprStmt; // METHODS string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -662,7 +662,7 @@ public: this->filep(filep); this->strp(strp); } - ASTGEN_MEMBERS_FError; + ASTGEN_MEMBERS_AstFError; string emitVerilog() override { return "%f$ferror(%l, %r)"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } @@ -685,7 +685,7 @@ public: this->startp(startp); this->countp(countp); } - ASTGEN_MEMBERS_FRead; + ASTGEN_MEMBERS_AstFRead; string verilogKwd() const override { return "$fread"; } string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -704,7 +704,7 @@ public: : ASTGEN_SUPER_FRewind(fl) { this->filep(filep); } - ASTGEN_MEMBERS_FRewind; + ASTGEN_MEMBERS_AstFRewind; string verilogKwd() const override { return "$frewind"; } string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -729,7 +729,7 @@ public: addExprsp(exprsp); this->filep(filep); } - ASTGEN_MEMBERS_FScanF; + ASTGEN_MEMBERS_AstFScanF; string name() const override { return m_text; } string verilogKwd() const override { return "$fscanf"; } string emitVerilog() override { V3ERROR_NA_RETURN(""); } @@ -756,7 +756,7 @@ public: this->offset(offset); this->operation(operation); } - ASTGEN_MEMBERS_FSeek; + ASTGEN_MEMBERS_AstFSeek; string verilogKwd() const override { return "$fseek"; } string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -775,7 +775,7 @@ public: : ASTGEN_SUPER_FTell(fl) { this->filep(filep); } - ASTGEN_MEMBERS_FTell; + ASTGEN_MEMBERS_AstFTell; string verilogKwd() const override { return "$ftell"; } string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -796,7 +796,7 @@ public: : ASTGEN_SUPER_Fell(fl) { this->exprp(exprp); } - ASTGEN_MEMBERS_Fell; + ASTGEN_MEMBERS_AstFell; string emitVerilog() override { return "$fell(%l)"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -814,7 +814,7 @@ public: this->exprp(exprp); this->rangep(rangep); } - ASTGEN_MEMBERS_GatePin; + ASTGEN_MEMBERS_AstGatePin; string emitVerilog() override { return "%l"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } @@ -830,7 +830,7 @@ public: this->lhsp(lhsp); this->rhsp(rhsp); } - ASTGEN_MEMBERS_Implication; + ASTGEN_MEMBERS_AstImplication; string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -848,7 +848,7 @@ public: this->addItemsp(itemsp); dtypeSetBit(); } - ASTGEN_MEMBERS_Inside; + ASTGEN_MEMBERS_AstInside; string emitVerilog() override { return "%l inside { %r }"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return false; } // NA @@ -862,7 +862,7 @@ public: this->lhsp(lhsp); this->rhsp(rhsp); } - ASTGEN_MEMBERS_InsideRange; + ASTGEN_MEMBERS_AstInsideRange; string emitVerilog() override { return "[%l:%r]"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return false; } // NA @@ -882,7 +882,7 @@ public: : ASTGEN_SUPER_LambdaArgRef(fl) , m_name{name} , m_index(index) {} - ASTGEN_MEMBERS_LambdaArgRef; + ASTGEN_MEMBERS_AstLambdaArgRef; bool same(const AstNode* /*samep*/) const override { return true; } string emitVerilog() override { return name(); } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -913,7 +913,7 @@ public: this->fromp(fromp); dtypep(dtp); } - ASTGEN_MEMBERS_MemberSel; + ASTGEN_MEMBERS_AstMemberSel; void cloneRelink() override; const char* broken() const override; void dump(std::ostream& str) const override; @@ -936,7 +936,7 @@ public: dtypeFrom(rhsp); // otherwise V3Width will resolve this->rhsp(rhsp); } - ASTGEN_MEMBERS_NewCopy; + ASTGEN_MEMBERS_AstNewCopy; string emitVerilog() override { return "new"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } @@ -955,7 +955,7 @@ public: this->sizep(sizep); this->rhsp(rhsp); } - ASTGEN_MEMBERS_NewDynamic; + ASTGEN_MEMBERS_AstNewDynamic; string emitVerilog() override { return "new"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } @@ -973,7 +973,7 @@ public: this->exprp(exprp); this->ticksp(ticksp); } - ASTGEN_MEMBERS_Past; + ASTGEN_MEMBERS_AstPast; string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -999,7 +999,7 @@ public: this->keyp(keyp); this->repp(repp); } - ASTGEN_MEMBERS_PatMember; + ASTGEN_MEMBERS_AstPatMember; string emitVerilog() override { return lhssp() ? "%f{%r{%k%l}}" : "%l"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -1020,7 +1020,7 @@ public: : ASTGEN_SUPER_Pattern(fl) { addItemsp(itemsp); } - ASTGEN_MEMBERS_Pattern; + ASTGEN_MEMBERS_AstPattern; string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -1048,7 +1048,7 @@ public: , m_urandom{urandom} { this->seedp(seedp); } - ASTGEN_MEMBERS_Rand; + ASTGEN_MEMBERS_AstRand; string emitVerilog() override { return seedp() ? (m_urandom ? "%f$urandom(%l)" : "%f$random(%l)") : (m_urandom ? "%f$urandom()" : "%f$random()"); @@ -1081,7 +1081,7 @@ public: : ASTGEN_SUPER_Rose(fl) { this->exprp(exprp); } - ASTGEN_MEMBERS_Rose; + ASTGEN_MEMBERS_AstRose; string emitVerilog() override { return "$rose(%l)"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -1102,7 +1102,7 @@ public: this->addExprsp(exprsp); this->fromp(fromp); } - ASTGEN_MEMBERS_SScanF; + ASTGEN_MEMBERS_AstSScanF; string name() const override { return m_text; } string verilogKwd() const override { return "$sscanf"; } string emitVerilog() override { V3ERROR_NA_RETURN(""); } @@ -1126,7 +1126,7 @@ public: : ASTGEN_SUPER_Sampled(fl) { this->exprp(exprp); } - ASTGEN_MEMBERS_Sampled; + ASTGEN_MEMBERS_AstSampled; string emitVerilog() override { return "$sampled(%l)"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -1152,7 +1152,7 @@ public: , m_forFormat{forFormat} { dtypeSetUInt64(); } - ASTGEN_MEMBERS_ScopeName; + ASTGEN_MEMBERS_AstScopeName; bool same(const AstNode* samep) const override { return (m_dpiExport == static_cast(samep)->m_dpiExport && m_forFormat == static_cast(samep)->m_forFormat); @@ -1190,7 +1190,7 @@ public: this->keyp(keyp); this->valuep(valuep); } - ASTGEN_MEMBERS_SetAssoc; + ASTGEN_MEMBERS_AstSetAssoc; string emitVerilog() override { return "'{}"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -1211,7 +1211,7 @@ public: this->keyp(keyp); this->valuep(valuep); } - ASTGEN_MEMBERS_SetWildcard; + ASTGEN_MEMBERS_AstSetWildcard; string emitVerilog() override { return "'{}"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -1228,7 +1228,7 @@ public: : ASTGEN_SUPER_Stable(fl) { this->exprp(exprp); } - ASTGEN_MEMBERS_Stable; + ASTGEN_MEMBERS_AstStable; string emitVerilog() override { return "$stable(%l)"; } string emitC() override { V3ERROR_NA_RETURN(""); } string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } @@ -1244,7 +1244,7 @@ public: : ASTGEN_SUPER_SystemF(fl) { this->lhsp(lhsp); } - ASTGEN_MEMBERS_SystemF; + ASTGEN_MEMBERS_AstSystemF; string verilogKwd() const override { return "$system"; } string emitVerilog() override { return verilogKwd(); } string emitC() override { return "VL_SYSTEM_%nq(%lw, %P)"; } @@ -1264,7 +1264,7 @@ public: : ASTGEN_SUPER_TestPlusArgs(fl) { this->searchp(searchp); } - ASTGEN_MEMBERS_TestPlusArgs; + ASTGEN_MEMBERS_AstTestPlusArgs; string verilogKwd() const override { return "$test$plusargs"; } string emitVerilog() override { return verilogKwd(); } string emitC() override { return "VL_VALUEPLUSARGS_%nq(%lw, %P, nullptr)"; } @@ -1282,7 +1282,7 @@ public: : ASTGEN_SUPER_UCFunc(fl) { this->addExprsp(exprsp); } - ASTGEN_MEMBERS_UCFunc; + ASTGEN_MEMBERS_AstUCFunc; bool cleanOut() const override { return false; } string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -1302,7 +1302,7 @@ public: : ASTGEN_SUPER_Unbounded(fl) { dtypeSetSigned32(); } - ASTGEN_MEMBERS_Unbounded; + ASTGEN_MEMBERS_AstUnbounded; string emitVerilog() override { return "$"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } @@ -1317,7 +1317,7 @@ public: this->searchp(searchp); this->outp(outp); } - ASTGEN_MEMBERS_ValuePlusArgs; + ASTGEN_MEMBERS_AstValuePlusArgs; string verilogKwd() const override { return "$value$plusargs"; } string emitVerilog() override { return "%f$value$plusargs(%l, %k%r)"; } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -1338,7 +1338,7 @@ public: : ASTGEN_SUPER_BufIf1(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_BufIf1; + ASTGEN_MEMBERS_AstBufIf1; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstBufIf1(this->fileline(), lhsp, rhsp); } @@ -1365,7 +1365,7 @@ class AstCastDynamic final : public AstNodeBiop { public: AstCastDynamic(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_CastDynamic(fl, lhsp, rhsp) {} - ASTGEN_MEMBERS_CastDynamic; + ASTGEN_MEMBERS_AstCastDynamic; void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) override { V3ERROR_NA; } @@ -1394,7 +1394,7 @@ public: , m_ignoreCase{ignoreCase} { dtypeSetUInt32(); } - ASTGEN_MEMBERS_CompareNN; + ASTGEN_MEMBERS_AstCompareNN; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstCompareNN(this->fileline(), lhsp, rhsp, m_ignoreCase); } @@ -1425,7 +1425,7 @@ public: VSigning::UNSIGNED); } } - ASTGEN_MEMBERS_Concat; + ASTGEN_MEMBERS_AstConcat; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstConcat(this->fileline(), lhsp, rhsp); } @@ -1448,7 +1448,7 @@ public: : ASTGEN_SUPER_ConcatN(fl, lhsp, rhsp) { dtypeSetString(); } - ASTGEN_MEMBERS_ConcatN; + ASTGEN_MEMBERS_AstConcatN; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstConcatN(this->fileline(), lhsp, rhsp); } @@ -1471,7 +1471,7 @@ public: : ASTGEN_SUPER_Div(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_Div; + ASTGEN_MEMBERS_AstDiv; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstDiv(this->fileline(), lhsp, rhsp); } @@ -1493,7 +1493,7 @@ public: : ASTGEN_SUPER_DivD(fl, lhsp, rhsp) { dtypeSetDouble(); } - ASTGEN_MEMBERS_DivD; + ASTGEN_MEMBERS_AstDivD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstDivD(this->fileline(), lhsp, rhsp); } @@ -1517,7 +1517,7 @@ public: : ASTGEN_SUPER_DivS(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_DivS; + ASTGEN_MEMBERS_AstDivS; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstDivS(this->fileline(), lhsp, rhsp); } @@ -1541,7 +1541,7 @@ public: : ASTGEN_SUPER_EqWild(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_EqWild; + ASTGEN_MEMBERS_AstEqWild; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstEqWild(this->fileline(), lhsp, rhsp); } @@ -1563,7 +1563,7 @@ class AstFGetS final : public AstNodeBiop { public: AstFGetS(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_FGetS(fl, lhsp, rhsp) {} - ASTGEN_MEMBERS_FGetS; + ASTGEN_MEMBERS_AstFGetS; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstFGetS(this->fileline(), lhsp, rhsp); } @@ -1588,7 +1588,7 @@ class AstFUngetC final : public AstNodeBiop { public: AstFUngetC(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_FUngetC(fl, lhsp, rhsp) {} - ASTGEN_MEMBERS_FUngetC; + ASTGEN_MEMBERS_AstFUngetC; void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) override { V3ERROR_NA; } @@ -1617,7 +1617,7 @@ public: : ASTGEN_SUPER_GetcN(fl, lhsp, rhsp) { dtypeSetBitSized(8, VSigning::UNSIGNED); } - ASTGEN_MEMBERS_GetcN; + ASTGEN_MEMBERS_AstGetcN; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstGetcN(this->fileline(), lhsp, rhsp); } @@ -1642,7 +1642,7 @@ public: : ASTGEN_SUPER_GetcRefN(fl, lhsp, rhsp) { dtypeSetBitSized(8, VSigning::UNSIGNED); } - ASTGEN_MEMBERS_GetcRefN; + ASTGEN_MEMBERS_AstGetcRefN; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstGetcRefN(this->fileline(), lhsp, rhsp); } @@ -1664,7 +1664,7 @@ public: : ASTGEN_SUPER_Gt(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_Gt; + ASTGEN_MEMBERS_AstGt; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstGt(this->fileline(), lhsp, rhsp); } @@ -1686,7 +1686,7 @@ public: : ASTGEN_SUPER_GtD(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_GtD; + ASTGEN_MEMBERS_AstGtD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstGtD(this->fileline(), lhsp, rhsp); } @@ -1710,7 +1710,7 @@ public: : ASTGEN_SUPER_GtN(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_GtN; + ASTGEN_MEMBERS_AstGtN; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstGtN(this->fileline(), lhsp, rhsp); } @@ -1734,7 +1734,7 @@ public: : ASTGEN_SUPER_GtS(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_GtS; + ASTGEN_MEMBERS_AstGtS; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstGtS(this->fileline(), lhsp, rhsp); } @@ -1757,7 +1757,7 @@ public: : ASTGEN_SUPER_Gte(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_Gte; + ASTGEN_MEMBERS_AstGte; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstGte(this->fileline(), lhsp, rhsp); } @@ -1779,7 +1779,7 @@ public: : ASTGEN_SUPER_GteD(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_GteD; + ASTGEN_MEMBERS_AstGteD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstGteD(this->fileline(), lhsp, rhsp); } @@ -1803,7 +1803,7 @@ public: : ASTGEN_SUPER_GteN(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_GteN; + ASTGEN_MEMBERS_AstGteN; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstGteN(this->fileline(), lhsp, rhsp); } @@ -1827,7 +1827,7 @@ public: : ASTGEN_SUPER_GteS(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_GteS; + ASTGEN_MEMBERS_AstGteS; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstGteS(this->fileline(), lhsp, rhsp); } @@ -1850,7 +1850,7 @@ public: : ASTGEN_SUPER_LogAnd(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_LogAnd; + ASTGEN_MEMBERS_AstLogAnd; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLogAnd(this->fileline(), lhsp, rhsp); } @@ -1873,7 +1873,7 @@ public: : ASTGEN_SUPER_LogIf(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_LogIf; + ASTGEN_MEMBERS_AstLogIf; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLogIf(this->fileline(), lhsp, rhsp); } @@ -1901,7 +1901,7 @@ public: : ASTGEN_SUPER_LogOr(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_LogOr; + ASTGEN_MEMBERS_AstLogOr; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLogOr(this->fileline(), lhsp, rhsp); } @@ -1932,7 +1932,7 @@ public: : ASTGEN_SUPER_Lt(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_Lt; + ASTGEN_MEMBERS_AstLt; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLt(this->fileline(), lhsp, rhsp); } @@ -1954,7 +1954,7 @@ public: : ASTGEN_SUPER_LtD(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_LtD; + ASTGEN_MEMBERS_AstLtD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLtD(this->fileline(), lhsp, rhsp); } @@ -1978,7 +1978,7 @@ public: : ASTGEN_SUPER_LtN(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_LtN; + ASTGEN_MEMBERS_AstLtN; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLtN(this->fileline(), lhsp, rhsp); } @@ -2002,7 +2002,7 @@ public: : ASTGEN_SUPER_LtS(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_LtS; + ASTGEN_MEMBERS_AstLtS; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLtS(this->fileline(), lhsp, rhsp); } @@ -2025,7 +2025,7 @@ public: : ASTGEN_SUPER_Lte(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_Lte; + ASTGEN_MEMBERS_AstLte; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLte(this->fileline(), lhsp, rhsp); } @@ -2047,7 +2047,7 @@ public: : ASTGEN_SUPER_LteD(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_LteD; + ASTGEN_MEMBERS_AstLteD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLteD(this->fileline(), lhsp, rhsp); } @@ -2071,7 +2071,7 @@ public: : ASTGEN_SUPER_LteN(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_LteN; + ASTGEN_MEMBERS_AstLteN; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLteN(this->fileline(), lhsp, rhsp); } @@ -2095,7 +2095,7 @@ public: : ASTGEN_SUPER_LteS(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_LteS; + ASTGEN_MEMBERS_AstLteS; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLteS(this->fileline(), lhsp, rhsp); } @@ -2118,7 +2118,7 @@ public: : ASTGEN_SUPER_ModDiv(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_ModDiv; + ASTGEN_MEMBERS_AstModDiv; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstModDiv(this->fileline(), lhsp, rhsp); } @@ -2140,7 +2140,7 @@ public: : ASTGEN_SUPER_ModDivS(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_ModDivS; + ASTGEN_MEMBERS_AstModDivS; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstModDivS(this->fileline(), lhsp, rhsp); } @@ -2163,7 +2163,7 @@ public: : ASTGEN_SUPER_NeqWild(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_NeqWild; + ASTGEN_MEMBERS_AstNeqWild; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstNeqWild(this->fileline(), lhsp, rhsp); } @@ -2185,7 +2185,7 @@ public: : ASTGEN_SUPER_Pow(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_Pow; + ASTGEN_MEMBERS_AstPow; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstPow(this->fileline(), lhsp, rhsp); } @@ -2208,7 +2208,7 @@ public: : ASTGEN_SUPER_PowD(fl, lhsp, rhsp) { dtypeSetDouble(); } - ASTGEN_MEMBERS_PowD; + ASTGEN_MEMBERS_AstPowD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstPowD(this->fileline(), lhsp, rhsp); } @@ -2231,7 +2231,7 @@ public: : ASTGEN_SUPER_PowSS(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_PowSS; + ASTGEN_MEMBERS_AstPowSS; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstPowSS(this->fileline(), lhsp, rhsp); } @@ -2255,7 +2255,7 @@ public: : ASTGEN_SUPER_PowSU(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_PowSU; + ASTGEN_MEMBERS_AstPowSU; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstPowSU(this->fileline(), lhsp, rhsp); } @@ -2279,7 +2279,7 @@ public: : ASTGEN_SUPER_PowUS(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_PowUS; + ASTGEN_MEMBERS_AstPowUS; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstPowUS(this->fileline(), lhsp, rhsp); } @@ -2313,7 +2313,7 @@ public: } AstReplicate(FileLine* fl, AstNode* lhsp, uint32_t repCount) : AstReplicate(fl, lhsp, new AstConst(fl, repCount)) {} - ASTGEN_MEMBERS_Replicate; + ASTGEN_MEMBERS_AstReplicate; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstReplicate(this->fileline(), lhsp, rhsp); } @@ -2338,7 +2338,7 @@ public: } AstReplicateN(FileLine* fl, AstNode* lhsp, uint32_t repCount) : AstReplicateN(fl, lhsp, new AstConst(fl, repCount)) {} - ASTGEN_MEMBERS_ReplicateN; + ASTGEN_MEMBERS_AstReplicateN; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstReplicateN(this->fileline(), lhsp, rhsp); } @@ -2361,7 +2361,7 @@ public: : ASTGEN_SUPER_ShiftL(fl, lhsp, rhsp) { if (setwidth) dtypeSetLogicSized(setwidth, VSigning::UNSIGNED); } - ASTGEN_MEMBERS_ShiftL; + ASTGEN_MEMBERS_AstShiftL; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstShiftL(this->fileline(), lhsp, rhsp); } @@ -2385,7 +2385,7 @@ public: : ASTGEN_SUPER_ShiftR(fl, lhsp, rhsp) { if (setwidth) dtypeSetLogicSized(setwidth, VSigning::UNSIGNED); } - ASTGEN_MEMBERS_ShiftR; + ASTGEN_MEMBERS_AstShiftR; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstShiftR(this->fileline(), lhsp, rhsp); } @@ -2413,7 +2413,7 @@ public: // Important that widthMin be correct, as opExtend requires it after V3Expand if (setwidth) dtypeSetLogicSized(setwidth, VSigning::SIGNED); } - ASTGEN_MEMBERS_ShiftRS; + ASTGEN_MEMBERS_AstShiftRS; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstShiftRS(this->fileline(), lhsp, rhsp); } @@ -2436,7 +2436,7 @@ public: : ASTGEN_SUPER_Sub(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_Sub; + ASTGEN_MEMBERS_AstSub; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstSub(this->fileline(), lhsp, rhsp); } @@ -2458,7 +2458,7 @@ public: : ASTGEN_SUPER_SubD(fl, lhsp, rhsp) { dtypeSetDouble(); } - ASTGEN_MEMBERS_SubD; + ASTGEN_MEMBERS_AstSubD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstSubD(this->fileline(), lhsp, rhsp); } @@ -2483,7 +2483,7 @@ public: : ASTGEN_SUPER_URandomRange(fl, lhsp, rhsp) { dtypeSetUInt32(); // Says IEEE } - ASTGEN_MEMBERS_URandomRange; + ASTGEN_MEMBERS_AstURandomRange; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstURandomRange(fileline(), lhsp, rhsp); } @@ -2509,7 +2509,7 @@ public: : ASTGEN_SUPER_Eq(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_Eq; + ASTGEN_MEMBERS_AstEq; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstEq(this->fileline(), lhsp, rhsp); } @@ -2533,7 +2533,7 @@ public: : ASTGEN_SUPER_EqCase(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_EqCase; + ASTGEN_MEMBERS_AstEqCase; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstEqCase(this->fileline(), lhsp, rhsp); } @@ -2555,7 +2555,7 @@ public: : ASTGEN_SUPER_EqD(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_EqD; + ASTGEN_MEMBERS_AstEqD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstEqD(this->fileline(), lhsp, rhsp); } @@ -2579,7 +2579,7 @@ public: : ASTGEN_SUPER_EqN(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_EqN; + ASTGEN_MEMBERS_AstEqN; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstEqN(this->fileline(), lhsp, rhsp); } @@ -2603,7 +2603,7 @@ public: : ASTGEN_SUPER_LogEq(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_LogEq; + ASTGEN_MEMBERS_AstLogEq; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstLogEq(this->fileline(), lhsp, rhsp); } @@ -2626,7 +2626,7 @@ public: : ASTGEN_SUPER_Neq(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_Neq; + ASTGEN_MEMBERS_AstNeq; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstNeq(this->fileline(), lhsp, rhsp); } @@ -2648,7 +2648,7 @@ public: : ASTGEN_SUPER_NeqCase(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_NeqCase; + ASTGEN_MEMBERS_AstNeqCase; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstNeqCase(this->fileline(), lhsp, rhsp); } @@ -2670,7 +2670,7 @@ public: : ASTGEN_SUPER_NeqD(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_NeqD; + ASTGEN_MEMBERS_AstNeqD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstNeqD(this->fileline(), lhsp, rhsp); } @@ -2694,7 +2694,7 @@ public: : ASTGEN_SUPER_NeqN(fl, lhsp, rhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_NeqN; + ASTGEN_MEMBERS_AstNeqN; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstNeqN(this->fileline(), lhsp, rhsp); } @@ -2720,7 +2720,7 @@ public: : ASTGEN_SUPER_Add(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_Add; + ASTGEN_MEMBERS_AstAdd; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstAdd(this->fileline(), lhsp, rhsp); } @@ -2742,7 +2742,7 @@ public: : ASTGEN_SUPER_AddD(fl, lhsp, rhsp) { dtypeSetDouble(); } - ASTGEN_MEMBERS_AddD; + ASTGEN_MEMBERS_AstAddD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstAddD(this->fileline(), lhsp, rhsp); } @@ -2766,7 +2766,7 @@ public: : ASTGEN_SUPER_And(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_And; + ASTGEN_MEMBERS_AstAnd; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstAnd(this->fileline(), lhsp, rhsp); } @@ -2788,7 +2788,7 @@ public: : ASTGEN_SUPER_Mul(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_Mul; + ASTGEN_MEMBERS_AstMul; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstMul(this->fileline(), lhsp, rhsp); } @@ -2811,7 +2811,7 @@ public: : ASTGEN_SUPER_MulD(fl, lhsp, rhsp) { dtypeSetDouble(); } - ASTGEN_MEMBERS_MulD; + ASTGEN_MEMBERS_AstMulD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstMulD(this->fileline(), lhsp, rhsp); } @@ -2835,7 +2835,7 @@ public: : ASTGEN_SUPER_MulS(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_MulS; + ASTGEN_MEMBERS_AstMulS; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstMulS(this->fileline(), lhsp, rhsp); } @@ -2860,7 +2860,7 @@ public: : ASTGEN_SUPER_Or(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_Or; + ASTGEN_MEMBERS_AstOr; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstOr(this->fileline(), lhsp, rhsp); } @@ -2882,7 +2882,7 @@ public: : ASTGEN_SUPER_Xor(fl, lhsp, rhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_Xor; + ASTGEN_MEMBERS_AstXor; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstXor(this->fileline(), lhsp, rhsp); } @@ -2920,7 +2920,7 @@ public: : ASTGEN_SUPER_ArraySel(fl, fromp, new AstConst(fl, bit)) { init(fromp); } - ASTGEN_MEMBERS_ArraySel; + ASTGEN_MEMBERS_AstArraySel; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstArraySel(this->fileline(), lhsp, rhsp); } @@ -2958,7 +2958,7 @@ public: : ASTGEN_SUPER_AssocSel(fl, fromp, bitp) { init(fromp); } - ASTGEN_MEMBERS_AssocSel; + ASTGEN_MEMBERS_AstAssocSel; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstAssocSel(this->fileline(), lhsp, rhsp); } @@ -2993,7 +2993,7 @@ public: : ASTGEN_SUPER_WildcardSel(fl, fromp, bitp) { init(fromp); } - ASTGEN_MEMBERS_WildcardSel; + ASTGEN_MEMBERS_AstWildcardSel; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstWildcardSel{this->fileline(), lhsp, rhsp}; } @@ -3019,7 +3019,7 @@ public: : ASTGEN_SUPER_WordSel(fl, fromp, bitp) { dtypeSetUInt32(); // Always used on WData arrays so returns edata size } - ASTGEN_MEMBERS_WordSel; + ASTGEN_MEMBERS_AstWordSel; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstWordSel(this->fileline(), lhsp, rhsp); } @@ -3044,7 +3044,7 @@ class AstStreamL final : public AstNodeStream { public: AstStreamL(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_StreamL(fl, lhsp, rhsp) {} - ASTGEN_MEMBERS_StreamL; + ASTGEN_MEMBERS_AstStreamL; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstStreamL(this->fileline(), lhsp, rhsp); } @@ -3065,7 +3065,7 @@ class AstStreamR final : public AstNodeStream { public: AstStreamR(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_StreamR(fl, lhsp, rhsp) {} - ASTGEN_MEMBERS_StreamR; + ASTGEN_MEMBERS_AstStreamR; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstStreamR(this->fileline(), lhsp, rhsp); } @@ -3087,7 +3087,7 @@ class AstAtan2D final : public AstNodeSystemBiop { public: AstAtan2D(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_Atan2D(fl, lhsp, rhsp) {} - ASTGEN_MEMBERS_Atan2D; + ASTGEN_MEMBERS_AstAtan2D; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstAtan2D(this->fileline(), lhsp, rhsp); } @@ -3101,7 +3101,7 @@ class AstHypotD final : public AstNodeSystemBiop { public: AstHypotD(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_HypotD(fl, lhsp, rhsp) {} - ASTGEN_MEMBERS_HypotD; + ASTGEN_MEMBERS_AstHypotD; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstHypotD(this->fileline(), lhsp, rhsp); } @@ -3123,7 +3123,7 @@ public: : ASTGEN_SUPER_CountBits(fl, exprp, ctrl1p, ctrl2p, ctrl2p->cloneTree(false)) {} AstCountBits(FileLine* fl, AstNode* exprp, AstNode* ctrl1p, AstNode* ctrl2p, AstNode* ctrl3p) : ASTGEN_SUPER_CountBits(fl, exprp, ctrl1p, ctrl2p, ctrl3p) {} - ASTGEN_MEMBERS_CountBits; + ASTGEN_MEMBERS_AstCountBits; void numberOperate(V3Number& out, const V3Number& expr, const V3Number& ctrl1, const V3Number& ctrl2, const V3Number& ctrl3) override { out.opCountBits(expr, ctrl1, ctrl2, ctrl3); @@ -3151,7 +3151,7 @@ public: , m_timeunit{timeunit} { dtypeSetUInt64(); } - ASTGEN_MEMBERS_Time; + ASTGEN_MEMBERS_AstTime; string emitVerilog() override { return "%f$time"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } @@ -3171,7 +3171,7 @@ public: , m_timeunit{timeunit} { dtypeSetDouble(); } - ASTGEN_MEMBERS_TimeD; + ASTGEN_MEMBERS_AstTimeD; string emitVerilog() override { return "%f$realtime"; } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } @@ -3194,7 +3194,7 @@ class AstPostAdd final : public AstNodeTriop { public: AstPostAdd(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* thsp) : ASTGEN_SUPER_PostAdd(fl, lhsp, rhsp, thsp) {} - ASTGEN_MEMBERS_PostAdd; + ASTGEN_MEMBERS_AstPostAdd; void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs, const V3Number& ths) override { V3ERROR_NA; // Need to modify lhs @@ -3219,7 +3219,7 @@ class AstPostSub final : public AstNodeTriop { public: AstPostSub(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* thsp) : ASTGEN_SUPER_PostSub(fl, lhsp, rhsp, thsp) {} - ASTGEN_MEMBERS_PostSub; + ASTGEN_MEMBERS_AstPostSub; void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs, const V3Number& ths) override { V3ERROR_NA; // Need to modify lhs @@ -3244,7 +3244,7 @@ class AstPreAdd final : public AstNodeTriop { public: AstPreAdd(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* thsp) : ASTGEN_SUPER_PreAdd(fl, lhsp, rhsp, thsp) {} - ASTGEN_MEMBERS_PreAdd; + ASTGEN_MEMBERS_AstPreAdd; void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs, const V3Number& ths) override { V3ERROR_NA; // Need to modify lhs @@ -3269,7 +3269,7 @@ class AstPreSub final : public AstNodeTriop { public: AstPreSub(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* thsp) : ASTGEN_SUPER_PreSub(fl, lhsp, rhsp, thsp) {} - ASTGEN_MEMBERS_PreSub; + ASTGEN_MEMBERS_AstPreSub; void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs, const V3Number& ths) override { V3ERROR_NA; // Need to modify lhs @@ -3292,7 +3292,7 @@ public: : ASTGEN_SUPER_PutcN(fl, lhsp, rhsp, ths) { dtypeSetString(); } - ASTGEN_MEMBERS_PutcN; + ASTGEN_MEMBERS_AstPutcN; void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs, const V3Number& ths) override { out.opPutcN(lhs, rhs, ths); @@ -3332,7 +3332,7 @@ public: , m_declElWidth{1} { dtypeSetLogicSized(bitwidth, VSigning::UNSIGNED); } - ASTGEN_MEMBERS_Sel; + ASTGEN_MEMBERS_AstSel; void dump(std::ostream& str) const override; void numberOperate(V3Number& out, const V3Number& from, const V3Number& bit, const V3Number& width) override { @@ -3373,7 +3373,7 @@ public: : ASTGEN_SUPER_SliceSel(fl, fromp, new AstConst(fl, declRange.lo()), new AstConst(fl, declRange.elements())) , m_declRange{declRange} {} - ASTGEN_MEMBERS_SliceSel; + ASTGEN_MEMBERS_AstSliceSel; void dump(std::ostream& str) const override; void numberOperate(V3Number& out, const V3Number& from, const V3Number& lo, const V3Number& width) override { @@ -3402,7 +3402,7 @@ public: : ASTGEN_SUPER_SubstrN(fl, lhsp, rhsp, ths) { dtypeSetString(); } - ASTGEN_MEMBERS_SubstrN; + ASTGEN_MEMBERS_AstSubstrN; void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs, const V3Number& ths) override { out.opSubstrN(lhs, rhs, ths); @@ -3428,7 +3428,7 @@ class AstCond final : public AstNodeCond { public: AstCond(FileLine* fl, AstNode* condp, AstNode* thenp, AstNode* elsep) : ASTGEN_SUPER_Cond(fl, condp, thenp, elsep) {} - ASTGEN_MEMBERS_Cond; + ASTGEN_MEMBERS_AstCond; AstNode* cloneType(AstNode* condp, AstNode* thenp, AstNode* elsep) override { return new AstCond(this->fileline(), condp, thenp, elsep); } @@ -3440,7 +3440,7 @@ class AstCondBound final : public AstNodeCond { public: AstCondBound(FileLine* fl, AstNode* condp, AstNode* thenp, AstNode* elsep) : ASTGEN_SUPER_CondBound(fl, condp, thenp, elsep) {} - ASTGEN_MEMBERS_CondBound; + ASTGEN_MEMBERS_AstCondBound; AstNode* cloneType(AstNode* condp, AstNode* thenp, AstNode* elsep) override { return new AstCondBound(this->fileline(), condp, thenp, elsep); } @@ -3460,7 +3460,7 @@ public: , m_fmt{fmt} { fmt == ATOREAL ? dtypeSetDouble() : dtypeSetSigned32(); } - ASTGEN_MEMBERS_AtoN; + ASTGEN_MEMBERS_AstAtoN; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opAtoN(lhs, m_fmt); } string name() const override { switch (m_fmt) { @@ -3494,7 +3494,7 @@ public: : ASTGEN_SUPER_BitsToRealD(fl, lhsp) { dtypeSetDouble(); } - ASTGEN_MEMBERS_BitsToRealD; + ASTGEN_MEMBERS_AstBitsToRealD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opBitsToRealD(lhs); } string emitVerilog() override { return "%f$bitstoreal(%l)"; } string emitC() override { return "VL_CVT_D_Q(%li)"; } @@ -3522,7 +3522,7 @@ public: dtypeFrom(typeFromp); m_size = width(); } - ASTGEN_MEMBERS_CCast; + ASTGEN_MEMBERS_AstCCast; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opAssign(lhs); } string emitVerilog() override { return "%f$_CAST(%l)"; } string emitC() override { return "VL_CAST_%nq%lq(%nw,%lw, %P, %li)"; } @@ -3542,7 +3542,7 @@ public: : ASTGEN_SUPER_CLog2(fl, lhsp) { dtypeSetSigned32(); } - ASTGEN_MEMBERS_CLog2; + ASTGEN_MEMBERS_AstCLog2; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opCLog2(lhs); } string emitVerilog() override { return "%f$clog2(%l)"; } string emitC() override { return "VL_CLOG2_%lq(%lW, %P, %li)"; } @@ -3556,7 +3556,7 @@ class AstCountOnes final : public AstNodeUniop { public: AstCountOnes(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_CountOnes(fl, lhsp) {} - ASTGEN_MEMBERS_CountOnes; + ASTGEN_MEMBERS_AstCountOnes; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opCountOnes(lhs); } string emitVerilog() override { return "%f$countones(%l)"; } string emitC() override { return "VL_COUNTONES_%lq(%lW, %P, %li)"; } @@ -3572,7 +3572,7 @@ public: : ASTGEN_SUPER_CvtPackString(fl, lhsp) { dtypeSetString(); } - ASTGEN_MEMBERS_CvtPackString; + ASTGEN_MEMBERS_AstCvtPackString; void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; } string emitVerilog() override { return "%f$_CAST(%l)"; } string emitC() override { return "VL_CVT_PACK_STR_N%lq(%lW, %li)"; } @@ -3590,7 +3590,7 @@ public: : ASTGEN_SUPER_Extend(fl, lhsp) { dtypeSetLogicSized(width, VSigning::UNSIGNED); } - ASTGEN_MEMBERS_Extend; + ASTGEN_MEMBERS_AstExtend; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opAssign(lhs); } string emitVerilog() override { return "%l"; } string emitC() override { return "VL_EXTEND_%nq%lq(%nw,%lw, %P, %li)"; } @@ -3611,7 +3611,7 @@ public: : ASTGEN_SUPER_ExtendS(fl, lhsp) { dtypeSetLogicSized(width, VSigning::UNSIGNED); } - ASTGEN_MEMBERS_ExtendS; + ASTGEN_MEMBERS_AstExtendS; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opExtendS(lhs, lhsp()->widthMinV()); } @@ -3629,7 +3629,7 @@ class AstFEof final : public AstNodeUniop { public: AstFEof(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_FEof(fl, lhsp) {} - ASTGEN_MEMBERS_FEof; + ASTGEN_MEMBERS_AstFEof; void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; } string emitVerilog() override { return "%f$feof(%l)"; } string emitC() override { return "(%li ? feof(VL_CVT_I_FP(%li)) : true)"; } @@ -3644,7 +3644,7 @@ class AstFGetC final : public AstNodeUniop { public: AstFGetC(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_FGetC(fl, lhsp) {} - ASTGEN_MEMBERS_FGetC; + ASTGEN_MEMBERS_AstFGetC; void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; } string emitVerilog() override { return "%f$fgetc(%l)"; } // Non-existent filehandle returns EOF @@ -3663,7 +3663,7 @@ public: : ASTGEN_SUPER_ISToRD(fl, lhsp) { dtypeSetDouble(); } - ASTGEN_MEMBERS_ISToRD; + ASTGEN_MEMBERS_AstISToRD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opISToRD(lhs); } string emitVerilog() override { return "%f$itor($signed(%l))"; } string emitC() override { return "VL_ISTOR_D_%lq(%lw, %li)"; } @@ -3680,7 +3680,7 @@ public: : ASTGEN_SUPER_IToRD(fl, lhsp) { dtypeSetDouble(); } - ASTGEN_MEMBERS_IToRD; + ASTGEN_MEMBERS_AstIToRD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opIToRD(lhs); } string emitVerilog() override { return "%f$itor(%l)"; } string emitC() override { return "VL_ITOR_D_%lq(%lw, %li)"; } @@ -3696,7 +3696,7 @@ public: : ASTGEN_SUPER_IsUnbounded(fl, lhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_IsUnbounded; + ASTGEN_MEMBERS_AstIsUnbounded; void numberOperate(V3Number& out, const V3Number&) override { // Any constant isn't unbounded out.setZero(); @@ -3714,7 +3714,7 @@ public: : ASTGEN_SUPER_IsUnknown(fl, lhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_IsUnknown; + ASTGEN_MEMBERS_AstIsUnknown; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opIsUnknown(lhs); } string emitVerilog() override { return "%f$isunknown(%l)"; } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -3729,7 +3729,7 @@ public: : ASTGEN_SUPER_LenN(fl, lhsp) { dtypeSetSigned32(); } - ASTGEN_MEMBERS_LenN; + ASTGEN_MEMBERS_AstLenN; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opLenN(lhs); } string emitVerilog() override { return "%f(%l)"; } string emitC() override { return "VL_LEN_IN(%li)"; } @@ -3743,7 +3743,7 @@ public: : ASTGEN_SUPER_LogNot(fl, lhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_LogNot; + ASTGEN_MEMBERS_AstLogNot; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opLogNot(lhs); } string emitVerilog() override { return "%f(! %l)"; } string emitC() override { return "VL_LOGNOT_%nq%lq(%nw,%lw, %P, %li)"; } @@ -3758,7 +3758,7 @@ public: : ASTGEN_SUPER_Negate(fl, lhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_Negate; + ASTGEN_MEMBERS_AstNegate; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opNegate(lhs); } string emitVerilog() override { return "%f(- %l)"; } string emitC() override { return "VL_NEGATE_%lq(%lW, %P, %li)"; } @@ -3773,7 +3773,7 @@ public: : ASTGEN_SUPER_NegateD(fl, lhsp) { dtypeSetDouble(); } - ASTGEN_MEMBERS_NegateD; + ASTGEN_MEMBERS_AstNegateD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opNegateD(lhs); } string emitVerilog() override { return "%f(- %l)"; } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -3790,7 +3790,7 @@ public: : ASTGEN_SUPER_Not(fl, lhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_Not; + ASTGEN_MEMBERS_AstNot; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opNot(lhs); } string emitVerilog() override { return "%f(~ %l)"; } string emitC() override { return "VL_NOT_%lq(%lW, %P, %li)"; } @@ -3807,7 +3807,7 @@ public: : ASTGEN_SUPER_NullCheck(fl, lhsp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_NullCheck; + ASTGEN_MEMBERS_AstNullCheck; void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; } int instrCount() const override { return 1; } // Rarely executes string emitVerilog() override { return "%l"; } @@ -3825,7 +3825,7 @@ public: : ASTGEN_SUPER_OneHot(fl, lhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_OneHot; + ASTGEN_MEMBERS_AstOneHot; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opOneHot(lhs); } string emitVerilog() override { return "%f$onehot(%l)"; } string emitC() override { return "VL_ONEHOT_%lq(%lW, %P, %li)"; } @@ -3841,7 +3841,7 @@ public: : ASTGEN_SUPER_OneHot0(fl, lhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_OneHot0; + ASTGEN_MEMBERS_AstOneHot0; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opOneHot0(lhs); } string emitVerilog() override { return "%f$onehot0(%l)"; } string emitC() override { return "VL_ONEHOT0_%lq(%lW, %P, %li)"; } @@ -3857,7 +3857,7 @@ public: : ASTGEN_SUPER_RToIRoundS(fl, lhsp) { dtypeSetSigned32(); } - ASTGEN_MEMBERS_RToIRoundS; + ASTGEN_MEMBERS_AstRToIRoundS; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRToIRoundS(lhs); } string emitVerilog() override { return "%f$rtoi_rounded(%l)"; } string emitC() override { @@ -3875,7 +3875,7 @@ public: : ASTGEN_SUPER_RToIS(fl, lhsp) { dtypeSetSigned32(); } - ASTGEN_MEMBERS_RToIS; + ASTGEN_MEMBERS_AstRToIS; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRToIS(lhs); } string emitVerilog() override { return "%f$rtoi(%l)"; } string emitC() override { return "VL_RTOI_I_D(%li)"; } @@ -3890,7 +3890,7 @@ public: : ASTGEN_SUPER_RealToBits(fl, lhsp) { dtypeSetUInt64(); } - ASTGEN_MEMBERS_RealToBits; + ASTGEN_MEMBERS_AstRealToBits; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRealToBits(lhs); } string emitVerilog() override { return "%f$realtobits(%l)"; } string emitC() override { return "VL_CVT_Q_D(%li)"; } @@ -3905,7 +3905,7 @@ public: : ASTGEN_SUPER_RedAnd(fl, lhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_RedAnd; + ASTGEN_MEMBERS_AstRedAnd; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRedAnd(lhs); } string emitVerilog() override { return "%f(& %l)"; } string emitC() override { return "VL_REDAND_%nq%lq(%lw, %P, %li)"; } @@ -3919,7 +3919,7 @@ public: : ASTGEN_SUPER_RedOr(fl, lhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_RedOr; + ASTGEN_MEMBERS_AstRedOr; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRedOr(lhs); } string emitVerilog() override { return "%f(| %l)"; } string emitC() override { return "VL_REDOR_%lq(%lW, %P, %li)"; } @@ -3933,7 +3933,7 @@ public: : ASTGEN_SUPER_RedXor(fl, lhsp) { dtypeSetBit(); } - ASTGEN_MEMBERS_RedXor; + ASTGEN_MEMBERS_AstRedXor; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opRedXor(lhs); } string emitVerilog() override { return "%f(^ %l)"; } string emitC() override { return "VL_REDXOR_%lq(%lW, %P, %li)"; } @@ -3953,7 +3953,7 @@ public: UASSERT_OBJ(!v3Global.assertDTypesResolved(), this, "not coded to create after dtypes resolved"); } - ASTGEN_MEMBERS_Signed; + ASTGEN_MEMBERS_AstSigned; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opAssign(lhs); out.isSigned(false); @@ -3971,7 +3971,7 @@ class AstTimeImport final : public AstNodeUniop { public: AstTimeImport(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_TimeImport(fl, lhsp) {} - ASTGEN_MEMBERS_TimeImport; + ASTGEN_MEMBERS_AstTimeImport; void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; } string emitVerilog() override { return "%l"; } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -3989,7 +3989,7 @@ public: : ASTGEN_SUPER_ToLowerN(fl, lhsp) { dtypeSetString(); } - ASTGEN_MEMBERS_ToLowerN; + ASTGEN_MEMBERS_AstToLowerN; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opToLowerN(lhs); } string emitVerilog() override { return "%l.tolower()"; } string emitC() override { return "VL_TOLOWER_NN(%li)"; } @@ -4004,7 +4004,7 @@ public: : ASTGEN_SUPER_ToUpperN(fl, lhsp) { dtypeSetString(); } - ASTGEN_MEMBERS_ToUpperN; + ASTGEN_MEMBERS_AstToUpperN; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opToUpperN(lhs); } string emitVerilog() override { return "%l.toupper()"; } string emitC() override { return "VL_TOUPPER_NN(%li)"; } @@ -4020,7 +4020,7 @@ public: UASSERT_OBJ(!v3Global.assertDTypesResolved(), this, "not coded to create after dtypes resolved"); } - ASTGEN_MEMBERS_Unsigned; + ASTGEN_MEMBERS_AstUnsigned; void numberOperate(V3Number& out, const V3Number& lhs) override { out.opAssign(lhs); out.isSigned(false); @@ -4038,7 +4038,7 @@ class AstAcosD final : public AstNodeSystemUniop { public: AstAcosD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_AcosD(fl, lhsp) {} - ASTGEN_MEMBERS_AcosD; + ASTGEN_MEMBERS_AstAcosD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::acos(lhs.toDouble())); } @@ -4049,7 +4049,7 @@ class AstAcoshD final : public AstNodeSystemUniop { public: AstAcoshD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_AcoshD(fl, lhsp) {} - ASTGEN_MEMBERS_AcoshD; + ASTGEN_MEMBERS_AstAcoshD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::acosh(lhs.toDouble())); } @@ -4060,7 +4060,7 @@ class AstAsinD final : public AstNodeSystemUniop { public: AstAsinD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_AsinD(fl, lhsp) {} - ASTGEN_MEMBERS_AsinD; + ASTGEN_MEMBERS_AstAsinD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::asin(lhs.toDouble())); } @@ -4071,7 +4071,7 @@ class AstAsinhD final : public AstNodeSystemUniop { public: AstAsinhD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_AsinhD(fl, lhsp) {} - ASTGEN_MEMBERS_AsinhD; + ASTGEN_MEMBERS_AstAsinhD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::asinh(lhs.toDouble())); } @@ -4082,7 +4082,7 @@ class AstAtanD final : public AstNodeSystemUniop { public: AstAtanD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_AtanD(fl, lhsp) {} - ASTGEN_MEMBERS_AtanD; + ASTGEN_MEMBERS_AstAtanD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::atan(lhs.toDouble())); } @@ -4093,7 +4093,7 @@ class AstAtanhD final : public AstNodeSystemUniop { public: AstAtanhD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_AtanhD(fl, lhsp) {} - ASTGEN_MEMBERS_AtanhD; + ASTGEN_MEMBERS_AstAtanhD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::atanh(lhs.toDouble())); } @@ -4104,7 +4104,7 @@ class AstCeilD final : public AstNodeSystemUniop { public: AstCeilD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_CeilD(fl, lhsp) {} - ASTGEN_MEMBERS_CeilD; + ASTGEN_MEMBERS_AstCeilD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::ceil(lhs.toDouble())); } @@ -4115,7 +4115,7 @@ class AstCosD final : public AstNodeSystemUniop { public: AstCosD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_CosD(fl, lhsp) {} - ASTGEN_MEMBERS_CosD; + ASTGEN_MEMBERS_AstCosD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::cos(lhs.toDouble())); } @@ -4126,7 +4126,7 @@ class AstCoshD final : public AstNodeSystemUniop { public: AstCoshD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_CoshD(fl, lhsp) {} - ASTGEN_MEMBERS_CoshD; + ASTGEN_MEMBERS_AstCoshD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::cosh(lhs.toDouble())); } @@ -4137,7 +4137,7 @@ class AstExpD final : public AstNodeSystemUniop { public: AstExpD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_ExpD(fl, lhsp) {} - ASTGEN_MEMBERS_ExpD; + ASTGEN_MEMBERS_AstExpD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::exp(lhs.toDouble())); } @@ -4148,7 +4148,7 @@ class AstFloorD final : public AstNodeSystemUniop { public: AstFloorD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_FloorD(fl, lhsp) {} - ASTGEN_MEMBERS_FloorD; + ASTGEN_MEMBERS_AstFloorD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::floor(lhs.toDouble())); } @@ -4159,7 +4159,7 @@ class AstLog10D final : public AstNodeSystemUniop { public: AstLog10D(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_Log10D(fl, lhsp) {} - ASTGEN_MEMBERS_Log10D; + ASTGEN_MEMBERS_AstLog10D; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::log10(lhs.toDouble())); } @@ -4170,7 +4170,7 @@ class AstLogD final : public AstNodeSystemUniop { public: AstLogD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_LogD(fl, lhsp) {} - ASTGEN_MEMBERS_LogD; + ASTGEN_MEMBERS_AstLogD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::log(lhs.toDouble())); } @@ -4181,7 +4181,7 @@ class AstSinD final : public AstNodeSystemUniop { public: AstSinD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_SinD(fl, lhsp) {} - ASTGEN_MEMBERS_SinD; + ASTGEN_MEMBERS_AstSinD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::sin(lhs.toDouble())); } @@ -4192,7 +4192,7 @@ class AstSinhD final : public AstNodeSystemUniop { public: AstSinhD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_SinhD(fl, lhsp) {} - ASTGEN_MEMBERS_SinhD; + ASTGEN_MEMBERS_AstSinhD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::sinh(lhs.toDouble())); } @@ -4203,7 +4203,7 @@ class AstSqrtD final : public AstNodeSystemUniop { public: AstSqrtD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_SqrtD(fl, lhsp) {} - ASTGEN_MEMBERS_SqrtD; + ASTGEN_MEMBERS_AstSqrtD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::sqrt(lhs.toDouble())); } @@ -4214,7 +4214,7 @@ class AstTanD final : public AstNodeSystemUniop { public: AstTanD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_TanD(fl, lhsp) {} - ASTGEN_MEMBERS_TanD; + ASTGEN_MEMBERS_AstTanD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::tan(lhs.toDouble())); } @@ -4225,7 +4225,7 @@ class AstTanhD final : public AstNodeSystemUniop { public: AstTanhD(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_TanhD(fl, lhsp) {} - ASTGEN_MEMBERS_TanhD; + ASTGEN_MEMBERS_AstTanhD; void numberOperate(V3Number& out, const V3Number& lhs) override { out.setDouble(std::tanh(lhs.toDouble())); } @@ -4244,7 +4244,7 @@ public: inline AstVarRef(FileLine* fl, AstVar* varp, const VAccess& access); // This form only allowed post-link (see above) inline AstVarRef(FileLine* fl, AstVarScope* varscp, const VAccess& access); - ASTGEN_MEMBERS_VarRef; + ASTGEN_MEMBERS_AstVarRef; void dump(std::ostream& str) const override; bool same(const AstNode* samep) const override; inline bool same(const AstVarRef* samep) const; @@ -4267,7 +4267,7 @@ public: : ASTGEN_SUPER_VarXRef(fl, name, nullptr, access) , m_dotted{dotted} {} inline AstVarXRef(FileLine* fl, AstVar* varp, const string& dotted, const VAccess& access); - ASTGEN_MEMBERS_VarXRef; + ASTGEN_MEMBERS_AstVarXRef; void dump(std::ostream& str) const override; string dotted() const { return m_dotted; } void dotted(const string& dotted) { m_dotted = dotted; } diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index dcf3afbb3..3ef73b94a 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -46,7 +46,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeBlock; + ASTGEN_MEMBERS_AstNodeBlock; void dump(std::ostream& str) const override; string name() const override { return m_name; } // * = Block name void name(const string& name) override { m_name = name; } @@ -116,7 +116,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeFTask; + ASTGEN_MEMBERS_AstNodeFTask; void dump(std::ostream& str = std::cout) const override; string name() const override { return m_name; } // * = Var name bool maybePointedTo() const override { return true; } @@ -184,7 +184,7 @@ public: AstNodeFile(VNType t, FileLine* fl, const string& name) : AstNode{t, fl} , m_name{name} {} - ASTGEN_MEMBERS_NodeFile; + ASTGEN_MEMBERS_AstNodeFile; void dump(std::ostream& str) const override; string name() const override { return m_name; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -228,7 +228,7 @@ protected: , m_recursiveClone{false} {} public: - ASTGEN_MEMBERS_NodeModule; + ASTGEN_MEMBERS_AstNodeModule; void dump(std::ostream& str) const override; bool maybePointedTo() const override { return true; } string name() const override { return m_name; } @@ -279,7 +279,7 @@ protected: } public: - ASTGEN_MEMBERS_NodePreSel; + ASTGEN_MEMBERS_AstNodePreSel; // METHODS bool same(const AstNode*) const override { return true; } }; @@ -294,7 +294,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeProcedure; + ASTGEN_MEMBERS_AstNodeProcedure; // METHODS void dump(std::ostream& str) const override; bool isJustOneBodyStmt() const { return stmtsp() && !stmtsp()->nextp(); } @@ -308,7 +308,7 @@ protected: : AstNode{t, fl} {} public: - ASTGEN_MEMBERS_NodeRange; + ASTGEN_MEMBERS_AstNodeRange; void dump(std::ostream& str) const override; }; class AstNodeStmt VL_NOT_FINAL : public AstNode { @@ -320,7 +320,7 @@ protected: , m_statement{statement} {} public: - ASTGEN_MEMBERS_NodeStmt; + ASTGEN_MEMBERS_AstNodeStmt; // METHODS bool isStatement() const { return m_statement; } // Really a statement void statement(bool flag) { m_statement = flag; } @@ -346,7 +346,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeAssign; + ASTGEN_MEMBERS_AstNodeAssign; // Clone single node, just get same type back. virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) = 0; bool hasDType() const override { return true; } @@ -373,7 +373,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeCCall; + ASTGEN_MEMBERS_AstNodeCCall; void dump(std::ostream& str = std::cout) const override; void cloneRelink() override; const char* broken() const override; @@ -403,7 +403,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeCase; + ASTGEN_MEMBERS_AstNodeCase; int instrCount() const override { return INSTR_COUNT_BRANCH; } }; class AstNodeCoverOrAssert VL_NOT_FINAL : public AstNodeStmt { @@ -424,7 +424,7 @@ public: this->propp(propp); this->addPasssp(passsp); } - ASTGEN_MEMBERS_NodeCoverOrAssert; + ASTGEN_MEMBERS_AstNodeCoverOrAssert; string name() const override { return m_name; } // * = Var name bool same(const AstNode* samep) const override { return samep->name() == name(); } void name(const string& name) override { m_name = name; } @@ -459,7 +459,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeFTaskRef; + ASTGEN_MEMBERS_AstNodeFTaskRef; const char* broken() const override; void cloneRelink() override; void dump(std::ostream& str = std::cout) const override; @@ -493,7 +493,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeFor; + ASTGEN_MEMBERS_AstNodeFor; bool isGateOptimizable() const override { return false; } int instrCount() const override { return INSTR_COUNT_BRANCH; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -515,7 +515,7 @@ protected: } public: - ASTGEN_MEMBERS_NodeIf; + ASTGEN_MEMBERS_AstNodeIf; bool isGateOptimizable() const override { return false; } bool isGateDedupable() const override { return true; } int instrCount() const override { return INSTR_COUNT_BRANCH; } @@ -545,7 +545,7 @@ public: this->lsbp(lsbp); this->msbp(msbp); } - ASTGEN_MEMBERS_NodeReadWriteMem; + ASTGEN_MEMBERS_AstNodeReadWriteMem; bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } bool isPure() const override { return false; } @@ -568,7 +568,7 @@ protected: , m_text{text} {} public: - ASTGEN_MEMBERS_NodeText; + ASTGEN_MEMBERS_AstNodeText; void dump(std::ostream& str = std::cout) const override; bool same(const AstNode* samep) const override { const AstNodeText* asamep = static_cast(samep); @@ -584,7 +584,7 @@ public: AstNodeSimpleText(VNType t, FileLine* fl, const string& textp, bool tracking = false) : AstNodeText(t, fl, textp) , m_tracking(tracking) {} - ASTGEN_MEMBERS_NodeSimpleText; + ASTGEN_MEMBERS_AstNodeSimpleText; void tracking(bool flag) { m_tracking = flag; } bool tracking() const { return m_tracking; } }; @@ -608,7 +608,7 @@ public: , m_sensesp{sensesp} { UASSERT(sensesp, "Sensesp required arg"); } - ASTGEN_MEMBERS_Active; + ASTGEN_MEMBERS_AstActive; void dump(std::ostream& str = std::cout) const override; string name() const override { return m_name; } const char* broken() const override; @@ -630,7 +630,7 @@ public: , m_name{name} { this->exprp(exprp); } - ASTGEN_MEMBERS_Arg; + ASTGEN_MEMBERS_AstArg; string name() const override { return m_name; } // * = Pin name, ""=go by number void name(const string& name) override { m_name = name; } bool emptyConnectNoNext() const { return !exprp() && name() == "" && !nextp(); } @@ -647,7 +647,7 @@ public: this->dimp(dimp); m_attrType = attrtype; } - ASTGEN_MEMBERS_AttrOf; + ASTGEN_MEMBERS_AstAttrOf; VAttrType attrType() const { return m_attrType; } void dump(std::ostream& str = std::cout) const override; }; @@ -664,7 +664,7 @@ public: UASSERT_OBJ(VN_IS(cellsp, Cell), cellsp, "Only instances allowed to be bound"); this->addCellsp(cellsp); } - ASTGEN_MEMBERS_Bind; + ASTGEN_MEMBERS_AstBind; // ACCESSORS string name() const override { return m_name; } // * = Bind Target name void name(const string& name) override { m_name = name; } @@ -735,7 +735,7 @@ public: m_dpiImportWrapper = false; m_dpiTraceInit = false; } - ASTGEN_MEMBERS_CFunc; + ASTGEN_MEMBERS_AstCFunc; string name() const override { return m_name; } const char* broken() const override; void cloneRelink() override; @@ -829,7 +829,7 @@ public: : ASTGEN_SUPER_CUse(fl) , m_useType{useType} , m_name{name} {} - ASTGEN_MEMBERS_CUse; + ASTGEN_MEMBERS_AstCUse; void dump(std::ostream& str = std::cout) const override; string name() const override { return m_name; } VUseType useType() const { return m_useType; } @@ -844,7 +844,7 @@ public: this->addCondsp(condsp); this->addStmtsp(stmtsp); } - ASTGEN_MEMBERS_CaseItem; + ASTGEN_MEMBERS_AstCaseItem; int instrCount() const override { return widthInstrs() + INSTR_COUNT_BRANCH; } bool isDefault() const { return condsp() == nullptr; } bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); } @@ -865,7 +865,7 @@ public: this->fromp(fromp); dtypeFrom(dtp); } - ASTGEN_MEMBERS_Cast; + ASTGEN_MEMBERS_AstCast; bool hasDType() const override { return true; } virtual string emitVerilog() { return "((%d)'(%l))"; } virtual bool cleanOut() const { V3ERROR_NA_RETURN(true); } @@ -884,7 +884,7 @@ public: this->lhsp(lhsp); this->dtp(dtp); } - ASTGEN_MEMBERS_CastParse; + ASTGEN_MEMBERS_AstCastParse; virtual string emitVerilog() { return "((%d)'(%l))"; } virtual string emitC() { V3ERROR_NA_RETURN(""); } virtual bool cleanOut() const { V3ERROR_NA_RETURN(true); } @@ -901,7 +901,7 @@ public: this->lhsp(lhsp); this->rhsp(rhsp); } - ASTGEN_MEMBERS_CastSize; + ASTGEN_MEMBERS_AstCastSize; // No hasDType because widthing removes this node before the hasDType check virtual string emitVerilog() { return "((%r)'(%l))"; } virtual bool cleanOut() const { V3ERROR_NA_RETURN(true); } @@ -937,7 +937,7 @@ public: this->addParamsp(paramsp); this->rangep(rangep); } - ASTGEN_MEMBERS_Cell; + ASTGEN_MEMBERS_AstCell; // No cloneRelink, we presume cloneee's want the same module linkages void dump(std::ostream& str) const override; const char* broken() const override; @@ -969,7 +969,7 @@ public: , m_name{name} { this->addSelp(selp); } - ASTGEN_MEMBERS_CellArrayRef; + ASTGEN_MEMBERS_AstCellArrayRef; // ACCESSORS string name() const override { return m_name; } // * = Array name }; @@ -992,7 +992,7 @@ public: , m_name{name} , m_origModName{origModName} , m_timeunit{timeunit} {} - ASTGEN_MEMBERS_CellInline; + ASTGEN_MEMBERS_AstCellInline; void dump(std::ostream& str) const override; const char* broken() const override; // ACCESSORS @@ -1017,7 +1017,7 @@ public: this->cellp(cellp); this->exprp(exprp); } - ASTGEN_MEMBERS_CellRef; + ASTGEN_MEMBERS_AstCellRef; // ACCESSORS string name() const override { return m_name; } // * = Array name }; @@ -1031,7 +1031,7 @@ public: : ASTGEN_SUPER_ClassExtends(fl) { this->classOrPkgsp(classOrPkgsp); // Only for parser } - ASTGEN_MEMBERS_ClassExtends; + ASTGEN_MEMBERS_AstClassExtends; bool hasDType() const override { return true; } string verilogKwd() const override { return "extends"; } AstClass* classp() const; // Class being extended (after link) @@ -1050,7 +1050,7 @@ public: , m_classOrPackageNodep{classOrPackageNodep} { this->addParamsp(paramsp); } - ASTGEN_MEMBERS_ClassOrPackageRef; + ASTGEN_MEMBERS_AstClassOrPackageRef; // METHODS const char* broken() const override { BROKEN_RTN(m_classOrPackageNodep && !m_classOrPackageNodep->brokeExists()); @@ -1084,7 +1084,7 @@ public: this->addSensesp(sensesp); this->addBodysp(bodysp); } - ASTGEN_MEMBERS_Clocking; + ASTGEN_MEMBERS_AstClocking; }; class AstConstPool final : public AstNode { // Container for const static data @@ -1098,7 +1098,7 @@ class AstConstPool final : public AstNode { public: explicit AstConstPool(FileLine* fl); - ASTGEN_MEMBERS_ConstPool; + ASTGEN_MEMBERS_AstConstPool; bool maybePointedTo() const override { return true; } const char* broken() const override; void cloneRelink() override { V3ERROR_NA; } @@ -1131,7 +1131,7 @@ public: this->rhsp(rhsp); } string name() const override { return m_name; } // * = Scope name - ASTGEN_MEMBERS_DefParam; + ASTGEN_MEMBERS_AstDefParam; bool same(const AstNode*) const override { return true; } string path() const { return m_path; } }; @@ -1148,7 +1148,7 @@ public: this->lhsp(lhsp); this->rhsp(rhsp); } - ASTGEN_MEMBERS_Dot; + ASTGEN_MEMBERS_AstDot; // For parser, make only if non-null package static AstNode* newIfPkg(FileLine* fl, AstNode* packageOrClassp, AstNode* rhsp) { if (!packageOrClassp) return rhsp; @@ -1169,7 +1169,7 @@ public: : ASTGEN_SUPER_DpiExport(fl) , m_name{vname} , m_cname{cname} {} - ASTGEN_MEMBERS_DpiExport; + ASTGEN_MEMBERS_AstDpiExport; string name() const override { return m_name; } void name(const string& name) override { m_name = name; } string cname() const { return m_cname; } @@ -1183,7 +1183,7 @@ private: public: inline AstElabDisplay(FileLine* fl, VDisplayType dispType, AstNode* exprsp); - ASTGEN_MEMBERS_ElabDisplay; + ASTGEN_MEMBERS_AstElabDisplay; const char* broken() const override { BROKEN_RTN(!fmtp()); return nullptr; @@ -1206,7 +1206,7 @@ class AstEmpty final : public AstNode { public: explicit AstEmpty(FileLine* fl) : ASTGEN_SUPER_Empty(fl) {} - ASTGEN_MEMBERS_Empty; + ASTGEN_MEMBERS_AstEmpty; bool same(const AstNode* /*samep*/) const override { return true; } }; class AstExecGraph final : public AstNode { @@ -1225,7 +1225,7 @@ class AstExecGraph final : public AstNode { public: explicit AstExecGraph(FileLine* fl, const string& name); - ASTGEN_MEMBERS_ExecGraph; + ASTGEN_MEMBERS_AstExecGraph; ~AstExecGraph() override; const char* broken() const override { BROKEN_RTN(!m_depGraphp); @@ -1244,7 +1244,7 @@ public: : ASTGEN_SUPER_Implicit(fl) { this->addExprsp(exprsp); } - ASTGEN_MEMBERS_Implicit; + ASTGEN_MEMBERS_AstImplicit; }; class AstInitArray final : public AstNode { // Set a var to a map of values @@ -1266,7 +1266,7 @@ public: dtypep(newDTypep); this->defaultp(defaultp); } - ASTGEN_MEMBERS_InitArray; + ASTGEN_MEMBERS_AstInitArray; void dump(std::ostream& str) const override; const char* broken() const override; void cloneRelink() override; @@ -1293,7 +1293,7 @@ public: : ASTGEN_SUPER_InitItem(fl) { this->valuep(valuep); } - ASTGEN_MEMBERS_InitItem; + ASTGEN_MEMBERS_AstInitItem; bool maybePointedTo() const override { return true; } bool hasDType() const override { return false; } // See valuep()'s dtype instead }; @@ -1306,7 +1306,7 @@ public: : ASTGEN_SUPER_IntfRef(fl) , m_name{name} {} string name() const override { return m_name; } - ASTGEN_MEMBERS_IntfRef; + ASTGEN_MEMBERS_AstIntfRef; }; class AstMTaskBody final : public AstNode { // Hold statements for each MTask @@ -1316,7 +1316,7 @@ class AstMTaskBody final : public AstNode { public: explicit AstMTaskBody(FileLine* fl) : ASTGEN_SUPER_MTaskBody(fl) {} - ASTGEN_MEMBERS_MTaskBody; + ASTGEN_MEMBERS_AstMTaskBody; const char* broken() const override { BROKEN_RTN(!m_execMTaskp); return nullptr; @@ -1344,7 +1344,7 @@ public: } string name() const override { return m_name; } bool maybePointedTo() const override { return true; } - ASTGEN_MEMBERS_Modport; + ASTGEN_MEMBERS_AstModport; }; class AstModportFTaskRef final : public AstNode { // An import/export referenced under a modport @@ -1360,7 +1360,7 @@ public: : ASTGEN_SUPER_ModportFTaskRef(fl) , m_name{name} , m_export{isExport} {} - ASTGEN_MEMBERS_ModportFTaskRef; + ASTGEN_MEMBERS_AstModportFTaskRef; const char* broken() const override; void cloneRelink() override; void dump(std::ostream& str) const override; @@ -1383,7 +1383,7 @@ public: : ASTGEN_SUPER_ModportVarRef(fl) , m_name{name} , m_direction{direction} {} - ASTGEN_MEMBERS_ModportVarRef; + ASTGEN_MEMBERS_AstModportVarRef; const char* broken() const override; void cloneRelink() override; void dump(std::ostream& str) const override; @@ -1417,7 +1417,7 @@ class AstNetlist final : public AstNode { uint32_t m_nextFreeMTaskProfilingID = 0; // Next unique ID to use for PGO public: AstNetlist(); - ASTGEN_MEMBERS_Netlist; + ASTGEN_MEMBERS_AstNetlist; const char* broken() const override; void cloneRelink() override { V3ERROR_NA; } string name() const override { return "$root"; } @@ -1463,7 +1463,7 @@ public: : ASTGEN_SUPER_PackageExport(fl) , m_name{name} , m_packagep{packagep} {} - ASTGEN_MEMBERS_PackageExport; + ASTGEN_MEMBERS_AstPackageExport; const char* broken() const override; void cloneRelink() override; void dump(std::ostream& str) const override; @@ -1477,7 +1477,7 @@ public: // cppcheck-suppress noExplicitConstructor AstPackageExportStarStar(FileLine* fl) : ASTGEN_SUPER_PackageExportStarStar(fl) {} - ASTGEN_MEMBERS_PackageExportStarStar; + ASTGEN_MEMBERS_AstPackageExportStarStar; }; class AstPackageImport final : public AstNode { private: @@ -1489,7 +1489,7 @@ public: : ASTGEN_SUPER_PackageImport(fl) , m_name{name} , m_packagep{packagep} {} - ASTGEN_MEMBERS_PackageImport; + ASTGEN_MEMBERS_AstPackageImport; const char* broken() const override; void cloneRelink() override; void dump(std::ostream& str) const override; @@ -1517,7 +1517,7 @@ public: this->lhsp(lhsp); this->ftaskrefp(ftaskrefp); } - ASTGEN_MEMBERS_ParseRef; + ASTGEN_MEMBERS_AstParseRef; void dump(std::ostream& str) const override; string name() const override { return m_name; } // * = Var name bool same(const AstNode* samep) const override { @@ -1546,7 +1546,7 @@ public: this->exprp(exprp); } inline AstPin(FileLine* fl, int pinNum, AstVarRef* varname, AstNode* exprp); - ASTGEN_MEMBERS_Pin; + ASTGEN_MEMBERS_AstPin; void dump(std::ostream& str) const override; const char* broken() const override; string name() const override { return m_name; } // * = Pin name, ""=go by number @@ -1574,7 +1574,7 @@ public: : ASTGEN_SUPER_Port(fl) , m_pinNum{pinnum} , m_name{name} {} - ASTGEN_MEMBERS_Port; + ASTGEN_MEMBERS_AstPort; string name() const override { return m_name; } // * = Port name int pinNum() const { return m_pinNum; } // * = Pin number, for order based instantiation }; @@ -1586,7 +1586,7 @@ public: AstPragma(FileLine* fl, VPragmaType pragType) : ASTGEN_SUPER_Pragma(fl) , m_pragType{pragType} {} - ASTGEN_MEMBERS_Pragma; + ASTGEN_MEMBERS_AstPragma; VPragmaType pragType() const { return m_pragType; } // *=type of the pragma bool isPredictOptimizable() const override { return false; } bool same(const AstNode* samep) const override { @@ -1607,7 +1607,7 @@ public: this->disablep(disablep); this->propp(propp); } - ASTGEN_MEMBERS_PropClocked; + ASTGEN_MEMBERS_AstPropClocked; bool hasDType() const override { return true; } // Used under Cover, which expects a bool child @@ -1623,7 +1623,7 @@ public: , m_direction{direction} { this->lhsp(lhsp); } - ASTGEN_MEMBERS_Pull; + ASTGEN_MEMBERS_AstPull; bool same(const AstNode* samep) const override { return direction() == static_cast(samep)->direction(); } @@ -1661,7 +1661,7 @@ public: dtypeSetString(); addExprsp(exprsp); } - ASTGEN_MEMBERS_SFormatF; + ASTGEN_MEMBERS_AstSFormatF; string name() const override { return m_text; } int instrCount() const override { return INSTR_COUNT_PLI; } bool hasDType() const override { return true; } @@ -1701,7 +1701,7 @@ public: , m_aboveScopep{aboveScopep} , m_aboveCellp{aboveCellp} , m_modp{modp} {} - ASTGEN_MEMBERS_Scope; + ASTGEN_MEMBERS_AstScope; void cloneRelink() override; const char* broken() const override; bool maybePointedTo() const override { return true; } @@ -1731,7 +1731,7 @@ public: this->fromp(fromp); this->addElementsp(elementsp); } - ASTGEN_MEMBERS_SelLoopVars; + ASTGEN_MEMBERS_AstSelLoopVars; bool same(const AstNode* /*samep*/) const override { return true; } bool maybePointedTo() const override { return false; } }; @@ -1769,7 +1769,7 @@ public: AstSenItem(FileLine* fl, Never) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_NEVER} {} - ASTGEN_MEMBERS_SenItem; + ASTGEN_MEMBERS_AstSenItem; void dump(std::ostream& str) const override; bool same(const AstNode* samep) const override { return edgeType() == static_cast(samep)->edgeType(); @@ -1799,7 +1799,7 @@ public: : ASTGEN_SUPER_SenTree(fl) { this->addSensesp(sensesp); } - ASTGEN_MEMBERS_SenTree; + ASTGEN_MEMBERS_AstSenTree; void dump(std::ostream& str) const override; bool maybePointedTo() const override { return true; } bool isMulti() const { return m_multi; } @@ -1817,7 +1817,7 @@ public: // Dummy node used within V3Split; never exists outside of V3Split. explicit AstSplitPlaceholder(FileLine* fl) : ASTGEN_SUPER_SplitPlaceholder(fl) {} - ASTGEN_MEMBERS_SplitPlaceholder; + ASTGEN_MEMBERS_AstSplitPlaceholder; }; class AstStrengthSpec final : public AstNode { private: @@ -1830,7 +1830,7 @@ public: , m_s0{s0} , m_s1{s1} {} - ASTGEN_MEMBERS_StrengthSpec; + ASTGEN_MEMBERS_AstStrengthSpec; VStrength strength0() { return m_s0; } VStrength strength1() { return m_s1; } void dump(std::ostream& str) const override; @@ -1850,7 +1850,7 @@ class AstTopScope final : public AstNode { } public: - ASTGEN_MEMBERS_TopScope; + ASTGEN_MEMBERS_AstTopScope; bool maybePointedTo() const override { return true; } }; class AstTypeTable final : public AstNode { @@ -1866,7 +1866,7 @@ class AstTypeTable final : public AstNode { public: explicit AstTypeTable(FileLine* fl); - ASTGEN_MEMBERS_TypeTable; + ASTGEN_MEMBERS_AstTypeTable; bool maybePointedTo() const override { return true; } const char* broken() const override { BROKEN_RTN(m_emptyQueuep && !m_emptyQueuep->brokeExists()); @@ -1904,7 +1904,7 @@ public: addAttrsp(attrsp); dtypep(nullptr); // V3Width will resolve } - ASTGEN_MEMBERS_Typedef; + ASTGEN_MEMBERS_AstTypedef; void dump(std::ostream& str) const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); } @@ -1927,7 +1927,7 @@ public: AstTypedefFwd(FileLine* fl, const string& name) : ASTGEN_SUPER_TypedefFwd(fl) , m_name{name} {} - ASTGEN_MEMBERS_TypedefFwd; + ASTGEN_MEMBERS_AstTypedefFwd; // METHODS string name() const override { return m_name; } bool maybePointedTo() const override { return true; } @@ -1939,7 +1939,7 @@ public: : ASTGEN_SUPER_UdpTable(fl) { this->addLinesp(linesp); } - ASTGEN_MEMBERS_UdpTable; + ASTGEN_MEMBERS_AstUdpTable; }; class AstUdpTableLine final : public AstNode { string m_text; @@ -1948,7 +1948,7 @@ public: AstUdpTableLine(FileLine* fl, const string& text) : ASTGEN_SUPER_UdpTableLine(fl) , m_text{text} {} - ASTGEN_MEMBERS_UdpTableLine; + ASTGEN_MEMBERS_AstUdpTableLine; string name() const override { return m_text; } string text() const { return m_text; } }; @@ -1965,7 +1965,7 @@ public: this->refp(refp); this->cellrefp(cellrefp); } - ASTGEN_MEMBERS_UnlinkedRef; + ASTGEN_MEMBERS_AstUnlinkedRef; }; class AstVar final : public AstNode { // A variable (in/out/wire/reg/param) inside a module @@ -2130,7 +2130,7 @@ public: dtypeFrom(examplep); m_declKwd = examplep->declKwd(); } - ASTGEN_MEMBERS_Var; + ASTGEN_MEMBERS_AstVar; void dump(std::ostream& str) const override; string name() const override { return m_name; } // * = Var name bool hasDType() const override { return true; } @@ -2354,7 +2354,7 @@ public: m_trace = true; dtypeFrom(varp); } - ASTGEN_MEMBERS_VarScope; + ASTGEN_MEMBERS_AstVarScope; void cloneRelink() override { if (m_varp && m_varp->clonep()) { m_varp = m_varp->clonep(); @@ -2393,7 +2393,7 @@ public: : ASTGEN_SUPER_Begin(fl, name, stmtsp) , m_generate{generate} , m_implied{implied} {} - ASTGEN_MEMBERS_Begin; + ASTGEN_MEMBERS_AstBegin; void dump(std::ostream& str) const override; void generate(bool flag) { m_generate = flag; } bool generate() const { return m_generate; } @@ -2409,7 +2409,7 @@ public: // Node that puts name into the output stream AstFork(FileLine* fl, const string& name, AstNode* stmtsp) : ASTGEN_SUPER_Fork(fl, name, stmtsp) {} - ASTGEN_MEMBERS_Fork; + ASTGEN_MEMBERS_AstFork; bool isTimingControl() const override { return !joinType().joinNone(); } void dump(std::ostream& str) const override; VJoinType joinType() const { return m_joinType; } @@ -2424,7 +2424,7 @@ public: : ASTGEN_SUPER_Func(fl, name, stmtp) { this->fvarp(fvarp); } - ASTGEN_MEMBERS_Func; + ASTGEN_MEMBERS_AstFunc; bool hasDType() const override { return true; } }; class AstTask final : public AstNodeFTask { @@ -2432,7 +2432,7 @@ class AstTask final : public AstNodeFTask { public: AstTask(FileLine* fl, const string& name, AstNode* stmtp) : ASTGEN_SUPER_Task(fl, name, stmtp) {} - ASTGEN_MEMBERS_Task; + ASTGEN_MEMBERS_AstTask; }; // === AstNodeFile === @@ -2449,7 +2449,7 @@ public: , m_slow{false} , m_source{false} , m_support{false} {} - ASTGEN_MEMBERS_CFile; + ASTGEN_MEMBERS_AstCFile; void dump(std::ostream& str = std::cout) const override; bool slow() const { return m_slow; } void slow(bool flag) { m_slow = flag; } @@ -2464,7 +2464,7 @@ class AstVFile final : public AstNodeFile { public: AstVFile(FileLine* fl, const string& name) : ASTGEN_SUPER_VFile(fl, name) {} - ASTGEN_MEMBERS_VFile; + ASTGEN_MEMBERS_AstVFile; void dump(std::ostream& str = std::cout) const override; }; @@ -2483,7 +2483,7 @@ class AstClass final : public AstNodeModule { public: AstClass(FileLine* fl, const string& name) : ASTGEN_SUPER_Class(fl, name) {} - ASTGEN_MEMBERS_Class; + ASTGEN_MEMBERS_AstClass; string verilogKwd() const override { return "class"; } bool maybePointedTo() const override { return true; } void dump(std::ostream& str) const override; @@ -2518,7 +2518,7 @@ class AstClassPackage final : public AstNodeModule { public: AstClassPackage(FileLine* fl, const string& name) : ASTGEN_SUPER_ClassPackage(fl, name) {} - ASTGEN_MEMBERS_ClassPackage; + ASTGEN_MEMBERS_AstClassPackage; string verilogKwd() const override { return "classpackage"; } const char* broken() const override; void cloneRelink() override; @@ -2531,7 +2531,7 @@ class AstIface final : public AstNodeModule { public: AstIface(FileLine* fl, const string& name) : ASTGEN_SUPER_Iface(fl, name) {} - ASTGEN_MEMBERS_Iface; + ASTGEN_MEMBERS_AstIface; // Interfaces have `timescale applicability but lots of code seems to // get false warnings if we enable this string verilogKwd() const override { return "interface"; } @@ -2545,7 +2545,7 @@ public: AstModule(FileLine* fl, const string& name, bool program = false) : ASTGEN_SUPER_Module(fl, name) , m_isProgram{program} {} - ASTGEN_MEMBERS_Module; + ASTGEN_MEMBERS_AstModule; string verilogKwd() const override { return m_isProgram ? "program" : "module"; } bool timescaleMatters() const override { return true; } }; @@ -2554,7 +2554,7 @@ class AstNotFoundModule final : public AstNodeModule { public: AstNotFoundModule(FileLine* fl, const string& name) : ASTGEN_SUPER_NotFoundModule(fl, name) {} - ASTGEN_MEMBERS_NotFoundModule; + ASTGEN_MEMBERS_AstNotFoundModule; string verilogKwd() const override { return "/*not-found-*/ module"; } bool timescaleMatters() const override { return false; } }; @@ -2563,7 +2563,7 @@ class AstPackage final : public AstNodeModule { public: AstPackage(FileLine* fl, const string& name) : ASTGEN_SUPER_Package(fl, name) {} - ASTGEN_MEMBERS_Package; + ASTGEN_MEMBERS_AstPackage; string verilogKwd() const override { return "package"; } bool timescaleMatters() const override { return !isDollarUnit(); } static string dollarUnitName() { return AstNode::encodeName("$unit"); } @@ -2574,7 +2574,7 @@ class AstPrimitive final : public AstNodeModule { public: AstPrimitive(FileLine* fl, const string& name) : ASTGEN_SUPER_Primitive(fl, name) {} - ASTGEN_MEMBERS_Primitive; + ASTGEN_MEMBERS_AstPrimitive; string verilogKwd() const override { return "primitive"; } bool timescaleMatters() const override { return false; } }; @@ -2589,7 +2589,7 @@ public: UASSERT_OBJ(!v3Global.assertDTypesResolved(), this, "not coded to create after dtypes resolved"); } - ASTGEN_MEMBERS_SelBit; + ASTGEN_MEMBERS_AstSelBit; AstNode* bitp() const { return rhsp(); } }; class AstSelExtract final : public AstNodePreSel { @@ -2597,7 +2597,7 @@ class AstSelExtract final : public AstNodePreSel { public: AstSelExtract(FileLine* fl, AstNode* fromp, AstNode* msbp, AstNode* lsbp) : ASTGEN_SUPER_SelExtract(fl, fromp, msbp, lsbp) {} - ASTGEN_MEMBERS_SelExtract; + ASTGEN_MEMBERS_AstSelExtract; AstNode* leftp() const { return rhsp(); } AstNode* rightp() const { return thsp(); } }; @@ -2607,7 +2607,7 @@ class AstSelMinus final : public AstNodePreSel { public: AstSelMinus(FileLine* fl, AstNode* fromp, AstNode* bitp, AstNode* widthp) : ASTGEN_SUPER_SelMinus(fl, fromp, bitp, widthp) {} - ASTGEN_MEMBERS_SelMinus; + ASTGEN_MEMBERS_AstSelMinus; AstNode* bitp() const { return rhsp(); } AstNode* widthp() const { return thsp(); } }; @@ -2617,7 +2617,7 @@ class AstSelPlus final : public AstNodePreSel { public: AstSelPlus(FileLine* fl, AstNode* fromp, AstNode* bitp, AstNode* widthp) : ASTGEN_SUPER_SelPlus(fl, fromp, bitp, widthp) {} - ASTGEN_MEMBERS_SelPlus; + ASTGEN_MEMBERS_AstSelPlus; AstNode* bitp() const { return rhsp(); } AstNode* widthp() const { return thsp(); } }; @@ -2633,7 +2633,7 @@ public: , m_keyword{keyword} { this->sensesp(sensesp); } - ASTGEN_MEMBERS_Always; + ASTGEN_MEMBERS_AstAlways; // void dump(std::ostream& str) const override; VAlwaysKwd keyword() const { return m_keyword; } @@ -2646,7 +2646,7 @@ public: : ASTGEN_SUPER_AlwaysPost(fl, stmtsp) { this->sensesp(sensesp); } - ASTGEN_MEMBERS_AlwaysPost; + ASTGEN_MEMBERS_AstAlwaysPost; }; class AstAlwaysPostponed final : public AstNodeProcedure { // Like always but postponement scheduling region @@ -2654,19 +2654,19 @@ class AstAlwaysPostponed final : public AstNodeProcedure { public: AstAlwaysPostponed(FileLine* fl, AstNode* stmtsp) : ASTGEN_SUPER_AlwaysPostponed(fl, stmtsp) {} - ASTGEN_MEMBERS_AlwaysPostponed; + ASTGEN_MEMBERS_AstAlwaysPostponed; }; class AstFinal final : public AstNodeProcedure { public: AstFinal(FileLine* fl, AstNode* stmtsp) : ASTGEN_SUPER_Final(fl, stmtsp) {} - ASTGEN_MEMBERS_Final; + ASTGEN_MEMBERS_AstFinal; }; class AstInitial final : public AstNodeProcedure { public: AstInitial(FileLine* fl, AstNode* stmtsp) : ASTGEN_SUPER_Initial(fl, stmtsp) {} - ASTGEN_MEMBERS_Initial; + ASTGEN_MEMBERS_AstInitial; }; class AstInitialAutomatic final : public AstNodeProcedure { // Automatic variable initialization @@ -2674,7 +2674,7 @@ class AstInitialAutomatic final : public AstNodeProcedure { public: AstInitialAutomatic(FileLine* fl, AstNode* stmtsp) : ASTGEN_SUPER_InitialAutomatic(fl, stmtsp) {} - ASTGEN_MEMBERS_InitialAutomatic; + ASTGEN_MEMBERS_AstInitialAutomatic; }; class AstInitialStatic final : public AstNodeProcedure { // Static variable initialization @@ -2682,7 +2682,7 @@ class AstInitialStatic final : public AstNodeProcedure { public: AstInitialStatic(FileLine* fl, AstNode* stmtsp) : ASTGEN_SUPER_InitialStatic(fl, stmtsp) {} - ASTGEN_MEMBERS_InitialStatic; + ASTGEN_MEMBERS_AstInitialStatic; }; // === AstNodeRange === @@ -2695,7 +2695,7 @@ public: : ASTGEN_SUPER_BracketRange(fl) { this->elementsp(elementsp); } - ASTGEN_MEMBERS_BracketRange; + ASTGEN_MEMBERS_AstBracketRange; virtual string emitC() { V3ERROR_NA_RETURN(""); } virtual string emitVerilog() { V3ERROR_NA_RETURN(""); } bool same(const AstNode* /*samep*/) const override { return true; } @@ -2715,7 +2715,7 @@ public: } inline AstRange(FileLine* fl, int left, int right); inline AstRange(FileLine* fl, const VNumRange& range); - ASTGEN_MEMBERS_Range; + ASTGEN_MEMBERS_AstRange; inline int leftConst() const; inline int rightConst() const; int hiConst() const { @@ -2739,7 +2739,7 @@ class AstUnsizedRange final : public AstNodeRange { public: explicit AstUnsizedRange(FileLine* fl) : ASTGEN_SUPER_UnsizedRange(fl) {} - ASTGEN_MEMBERS_UnsizedRange; + ASTGEN_MEMBERS_AstUnsizedRange; virtual string emitC() { V3ERROR_NA_RETURN(""); } virtual string emitVerilog() { return "[]"; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -2749,7 +2749,7 @@ class AstWildcardRange final : public AstNodeRange { public: explicit AstWildcardRange(FileLine* fl) : ASTGEN_SUPER_WildcardRange(fl) {} - ASTGEN_MEMBERS_WildcardRange; + ASTGEN_MEMBERS_AstWildcardRange; virtual string emitC() { V3ERROR_NA_RETURN(""); } virtual string emitVerilog() { return "[*]"; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -2767,7 +2767,7 @@ public: addSensesp(sensesp); addStmtsp(stmtsp); } - ASTGEN_MEMBERS_AlwaysPublic; + ASTGEN_MEMBERS_AstAlwaysPublic; bool same(const AstNode* /*samep*/) const override { return true; } // Special accessors bool isJustOneBodyStmt() const { return stmtsp() && !stmtsp()->nextp(); } @@ -2777,7 +2777,7 @@ class AstBreak final : public AstNodeStmt { public: explicit AstBreak(FileLine* fl) : ASTGEN_SUPER_Break(fl) {} - ASTGEN_MEMBERS_Break; + ASTGEN_MEMBERS_AstBreak; string verilogKwd() const override { return "break"; } bool isBrancher() const override { return true; // SPECIAL: We don't process code after breaks @@ -2793,7 +2793,7 @@ public: , m_sensesp{sensesp} { this->exprp(exprp); } - ASTGEN_MEMBERS_CAwait; + ASTGEN_MEMBERS_AstCAwait; bool isTimingControl() const override { return true; } const char* broken() const override { BROKEN_RTN(m_sensesp && !m_sensesp->brokeExists()); @@ -2831,7 +2831,7 @@ public: this->fromp(fromp); this->addPinsp(pinsp); } - ASTGEN_MEMBERS_CMethodHard; + ASTGEN_MEMBERS_AstCMethodHard; string name() const override { return m_name; } // * = Var name bool hasDType() const override { return true; } void name(const string& name) override { m_name = name; } @@ -2854,7 +2854,7 @@ public: : ASTGEN_SUPER_CReset(fl) { this->varrefp(varrefp); } - ASTGEN_MEMBERS_CReset; + ASTGEN_MEMBERS_AstCReset; bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -2867,7 +2867,7 @@ public: : ASTGEN_SUPER_CReturn(fl) { this->lhsp(lhsp); } - ASTGEN_MEMBERS_CReturn; + ASTGEN_MEMBERS_AstCReturn; int instrCount() const override { return widthInstrs(); } bool same(const AstNode* /*samep*/) const override { return true; } }; @@ -2880,7 +2880,7 @@ public: this->addExprsp(exprsp); } inline AstCStmt(FileLine* fl, const string& textStmt); - ASTGEN_MEMBERS_CStmt; + ASTGEN_MEMBERS_AstCStmt; bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -2895,7 +2895,7 @@ public: : ASTGEN_SUPER_Comment(fl) , m_showAt{showAt} , m_name{name} {} - ASTGEN_MEMBERS_Comment; + ASTGEN_MEMBERS_AstComment; string name() const override { return m_name; } // * = Text bool same(const AstNode* samep) const override { return true; } // Ignore name in comments virtual bool showAt() const { return m_showAt; } @@ -2904,7 +2904,7 @@ class AstContinue final : public AstNodeStmt { public: explicit AstContinue(FileLine* fl) : ASTGEN_SUPER_Continue(fl) {} - ASTGEN_MEMBERS_Continue; + ASTGEN_MEMBERS_AstContinue; string verilogKwd() const override { return "continue"; } bool isBrancher() const override { return true; // SPECIAL: We don't process code after breaks @@ -2931,7 +2931,7 @@ public: , m_text{comment} , m_linescov{linescov} , m_offset{offset} {} - ASTGEN_MEMBERS_CoverDecl; + ASTGEN_MEMBERS_AstCoverDecl; const char* broken() const override { BROKEN_RTN(m_dataDeclp && !m_dataDeclp->brokeExists()); if (m_dataDeclp && m_dataDeclp->m_dataDeclp) { // Avoid O(n^2) accessing @@ -2976,7 +2976,7 @@ public: AstCoverInc(FileLine* fl, AstCoverDecl* declp) : ASTGEN_SUPER_CoverInc(fl) , m_declp{declp} {} - ASTGEN_MEMBERS_CoverInc; + ASTGEN_MEMBERS_AstCoverInc; const char* broken() const override { BROKEN_RTN(!declp()->brokeExists()); return nullptr; @@ -3008,7 +3008,7 @@ public: this->origp(origp); this->changep(changep); } - ASTGEN_MEMBERS_CoverToggle; + ASTGEN_MEMBERS_AstCoverToggle; int instrCount() const override { return 3 + INSTR_COUNT_BRANCH + INSTR_COUNT_LD; } bool same(const AstNode* /*samep*/) const override { return true; } bool isGateOptimizable() const override { return false; } @@ -3028,7 +3028,7 @@ public: this->lhsp(lhsp); this->addStmtsp(stmtsp); } - ASTGEN_MEMBERS_Delay; + ASTGEN_MEMBERS_AstDelay; bool isTimingControl() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } }; @@ -3039,7 +3039,7 @@ public: AstDisable(FileLine* fl, const string& name) : ASTGEN_SUPER_Disable(fl) , m_name{name} {} - ASTGEN_MEMBERS_Disable; + ASTGEN_MEMBERS_AstDisable; string name() const override { return m_name; } // * = Block name void name(const string& flag) override { m_name = flag; } bool isBrancher() const override { @@ -3051,7 +3051,7 @@ class AstDisableFork final : public AstNodeStmt { public: explicit AstDisableFork(FileLine* fl) : ASTGEN_SUPER_DisableFork(fl) {} - ASTGEN_MEMBERS_DisableFork; + ASTGEN_MEMBERS_AstDisableFork; }; class AstDisplay final : public AstNodeStmt { // Parents: stmtlist @@ -3075,7 +3075,7 @@ public: this->fmtp(new AstSFormatF{fl, AstSFormatF::NoFormat(), exprsp, missingArgChar}); this->filep(filep); } - ASTGEN_MEMBERS_Display; + ASTGEN_MEMBERS_AstDisplay; void dump(std::ostream& str) const override; const char* broken() const override { BROKEN_RTN(!fmtp()); @@ -3110,7 +3110,7 @@ public: , m_ctlType{ctlType} { this->exprp(exprp); } - ASTGEN_MEMBERS_DumpCtl; + ASTGEN_MEMBERS_AstDumpCtl; string verilogKwd() const override { return ctlType().ascii(); } bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } @@ -3129,7 +3129,7 @@ public: this->sensesp(sensesp); this->addStmtsp(stmtsp); } - ASTGEN_MEMBERS_EventControl; + ASTGEN_MEMBERS_AstEventControl; string verilogKwd() const override { return "@(%l) %r"; } bool isTimingControl() const override { return true; } int instrCount() const override { return 0; } @@ -3142,7 +3142,7 @@ public: : ASTGEN_SUPER_FClose(fl) { this->filep(filep); } - ASTGEN_MEMBERS_FClose; + ASTGEN_MEMBERS_AstFClose; string verilogKwd() const override { return "$fclose"; } bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } @@ -3159,7 +3159,7 @@ public: : ASTGEN_SUPER_FFlush(fl) { this->filep(filep); } - ASTGEN_MEMBERS_FFlush; + ASTGEN_MEMBERS_AstFFlush; string verilogKwd() const override { return "$fflush"; } bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } @@ -3180,7 +3180,7 @@ public: this->filenamep(filenamep); this->modep(modep); } - ASTGEN_MEMBERS_FOpen; + ASTGEN_MEMBERS_AstFOpen; string verilogKwd() const override { return "$fopen"; } bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } @@ -3199,7 +3199,7 @@ public: this->filep(filep); this->filenamep(filenamep); } - ASTGEN_MEMBERS_FOpenMcd; + ASTGEN_MEMBERS_AstFOpenMcd; string verilogKwd() const override { return "$fopen"; } bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } @@ -3212,7 +3212,7 @@ class AstFinish final : public AstNodeStmt { public: explicit AstFinish(FileLine* fl) : ASTGEN_SUPER_Finish(fl) {} - ASTGEN_MEMBERS_Finish; + ASTGEN_MEMBERS_AstFinish; bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } bool isPure() const override { return false; } // SPECIAL: $display has 'visual' ordering @@ -3231,7 +3231,7 @@ public: , m_delayed{delayed} { this->operandp(operandp); } - ASTGEN_MEMBERS_FireEvent; + ASTGEN_MEMBERS_AstFireEvent; bool isDelayed() const { return m_delayed; } }; class AstForeach final : public AstNodeStmt { @@ -3243,7 +3243,7 @@ public: this->arrayp(arrayp); this->addStmtsp(stmtsp); } - ASTGEN_MEMBERS_Foreach; + ASTGEN_MEMBERS_AstForeach; bool isGateOptimizable() const override { return false; } int instrCount() const override { return INSTR_COUNT_BRANCH; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -3266,7 +3266,7 @@ public: } const char* broken() const override; void cloneRelink() override; - ASTGEN_MEMBERS_JumpBlock; + ASTGEN_MEMBERS_AstJumpBlock; int instrCount() const override { return 0; } bool maybePointedTo() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -3286,7 +3286,7 @@ public: AstJumpGo(FileLine* fl, AstJumpLabel* labelp) : ASTGEN_SUPER_JumpGo(fl) , m_labelp{labelp} {} - ASTGEN_MEMBERS_JumpGo; + ASTGEN_MEMBERS_AstJumpGo; const char* broken() const override; void cloneRelink() override; void dump(std::ostream& str) const override; @@ -3310,7 +3310,7 @@ public: AstJumpLabel(FileLine* fl, AstJumpBlock* blockp) : ASTGEN_SUPER_JumpLabel(fl) , m_blockp{blockp} {} - ASTGEN_MEMBERS_JumpLabel; + ASTGEN_MEMBERS_AstJumpLabel; bool maybePointedTo() const override { return true; } const char* broken() const override { BROKEN_RTN(!blockp()->brokeExistsAbove()); @@ -3334,7 +3334,7 @@ public: AstMonitorOff(FileLine* fl, bool off) : ASTGEN_SUPER_MonitorOff(fl) , m_off{off} {} - ASTGEN_MEMBERS_MonitorOff; + ASTGEN_MEMBERS_AstMonitorOff; string verilogKwd() const override { return m_off ? "$monitoroff" : "$monitoron"; } bool isGateOptimizable() const override { return false; } // Though deleted before opt bool isPredictOptimizable() const override { return false; } // Though deleted before opt @@ -3353,7 +3353,7 @@ class AstPrintTimeScale final : public AstNodeStmt { public: explicit AstPrintTimeScale(FileLine* fl) : ASTGEN_SUPER_PrintTimeScale(fl) {} - ASTGEN_MEMBERS_PrintTimeScale; + ASTGEN_MEMBERS_AstPrintTimeScale; void name(const string& name) override { m_name = name; } string name() const override { return m_name; } // * = Var name void dump(std::ostream& str) const override; @@ -3374,7 +3374,7 @@ public: : ASTGEN_SUPER_Release(fl) { this->lhsp(lhsp); } - ASTGEN_MEMBERS_Release; + ASTGEN_MEMBERS_AstRelease; }; class AstRepeat final : public AstNodeStmt { // @astgen op1 := countp : AstNode @@ -3385,7 +3385,7 @@ public: this->countp(countp); this->addStmtsp(stmtsp); } - ASTGEN_MEMBERS_Repeat; + ASTGEN_MEMBERS_AstRepeat; bool isGateOptimizable() const override { return false; } // Not relevant - converted to FOR int instrCount() const override { return INSTR_COUNT_BRANCH; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -3398,7 +3398,7 @@ public: : ASTGEN_SUPER_Return(fl) { this->lhsp(lhsp); } - ASTGEN_MEMBERS_Return; + ASTGEN_MEMBERS_AstReturn; string verilogKwd() const override { return "return"; } bool isBrancher() const override { return true; // SPECIAL: We don't process code after breaks @@ -3420,7 +3420,7 @@ public: this->fmtp(new AstSFormatF{fl, AstSFormatF::NoFormat(), exprsp, missingArgChar}); this->lhsp(lhsp); } - ASTGEN_MEMBERS_SFormat; + ASTGEN_MEMBERS_AstSFormat; const char* broken() const override { BROKEN_RTN(!fmtp()); return nullptr; @@ -3438,7 +3438,7 @@ class AstStop final : public AstNodeStmt { public: AstStop(FileLine* fl, bool maybe) : ASTGEN_SUPER_Stop(fl) {} - ASTGEN_MEMBERS_Stop; + ASTGEN_MEMBERS_AstStop; bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } bool isPure() const override { return false; } // SPECIAL: $display has 'visual' ordering @@ -3455,7 +3455,7 @@ public: : ASTGEN_SUPER_SysFuncAsTask(fl) { this->lhsp(lhsp); } - ASTGEN_MEMBERS_SysFuncAsTask; + ASTGEN_MEMBERS_AstSysFuncAsTask; string verilogKwd() const override { return ""; } bool isGateOptimizable() const override { return true; } bool isPredictOptimizable() const override { return true; } @@ -3471,7 +3471,7 @@ public: : ASTGEN_SUPER_SysIgnore(fl) { this->addExprsp(exprsp); } - ASTGEN_MEMBERS_SysIgnore; + ASTGEN_MEMBERS_AstSysIgnore; string verilogKwd() const override { return "$ignored"; } bool isGateOptimizable() const override { return false; } // Though deleted before opt bool isPredictOptimizable() const override { return false; } // Though deleted before opt @@ -3487,7 +3487,7 @@ public: : ASTGEN_SUPER_SystemT(fl) { this->lhsp(lhsp); } - ASTGEN_MEMBERS_SystemT; + ASTGEN_MEMBERS_AstSystemT; string verilogKwd() const override { return "$system"; } bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } @@ -3511,7 +3511,7 @@ public: this->suffixp(suffixp); this->widthp(widthp); } - ASTGEN_MEMBERS_TimeFormat; + ASTGEN_MEMBERS_AstTimeFormat; string verilogKwd() const override { return "$timeformat"; } bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } @@ -3553,7 +3553,7 @@ public: } void dump(std::ostream& str) const override; int instrCount() const override { return 100; } // Large... - ASTGEN_MEMBERS_TraceDecl; + ASTGEN_MEMBERS_AstTraceDecl; string name() const override { return m_showname; } bool maybePointedTo() const override { return true; } bool hasDType() const override { return true; } @@ -3589,7 +3589,7 @@ public: this->valuep( declp->valuep()->cloneTree(true)); // TODO: maybe use reference to TraceDecl instead? } - ASTGEN_MEMBERS_TraceInc; + ASTGEN_MEMBERS_AstTraceInc; const char* broken() const override { BROKEN_RTN(!declp()->brokeExists()); return nullptr; @@ -3617,7 +3617,7 @@ public: AstTracePopNamePrefix(FileLine* fl, unsigned count) : ASTGEN_SUPER_TracePopNamePrefix(fl) , m_count{count} {} - ASTGEN_MEMBERS_TracePopNamePrefix; + ASTGEN_MEMBERS_AstTracePopNamePrefix; bool same(const AstNode* samep) const override { return false; } unsigned count() const { return m_count; } }; @@ -3627,7 +3627,7 @@ public: AstTracePushNamePrefix(FileLine* fl, const string& prefix) : ASTGEN_SUPER_TracePushNamePrefix(fl) , m_prefix{prefix} {} - ASTGEN_MEMBERS_TracePushNamePrefix; + ASTGEN_MEMBERS_AstTracePushNamePrefix; bool same(const AstNode* samep) const override { return false; } string prefix() const { return m_prefix; } }; @@ -3639,7 +3639,7 @@ public: : ASTGEN_SUPER_UCStmt(fl) { this->addExprsp(exprsp); } - ASTGEN_MEMBERS_UCStmt; + ASTGEN_MEMBERS_AstUCStmt; bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } bool isPure() const override { return false; } @@ -3655,7 +3655,7 @@ public: this->condp(condp); this->addStmtsp(stmtsp); } - ASTGEN_MEMBERS_Wait; + ASTGEN_MEMBERS_AstWait; bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); } bool isTimingControl() const override { return true; } }; @@ -3664,7 +3664,7 @@ class AstWaitFork final : public AstNodeStmt { public: explicit AstWaitFork(FileLine* fl) : ASTGEN_SUPER_WaitFork(fl) {} - ASTGEN_MEMBERS_WaitFork; + ASTGEN_MEMBERS_AstWaitFork; }; class AstWhile final : public AstNodeStmt { // @astgen op1 := precondsp : List[AstNode] @@ -3678,7 +3678,7 @@ public: this->addStmtsp(stmtsp); this->addIncsp(incsp); } - ASTGEN_MEMBERS_While; + ASTGEN_MEMBERS_AstWhile; bool isGateOptimizable() const override { return false; } int instrCount() const override { return INSTR_COUNT_BRANCH; } bool same(const AstNode* /*samep*/) const override { return true; } @@ -3706,7 +3706,7 @@ public: this->valueArgRefp(valueArgRefp); this->exprp(exprp); } - ASTGEN_MEMBERS_With; + ASTGEN_MEMBERS_AstWith; bool same(const AstNode* /*samep*/) const override { return true; } bool hasDType() const override { return true; } const char* broken() const override { @@ -3729,7 +3729,7 @@ public: this->funcrefp(funcrefp); this->exprp(exprp); } - ASTGEN_MEMBERS_WithParse; + ASTGEN_MEMBERS_AstWithParse; bool same(const AstNode* /*samep*/) const override { return true; } }; @@ -3740,7 +3740,7 @@ public: : ASTGEN_SUPER_Assign(fl, lhsp, rhsp, timingControlp) { dtypeFrom(lhsp); } - ASTGEN_MEMBERS_Assign; + ASTGEN_MEMBERS_AstAssign; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; return new AstAssign{fileline(), lhsp, rhsp, controlp}; @@ -3753,7 +3753,7 @@ class AstAssignAlias final : public AstNodeAssign { public: AstAssignAlias(FileLine* fl, AstVarRef* lhsp, AstVarRef* rhsp) : ASTGEN_SUPER_AssignAlias(fl, (AstNode*)lhsp, (AstNode*)rhsp) {} - ASTGEN_MEMBERS_AssignAlias; + ASTGEN_MEMBERS_AstAssignAlias; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { V3ERROR_NA_RETURN(nullptr); } bool brokeLhsMustBeLvalue() const override { return false; } }; @@ -3761,7 +3761,7 @@ class AstAssignDly final : public AstNodeAssign { public: AstAssignDly(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* timingControlp = nullptr) : ASTGEN_SUPER_AssignDly(fl, lhsp, rhsp, timingControlp) {} - ASTGEN_MEMBERS_AssignDly; + ASTGEN_MEMBERS_AstAssignDly; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; return new AstAssignDly{fileline(), lhsp, rhsp, controlp}; @@ -3775,7 +3775,7 @@ class AstAssignForce final : public AstNodeAssign { public: AstAssignForce(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_AssignForce(fl, lhsp, rhsp) {} - ASTGEN_MEMBERS_AssignForce; + ASTGEN_MEMBERS_AstAssignForce; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstAssignForce{this->fileline(), lhsp, rhsp}; } @@ -3786,7 +3786,7 @@ class AstAssignPost final : public AstNodeAssign { public: AstAssignPost(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_AssignPost(fl, lhsp, rhsp) {} - ASTGEN_MEMBERS_AssignPost; + ASTGEN_MEMBERS_AstAssignPost; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstAssignPost(this->fileline(), lhsp, rhsp); } @@ -3797,7 +3797,7 @@ class AstAssignPre final : public AstNodeAssign { public: AstAssignPre(FileLine* fl, AstNode* lhsp, AstNode* rhsp) : ASTGEN_SUPER_AssignPre(fl, lhsp, rhsp) {} - ASTGEN_MEMBERS_AssignPre; + ASTGEN_MEMBERS_AstAssignPre; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstAssignPre(this->fileline(), lhsp, rhsp); } @@ -3810,7 +3810,7 @@ public: : ASTGEN_SUPER_AssignVarScope(fl, lhsp, rhsp) { dtypeFrom(rhsp); } - ASTGEN_MEMBERS_AssignVarScope; + ASTGEN_MEMBERS_AstAssignVarScope; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { return new AstAssignVarScope(this->fileline(), lhsp, rhsp); } @@ -3822,7 +3822,7 @@ class AstAssignW final : public AstNodeAssign { public: AstAssignW(FileLine* fl, AstNode* lhsp, AstNode* rhsp, AstNode* timingControlp = nullptr) : ASTGEN_SUPER_AssignW(fl, lhsp, rhsp, timingControlp) {} - ASTGEN_MEMBERS_AssignW; + ASTGEN_MEMBERS_AstAssignW; AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; return new AstAssignW{fileline(), lhsp, rhsp, controlp}; @@ -3842,7 +3842,7 @@ class AstCCall final : public AstNodeCCall { public: AstCCall(FileLine* fl, AstCFunc* funcp, AstNode* argsp = nullptr) : ASTGEN_SUPER_CCall(fl, funcp, argsp) {} - ASTGEN_MEMBERS_CCall; + ASTGEN_MEMBERS_AstCCall; string selfPointer() const { return m_selfPointer; } void selfPointer(const string& value) { m_selfPointer = value; } @@ -3857,7 +3857,7 @@ public: : ASTGEN_SUPER_CMethodCall(fl, funcp, argsp) { this->fromp(fromp); } - ASTGEN_MEMBERS_CMethodCall; + ASTGEN_MEMBERS_AstCMethodCall; const char* broken() const override { BROKEN_BASE_RTN(AstNodeCCall::broken()); BROKEN_RTN(!fromp()); @@ -3874,7 +3874,7 @@ public: statement(false); } bool hasDType() const override { return true; } - ASTGEN_MEMBERS_CNew; + ASTGEN_MEMBERS_AstCNew; }; // === AstNodeCase === @@ -3892,7 +3892,7 @@ public: AstCase(FileLine* fl, VCaseType casex, AstNode* exprp, AstCaseItem* itemsp) : ASTGEN_SUPER_Case(fl, exprp, itemsp) , m_casex{casex} {} - ASTGEN_MEMBERS_Case; + ASTGEN_MEMBERS_AstCase; string verilogKwd() const override { return casez() ? "casez" : casex() ? "casex" : "case"; } bool same(const AstNode* samep) const override { return m_casex == static_cast(samep)->m_casex; @@ -3919,14 +3919,14 @@ class AstGenCase final : public AstNodeCase { public: AstGenCase(FileLine* fl, AstNode* exprp, AstCaseItem* itemsp) : ASTGEN_SUPER_GenCase(fl, exprp, itemsp) {} - ASTGEN_MEMBERS_GenCase; + ASTGEN_MEMBERS_AstGenCase; }; // === AstNodeCoverOrAssert === class AstAssert final : public AstNodeCoverOrAssert { // @astgen op3 := failsp: List[AstNode] // Statments when propp is failing/falsey public: - ASTGEN_MEMBERS_Assert; + ASTGEN_MEMBERS_AstAssert; AstAssert(FileLine* fl, AstNode* propp, AstNode* passsp, AstNode* failsp, bool immediate, const string& name = "") : ASTGEN_SUPER_Assert(fl, propp, passsp, immediate, name) { @@ -3937,7 +3937,7 @@ class AstAssertIntrinsic final : public AstNodeCoverOrAssert { // A $cast or other compiler inserted assert, that must run even without --assert option // @astgen op3 := failsp: List[AstNode] // Statments when propp is failing/falsey public: - ASTGEN_MEMBERS_AssertIntrinsic; + ASTGEN_MEMBERS_AstAssertIntrinsic; AstAssertIntrinsic(FileLine* fl, AstNode* propp, AstNode* passsp, AstNode* failsp, bool immediate, const string& name = "") : ASTGEN_SUPER_AssertIntrinsic(fl, propp, passsp, immediate, name) { @@ -3947,7 +3947,7 @@ public: class AstCover final : public AstNodeCoverOrAssert { // @astgen op3 := coverincsp: List[AstNode] // Coverage node public: - ASTGEN_MEMBERS_Cover; + ASTGEN_MEMBERS_AstCover; AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, bool immediate, const string& name = "") : ASTGEN_SUPER_Cover(fl, propp, stmtsp, immediate, name) {} @@ -3955,7 +3955,7 @@ public: }; class AstRestrict final : public AstNodeCoverOrAssert { public: - ASTGEN_MEMBERS_Restrict; + ASTGEN_MEMBERS_AstRestrict; AstRestrict(FileLine* fl, AstNode* propp) : ASTGEN_SUPER_Restrict(fl, propp, nullptr, false, "") {} }; @@ -3968,7 +3968,7 @@ public: : ASTGEN_SUPER_FuncRef(fl, false, namep, pinsp) {} AstFuncRef(FileLine* fl, const string& name, AstNode* pinsp) : ASTGEN_SUPER_FuncRef(fl, false, name, pinsp) {} - ASTGEN_MEMBERS_FuncRef; + ASTGEN_MEMBERS_AstFuncRef; bool hasDType() const override { return true; } }; class AstMethodCall final : public AstNodeFTaskRef { @@ -3989,7 +3989,7 @@ public: : ASTGEN_SUPER_MethodCall(fl, false, name, pinsp) { this->fromp(fromp); } - ASTGEN_MEMBERS_MethodCall; + ASTGEN_MEMBERS_AstMethodCall; const char* broken() const override { BROKEN_BASE_RTN(AstNodeFTaskRef::broken()); BROKEN_RTN(!fromp()); @@ -4010,7 +4010,7 @@ class AstNew final : public AstNodeFTaskRef { public: AstNew(FileLine* fl, AstNode* pinsp) : ASTGEN_SUPER_New(fl, false, "new", pinsp) {} - ASTGEN_MEMBERS_New; + ASTGEN_MEMBERS_AstNew; virtual bool cleanOut() const { return true; } bool same(const AstNode* /*samep*/) const override { return true; } bool hasDType() const override { return true; } @@ -4025,7 +4025,7 @@ public: } AstTaskRef(FileLine* fl, const string& name, AstNode* pinsp) : ASTGEN_SUPER_TaskRef(fl, true, name, pinsp) {} - ASTGEN_MEMBERS_TaskRef; + ASTGEN_MEMBERS_AstTaskRef; }; // === AstNodeFor === @@ -4033,7 +4033,7 @@ class AstGenFor final : public AstNodeFor { public: AstGenFor(FileLine* fl, AstNode* initsp, AstNode* condp, AstNode* incsp, AstNode* stmtsp) : ASTGEN_SUPER_GenFor(fl, initsp, condp, incsp, stmtsp) {} - ASTGEN_MEMBERS_GenFor; + ASTGEN_MEMBERS_AstGenFor; }; // === AstNodeIf === @@ -4041,7 +4041,7 @@ class AstGenIf final : public AstNodeIf { public: AstGenIf(FileLine* fl, AstNode* condp, AstNode* thensp, AstNode* elsesp) : ASTGEN_SUPER_GenIf(fl, condp, thensp, elsesp) {} - ASTGEN_MEMBERS_GenIf; + ASTGEN_MEMBERS_AstGenIf; }; class AstIf final : public AstNodeIf { private: @@ -4051,7 +4051,7 @@ private: public: AstIf(FileLine* fl, AstNode* condp, AstNode* thensp = nullptr, AstNode* elsesp = nullptr) : ASTGEN_SUPER_If(fl, condp, thensp, elsesp) {} - ASTGEN_MEMBERS_If; + ASTGEN_MEMBERS_AstIf; bool uniquePragma() const { return m_uniquePragma; } void uniquePragma(bool flag) { m_uniquePragma = flag; } bool unique0Pragma() const { return m_unique0Pragma; } @@ -4066,7 +4066,7 @@ public: AstReadMem(FileLine* fl, bool hex, AstNode* filenamep, AstNode* memp, AstNode* lsbp, AstNode* msbp) : ASTGEN_SUPER_ReadMem(fl, hex, filenamep, memp, lsbp, msbp) {} - ASTGEN_MEMBERS_ReadMem; + ASTGEN_MEMBERS_AstReadMem; string verilogKwd() const override { return (isHex() ? "$readmemh" : "$readmemb"); } const char* cFuncPrefixp() const override { return "VL_READMEM_"; } }; @@ -4075,7 +4075,7 @@ public: AstWriteMem(FileLine* fl, bool hex, AstNode* filenamep, AstNode* memp, AstNode* lsbp, AstNode* msbp) : ASTGEN_SUPER_WriteMem(fl, hex, filenamep, memp, lsbp, msbp) {} - ASTGEN_MEMBERS_WriteMem; + ASTGEN_MEMBERS_AstWriteMem; string verilogKwd() const override { return (isHex() ? "$writememh" : "$writememb"); } const char* cFuncPrefixp() const override { return "VL_WRITEMEM_"; } }; @@ -4085,7 +4085,7 @@ class AstScCtor final : public AstNodeText { public: AstScCtor(FileLine* fl, const string& textp) : ASTGEN_SUPER_ScCtor(fl, textp) {} - ASTGEN_MEMBERS_ScCtor; + ASTGEN_MEMBERS_AstScCtor; bool isPure() const override { return false; } // SPECIAL: User may order w/other sigs bool isOutputter() const override { return true; } }; @@ -4093,7 +4093,7 @@ class AstScDtor final : public AstNodeText { public: AstScDtor(FileLine* fl, const string& textp) : ASTGEN_SUPER_ScDtor(fl, textp) {} - ASTGEN_MEMBERS_ScDtor; + ASTGEN_MEMBERS_AstScDtor; bool isPure() const override { return false; } // SPECIAL: User may order w/other sigs bool isOutputter() const override { return true; } }; @@ -4101,7 +4101,7 @@ class AstScHdr final : public AstNodeText { public: AstScHdr(FileLine* fl, const string& textp) : ASTGEN_SUPER_ScHdr(fl, textp) {} - ASTGEN_MEMBERS_ScHdr; + ASTGEN_MEMBERS_AstScHdr; bool isPure() const override { return false; } // SPECIAL: User may order w/other sigs bool isOutputter() const override { return true; } }; @@ -4109,7 +4109,7 @@ class AstScImp final : public AstNodeText { public: AstScImp(FileLine* fl, const string& textp) : ASTGEN_SUPER_ScImp(fl, textp) {} - ASTGEN_MEMBERS_ScImp; + ASTGEN_MEMBERS_AstScImp; bool isPure() const override { return false; } // SPECIAL: User may order w/other sigs bool isOutputter() const override { return true; } }; @@ -4117,7 +4117,7 @@ class AstScImpHdr final : public AstNodeText { public: AstScImpHdr(FileLine* fl, const string& textp) : ASTGEN_SUPER_ScImpHdr(fl, textp) {} - ASTGEN_MEMBERS_ScImpHdr; + ASTGEN_MEMBERS_AstScImpHdr; bool isPure() const override { return false; } // SPECIAL: User may order w/other sigs bool isOutputter() const override { return true; } }; @@ -4125,7 +4125,7 @@ class AstScInt final : public AstNodeText { public: AstScInt(FileLine* fl, const string& textp) : ASTGEN_SUPER_ScInt(fl, textp) {} - ASTGEN_MEMBERS_ScInt; + ASTGEN_MEMBERS_AstScInt; bool isPure() const override { return false; } // SPECIAL: User may order w/other sigs bool isOutputter() const override { return true; } }; @@ -4135,7 +4135,7 @@ class AstText final : public AstNodeSimpleText { public: AstText(FileLine* fl, const string& textp, bool tracking = false) : ASTGEN_SUPER_Text(fl, textp, tracking) {} - ASTGEN_MEMBERS_Text; + ASTGEN_MEMBERS_AstText; }; class AstTextBlock final : public AstNodeSimpleText { // @astgen op1 := nodesp : List[AstNode] @@ -4145,7 +4145,7 @@ public: bool commas = false) : ASTGEN_SUPER_TextBlock(fl, textp, tracking) , m_commas(commas) {} - ASTGEN_MEMBERS_TextBlock; + ASTGEN_MEMBERS_AstTextBlock; void commas(bool flag) { m_commas = flag; } bool commas() const { return m_commas; } void addText(FileLine* fl, const string& textp, bool tracking = false) { diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 8c86710b8..e353c5b5c 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -208,8 +208,7 @@ class ExtractCyclicComponents final { // The extracted cyclic components std::vector> m_components; // Map from 'variable vertex' -> 'component index' -> 'clone in that component' - std::unordered_map> - m_clones; + std::unordered_map> m_clones; // METHODS @@ -285,7 +284,7 @@ class ExtractCyclicComponents final { void visitMergeSCCs(const DfgVertex& vtx, size_t targetComponent) { // We stop at variable boundaries, which is where we will split the graphs - if (vtx.is() || vtx.is()) return; + if (vtx.is()) return; // Mark visited/move on if already visited if (!m_merged.insert(&vtx).second) return; @@ -312,9 +311,9 @@ class ExtractCyclicComponents final { // Methods for extraction // Retrieve clone of vertex in the given component - DfgVertexLValue& getClone(DfgVertexLValue& vtx, size_t component) { + DfgVertexVar& getClone(DfgVertexVar& vtx, size_t component) { UASSERT_OBJ(m_state.at(&vtx).component != component, &vtx, "Vertex is in that component"); - DfgVertexLValue*& clonep = m_clones[&vtx][component]; + DfgVertexVar*& clonep = m_clones[&vtx][component]; if (!clonep) { DfgGraph& dfg = component == 0 ? m_dfg : *m_components[component - 1]; if (DfgVarPacked* const pVtxp = vtx.cast()) { @@ -322,7 +321,7 @@ class ExtractCyclicComponents final { } else if (DfgVarArray* const aVtxp = vtx.cast()) { clonep = new DfgVarArray{dfg, aVtxp->varp()}; } - UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexLValue' sub-type"); + UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexVar' sub-type"); if (VL_UNLIKELY(m_doExpensiveChecks)) { // Assign component number of clone for later checks m_state @@ -338,30 +337,30 @@ class ExtractCyclicComponents final { return *clonep; } - // Fix up non-variable sources of a DfgVertexLValue that are in a different component, + // Fix up non-variable sources of a DfgVertexVar that are in a different component, // using the provided 'relink' callback template void fixSources(T_Vertex& vtx, std::function relink) { - static_assert(std::is_base_of::value, - "'Vertex' must be a 'DfgVertexLValue'"); + static_assert(std::is_base_of::value, + "'Vertex' must be a 'DfgVertexVar'"); const size_t component = m_state.at(&vtx).component; vtx.forEachSourceEdge([&](DfgEdge& edge, size_t idx) { DfgVertex& source = *edge.sourcep(); - // DfgVertexLValue sources are fixed up by `fixSinks` on those sources - if (source.is() || source.is()) return; + // DfgVertexVar sources are fixed up by `fixSinks` on those sources + if (source.is()) return; const size_t sourceComponent = m_state.at(&source).component; // Same component is OK if (sourceComponent == component) return; // Unlink the source edge (source is reconnected by 'relink' edge.unlinkSource(); // Apply the fixup - DfgVertexLValue& clone = getClone(vtx, sourceComponent); + DfgVertexVar& clone = getClone(vtx, sourceComponent); relink(*(clone.as()), source, idx); }); } // Fix up sinks of given variable vertex that are in a different component - void fixSinks(DfgVertexLValue& vtx) { + void fixSinks(DfgVertexVar& vtx) { const size_t component = m_state.at(&vtx).component; vtx.forEachSinkEdge([&](DfgEdge& edge) { const size_t sinkComponent = m_state.at(edge.sinkp()).component; @@ -400,7 +399,7 @@ class ExtractCyclicComponents final { vtx.forEachSourceEdge([&](DfgEdge& edge, size_t) { DfgVertex& source = *edge.sourcep(); // OK to cross at variables - if (source.is() || source.is()) return; + if (source.is()) return; UASSERT_OBJ(component == m_state.at(&source).component, &vtx, "Component crossing edge without variable involvement"); }); @@ -670,9 +669,7 @@ static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { // if (edge.sourcep()) { string headLabel; - if (vtx.arity() > 1 || vtx.is() || vtx.is()) { - headLabel = vtx.srcName(idx); - } + if (vtx.arity() > 1 || vtx.is()) headLabel = vtx.srcName(idx); dumpDotEdge(os, edge, headLabel); } }); @@ -843,7 +840,7 @@ void DfgEdge::relinkSource(DfgVertex* newSourcep) { // DfgVertex //------------------------------------------------------------------------------ -DfgVertex::DfgVertex(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type) +DfgVertex::DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep) : m_filelinep{flp} , m_dtypep{dtypep} , m_type{type} { @@ -897,7 +894,7 @@ V3Hash DfgVertex::hash(HashCache& cache) const { result += selfHash(); // Variables are defined by themselves, so there is no need to hash the sources. This // enables sound hashing of graphs circular only through variables, which we rely on. - if (!is() && !is()) { + if (!is()) { forEachSource([&result, &cache](const DfgVertex& src) { result += src.hash(cache); }); } } @@ -930,7 +927,6 @@ void DfgVertex::replaceWith(DfgVertex* newSorucep) { //------------------------------------------------------------------------------ // DfgVarPacked ---------- -void DfgVarPacked::accept(DfgVisitor& visitor) { visitor.visit(this); } bool DfgVarPacked::selfEquals(const DfgVertex& that) const { if (const DfgVarPacked* otherp = that.cast()) { @@ -943,7 +939,6 @@ bool DfgVarPacked::selfEquals(const DfgVertex& that) const { V3Hash DfgVarPacked::selfHash() const { return V3Hasher::uncachedHash(varp()); } // DfgVarPacked ---------- -void DfgVarArray::accept(DfgVisitor& visitor) { visitor.visit(this); } bool DfgVarArray::selfEquals(const DfgVertex& that) const { if (const DfgVarArray* otherp = that.cast()) { @@ -956,7 +951,6 @@ bool DfgVarArray::selfEquals(const DfgVertex& that) const { V3Hash DfgVarArray::selfHash() const { return V3Hasher::uncachedHash(varp()); } // DfgConst ---------- -void DfgConst::accept(DfgVisitor& visitor) { visitor.visit(this); } bool DfgConst::selfEquals(const DfgVertex& that) const { if (const DfgConst* otherp = that.cast()) { @@ -971,12 +965,4 @@ V3Hash DfgConst::selfHash() const { return V3Hasher::uncachedHash(m_constp); } // DfgVisitor //------------------------------------------------------------------------------ -void DfgVisitor::visit(DfgVarPacked* vtxp) { visit(static_cast(vtxp)); } -void DfgVisitor::visit(DfgVarArray* vtxp) { visit(static_cast(vtxp)); } -void DfgVisitor::visit(DfgConst* vtxp) { visit(static_cast(vtxp)); } - -//------------------------------------------------------------------------------ -// 'astgen' generated definitions -//------------------------------------------------------------------------------ - -#include "V3Dfg__gen_definitions.h" +#include "V3Dfg__gen_visitor_defns.h" // From ./astgen diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 1cfe8768e..e0745dad2 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -21,7 +21,7 @@ // than the linked list based structures used by V3Graph. // // A bulk of the DfgVertex sub-types are generated by astgen, and are -// analogous to the correspondign AstNode sub-types. +// analogous to the corresponding AstNode sub-types. // // See also the internals documentation docs/internals.rst // @@ -39,6 +39,8 @@ #include "V3Hasher.h" #include "V3List.h" +#include "V3Dfg__gen_forward_class_decls.h" // From ./astgen + #include #include #include @@ -46,7 +48,10 @@ #include #include -class DfgVertex; +#ifndef VL_NOT_FINAL +#define VL_NOT_FINAL // This #define fixes broken code folding in the CLion IDE +#endif + class DfgEdge; class DfgVisitor; @@ -63,6 +68,25 @@ struct std::hash> final { } }; +//------------------------------------------------------------------------------ +// Dataflow vertex type enum +//------------------------------------------------------------------------------ + +class VDfgType final { +public: +#include "V3Dfg__gen_type_enum.h" // From ./astgen + enum en m_e; + VDfgType() = default; + // cppcheck-suppress noExplicitConstructor + constexpr VDfgType(en _e) + : m_e{_e} {} + constexpr operator en() const { return m_e; } +}; +constexpr bool operator==(VDfgType lhs, VDfgType rhs) { return lhs.m_e == rhs.m_e; } +constexpr bool operator==(VDfgType lhs, VDfgType::en rhs) { return lhs.m_e == rhs; } +constexpr bool operator==(VDfgType::en lhs, VDfgType rhs) { return lhs == rhs.m_e; } +inline std::ostream& operator<<(std::ostream& os, const VDfgType& t) { return os << t.ascii(); } + //------------------------------------------------------------------------------ // Dataflow graph //------------------------------------------------------------------------------ @@ -191,9 +215,6 @@ public: // Dataflow graph vertex //------------------------------------------------------------------------------ -// Reuse the generated type constants -using DfgType = VNType; - // Base data flow graph vertex class DfgVertex VL_NOT_FINAL { friend class DfgGraph; @@ -206,10 +227,10 @@ protected: DfgEdge* m_sinksp = nullptr; // List of sinks of this vertex FileLine* const m_filelinep; // Source location AstNodeDType* m_dtypep = nullptr; // Data type of the result of this vertex - const DfgType m_type; + const VDfgType m_type; // CONSTRUCTOR - DfgVertex(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type); + DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep); public: virtual ~DfgVertex(); @@ -277,7 +298,7 @@ public: // The data type of the result of the nodes AstNodeDType* dtypep() const { return m_dtypep; } // The type of this vertex - DfgType type() const { return m_type; } + VDfgType type() const { return m_type; } // Width of result uint32_t width() const { @@ -396,11 +417,18 @@ public: string warnMore() const { return fileline()->warnMore(); } string warnOther() const { return fileline()->warnOther(); } +private: + // For internal use only. + // Note: specializations for particular vertex types are provided by 'astgen' + template + inline static bool privateTypeTest(const DfgVertex* nodep); + +public: // Subtype test template bool is() const { static_assert(std::is_base_of::value, "'T' must be a subtype of DfgVertex"); - return m_type == T::dfgType(); + return privateTypeTest::type>(this); } // Ensure subtype, then cast to that type @@ -436,367 +464,8 @@ public: virtual const string srcName(size_t idx) const = 0; }; -// DfgVertices are, well ... DfgVertices -template <> -constexpr bool DfgVertex::is() const { - return true; -} -template <> -constexpr DfgVertex* DfgVertex::as() { - return this; -} -template <> -constexpr const DfgVertex* DfgVertex::as() const { - return this; -} -template <> -constexpr DfgVertex* DfgVertex::cast() { - return this; -} -template <> -constexpr const DfgVertex* DfgVertex::cast() const { - return this; -} - -template -class DfgVertexWithArity VL_NOT_FINAL : public DfgVertex { - static_assert(1 <= Arity && Arity <= 4, "Arity must be between 1 and 4 inclusive"); - - std::array m_srcs; // Source edges - -protected: - DfgVertexWithArity(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type) - : DfgVertex{dfg, flp, dtypep, type} { - // Initialize source edges - for (size_t i = 0; i < Arity; ++i) m_srcs[i].init(this); - } - - ~DfgVertexWithArity() override = default; - -public: - std::pair sourceEdges() override { // - return {m_srcs.data(), Arity}; - } - std::pair sourceEdges() const override { - return {m_srcs.data(), Arity}; - } - - template - DfgEdge* sourceEdge() { - static_assert(Index < Arity, "Source index out of range"); - return &m_srcs[Index]; - } - - template - const DfgEdge* sourceEdge() const { - static_assert(Index < Arity, "Source index out of range"); - return &m_srcs[Index]; - } - - template - DfgVertex* source() const { - static_assert(Index < Arity, "Source index out of range"); - return m_srcs[Index].sourcep(); - } - - template - void relinkSource(DfgVertex* newSourcep) { - static_assert(Index < Arity, "Source index out of range"); - UASSERT_OBJ(m_srcs[Index].sinkp() == this, this, "Inconsistent"); - m_srcs[Index].relinkSource(newSourcep); - } - - // Named source getter/setter for unary vertices - template - typename std::enable_if::type srcp() const { - static_assert(A == Arity, "Should not be changed"); - return source<0>(); - } - template - typename std::enable_if::type srcp(DfgVertex* vtxp) { - static_assert(A == Arity, "Should not be changed"); - relinkSource<0>(vtxp); - } - - // Named source getter/setter for binary vertices - template - typename std::enable_if::type lhsp() const { - static_assert(A == Arity, "Should not be changed"); - return source<0>(); - } - template - typename std::enable_if::type lhsp(DfgVertex* vtxp) { - static_assert(A == Arity, "Should not be changed"); - relinkSource<0>(vtxp); - } - - template - typename std::enable_if::type rhsp() const { - static_assert(A == Arity, "Should not be changed"); - return source<1>(); - } - template - typename std::enable_if::type rhsp(DfgVertex* vtxp) { - static_assert(A == Arity, "Should not be changed"); - relinkSource<1>(vtxp); - } -}; - -class DfgVertexVariadic VL_NOT_FINAL : public DfgVertex { - DfgEdge* m_srcsp; // The source edges - uint32_t m_srcCnt = 0; // Number of sources used - uint32_t m_srcCap; // Number of sources allocated - - // Allocate a new source edge array - DfgEdge* allocSources(size_t n) { - DfgEdge* const srcsp = new DfgEdge[n]; - for (size_t i = 0; i < n; ++i) srcsp[i].init(this); - return srcsp; - } - - // Double the capacity of m_srcsp - void growSources() { - m_srcCap *= 2; - DfgEdge* const newsp = allocSources(m_srcCap); - for (size_t i = 0; i < m_srcCnt; ++i) { - DfgEdge* const oldp = m_srcsp + i; - // Skip over unlinked source edge - if (!oldp->sourcep()) continue; - // New edge driven from the same vertex as the old edge - newsp[i].relinkSource(oldp->sourcep()); - // Unlink the old edge, it will be deleted - oldp->unlinkSource(); - } - // Delete old source edges - delete[] m_srcsp; - // Keep hold of new source edges - m_srcsp = newsp; - } - -protected: - DfgVertexVariadic(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep, DfgType type, - uint32_t initialCapacity = 1) - : DfgVertex{dfg, flp, dtypep, type} - , m_srcsp{allocSources(initialCapacity)} - , m_srcCap{initialCapacity} {} - - ~DfgVertexVariadic() override { delete[] m_srcsp; }; - - DfgEdge* addSource() { - if (m_srcCnt == m_srcCap) growSources(); - return m_srcsp + m_srcCnt++; - } - - void resetSources() { - // #ifdef VL_DEBUG TODO: DEBUG ONLY - for (uint32_t i = 0; i < m_srcCnt; ++i) { - UASSERT_OBJ(!m_srcsp[i].sourcep(), m_srcsp[i].sourcep(), "Connected source"); - } - // #endif - m_srcCnt = 0; - } - -public: - DfgEdge* sourceEdge(size_t idx) const { return &m_srcsp[idx]; } - DfgVertex* source(size_t idx) const { return m_srcsp[idx].sourcep(); } - - std::pair sourceEdges() override { return {m_srcsp, m_srcCnt}; } - std::pair sourceEdges() const override { return {m_srcsp, m_srcCnt}; } -}; - -//------------------------------------------------------------------------------ -// Vertex classes -//------------------------------------------------------------------------------ - -class DfgVertexLValue VL_NOT_FINAL : public DfgVertexVariadic { - AstVar* const m_varp; // The AstVar associated with this vertex (not owned by this vertex) - bool m_hasModRefs = false; // This AstVar is referenced outside the DFG, but in the module - bool m_hasExtRefs = false; // This AstVar is referenced from outside the module - -public: - DfgVertexLValue(DfgGraph& dfg, DfgType type, AstVar* varp, uint32_t initialCapacity) - : DfgVertexVariadic{dfg, varp->fileline(), dtypeFor(varp), type, initialCapacity} - , m_varp{varp} {} - - AstVar* varp() const { return m_varp; } - bool hasModRefs() const { return m_hasModRefs; } - void setHasModRefs() { m_hasModRefs = true; } - bool hasExtRefs() const { return m_hasExtRefs; } - void setHasExtRefs() { m_hasExtRefs = true; } - bool hasRefs() const { return m_hasModRefs || m_hasExtRefs; } - - // Variable cannot be removed, even if redundant in the DfgGraph (might be used externally) - bool keep() const { - // Keep if referenced outside this module - if (hasExtRefs()) return true; - // Keep if traced - if (v3Global.opt.trace() && varp()->isTrace()) return true; - // Keep if public - if (varp()->isSigPublic()) return true; - // Otherwise it can be removed - return false; - } -}; - -class DfgVarPacked final : public DfgVertexLValue { - friend class DfgVertex; - friend class DfgVisitor; - - using DriverData = std::pair; - - std::vector m_driverData; // Additional data associate with each driver - - void accept(DfgVisitor& visitor) override; - bool selfEquals(const DfgVertex& that) const override; - V3Hash selfHash() const override; - static constexpr DfgType dfgType() { return DfgType::atVar; }; - -public: - DfgVarPacked(DfgGraph& dfg, AstVar* varp) - : DfgVertexLValue{dfg, dfgType(), varp, 1u} {} - - bool isDrivenByDfg() const { return arity() > 0; } - bool isDrivenFullyByDfg() const { return arity() == 1 && source(0)->dtypep() == dtypep(); } - - void addDriver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp) { - m_driverData.emplace_back(flp, lsb); - DfgVertexVariadic::addSource()->relinkSource(vtxp); - } - - void resetSources() { - m_driverData.clear(); - DfgVertexVariadic::resetSources(); - } - - // Remove undriven sources - void packSources() { - // Grab and reset the driver data - std::vector driverData{std::move(m_driverData)}; - - // Grab and unlink the sources - std::vector sources{arity()}; - forEachSourceEdge([&](DfgEdge& edge, size_t idx) { - sources[idx] = edge.sourcep(); - edge.unlinkSource(); - }); - DfgVertexVariadic::resetSources(); - - // Add back the driven sources - for (size_t i = 0; i < sources.size(); ++i) { - if (!sources[i]) continue; - addDriver(driverData[i].first, driverData[i].second, sources[i]); - } - } - - FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; } - uint32_t driverLsb(size_t idx) const { return m_driverData[idx].second; } - - const string srcName(size_t idx) const override { - return isDrivenFullyByDfg() ? "" : cvtToStr(driverLsb(idx)); - } -}; - -class DfgVarArray final : public DfgVertexLValue { - friend class DfgVertex; - friend class DfgVisitor; - - using DriverData = std::pair; - - std::vector m_driverData; // Additional data associate with each driver - - void accept(DfgVisitor& visitor) override; - bool selfEquals(const DfgVertex& that) const override; - V3Hash selfHash() const override; - static constexpr DfgType dfgType() { return DfgType::atUnpackArrayDType; }; // TODO: gross - -public: - DfgVarArray(DfgGraph& dfg, AstVar* varp) - : DfgVertexLValue{dfg, dfgType(), varp, 4u} { - UASSERT_OBJ(VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType), varp, "Non array DfgVarArray"); - } - - bool isDrivenByDfg() const { return arity() > 0; } - - void addDriver(FileLine* flp, uint32_t index, DfgVertex* vtxp) { - m_driverData.emplace_back(flp, index); - DfgVertexVariadic::addSource()->relinkSource(vtxp); - } - - void resetSources() { - m_driverData.clear(); - DfgVertexVariadic::resetSources(); - } - - // Remove undriven sources - void packSources() { - // Grab and reset the driver data - std::vector driverData{std::move(m_driverData)}; - - // Grab and unlink the sources - std::vector sources{arity()}; - forEachSourceEdge([&](DfgEdge& edge, size_t idx) { - sources[idx] = edge.sourcep(); - edge.unlinkSource(); - }); - DfgVertexVariadic::resetSources(); - - // Add back the driven sources - for (size_t i = 0; i < sources.size(); ++i) { - if (!sources[i]) continue; - addDriver(driverData[i].first, driverData[i].second, sources[i]); - } - } - - FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; } - uint32_t driverIndex(size_t idx) const { return m_driverData[idx].second; } - - DfgVertex* driverAt(size_t idx) const { - const DfgEdge* const edgep = findSourceEdge([=](const DfgEdge&, size_t i) { // - return driverIndex(i) == idx; - }); - return edgep ? edgep->sourcep() : nullptr; - } - - const string srcName(size_t idx) const override { return cvtToStr(driverIndex(idx)); } -}; - -class DfgConst final : public DfgVertex { - friend class DfgVertex; - friend class DfgVisitor; - - AstConst* const m_constp; // The AstConst associated with this vertex (owned by this vertex) - - void accept(DfgVisitor& visitor) override; - bool selfEquals(const DfgVertex& that) const override; - V3Hash selfHash() const override; - static constexpr DfgType dfgType() { return DfgType::atConst; }; - -public: - DfgConst(DfgGraph& dfg, AstConst* constp) - : DfgVertex{dfg, constp->fileline(), dtypeFor(constp), dfgType()} - , m_constp{constp} {} - - ~DfgConst() override { VL_DO_DANGLING(m_constp->deleteTree(), m_constp); } - - AstConst* constp() const { return m_constp; } - V3Number& num() const { return m_constp->num(); } - - uint32_t toU32() const { return num().toUInt(); } - int32_t toI32() const { return num().toSInt(); } - - bool isZero() const { return num().isEqZero(); } - bool isOnes() const { return num().isEqAllOnes(width()); } - - std::pair sourceEdges() override { return {nullptr, 0}; } - std::pair sourceEdges() const override { return {nullptr, 0}; } - const string srcName(size_t) const override { // LCOV_EXCL_START - VL_UNREACHABLE; - return ""; - } // LCOV_EXCL_STOP -}; - -// The rest of the DfgVertex subclasses are generated by 'astgen' from AstNodeMath nodes -#include "V3Dfg__gen_vertex_classes.h" +// Specialisations of privateTypeTest +#include "V3Dfg__gen_type_tests.h" // From ./astgen //------------------------------------------------------------------------------ // Dfg vertex visitor @@ -807,10 +476,8 @@ public: // Dispatch to most specific 'visit' method on 'vtxp' void iterate(DfgVertex* vtxp) { vtxp->accept(*this); } - virtual void visit(DfgVarPacked* vtxp); - virtual void visit(DfgVarArray* vtxp); - virtual void visit(DfgConst* vtxp); -#include "V3Dfg__gen_visitor_decls.h" + virtual void visit(DfgVertex* nodep) = 0; +#include "V3Dfg__gen_visitor_decls.h" // From ./astgen }; //------------------------------------------------------------------------------ @@ -937,6 +604,180 @@ Vertex* DfgVertex::findSink() const { return findSink([](const Vertex&) { return true; }); } +//------------------------------------------------------------------------------ +// DfgVertex sub-types follow +//------------------------------------------------------------------------------ + +// Include macros generated by 'astgen'. These include DFGGEN_MEMBERS_ +// for each DfgVertex sub-type. The generated members include boilerplate +// methods related to cloning, visitor dispatch, and other functionality. +// For precise details please read the generated macros. +#include "V3Dfg__gen_macros.h" + +//------------------------------------------------------------------------------ +// Implementation of dataflow graph vertices with a fixed number of sources +//------------------------------------------------------------------------------ + +template +class DfgVertexWithArity VL_NOT_FINAL : public DfgVertex { + static_assert(1 <= Arity && Arity <= 4, "Arity must be between 1 and 4 inclusive"); + + std::array m_srcs; // Source edges + +protected: + DfgVertexWithArity(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep) + : DfgVertex{dfg, type, flp, dtypep} { + // Initialize source edges + for (size_t i = 0; i < Arity; ++i) m_srcs[i].init(this); + } + + ~DfgVertexWithArity() override = default; + +public: + std::pair sourceEdges() final override { // + return {m_srcs.data(), Arity}; + } + std::pair sourceEdges() const final override { + return {m_srcs.data(), Arity}; + } + + template + DfgEdge* sourceEdge() { + static_assert(Index < Arity, "Source index out of range"); + return &m_srcs[Index]; + } + + template + const DfgEdge* sourceEdge() const { + static_assert(Index < Arity, "Source index out of range"); + return &m_srcs[Index]; + } + + template + DfgVertex* source() const { + static_assert(Index < Arity, "Source index out of range"); + return m_srcs[Index].sourcep(); + } + + template + void relinkSource(DfgVertex* newSourcep) { + static_assert(Index < Arity, "Source index out of range"); + UASSERT_OBJ(m_srcs[Index].sinkp() == this, this, "Inconsistent"); + m_srcs[Index].relinkSource(newSourcep); + } +}; + +class DfgVertexUnary VL_NOT_FINAL : public DfgVertexWithArity<1> { +protected: + DfgVertexUnary(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep) + : DfgVertexWithArity<1>{dfg, type, flp, dtypep} {} + +public: + ASTGEN_MEMBERS_DfgVertexUnary; + + // Named getter/setter for sources + DfgVertex* srcp() const { return source<0>(); } + void srcp(DfgVertex* vtxp) { relinkSource<0>(vtxp); } +}; + +class DfgVertexBinary VL_NOT_FINAL : public DfgVertexWithArity<2> { +protected: + DfgVertexBinary(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep) + : DfgVertexWithArity<2>{dfg, type, flp, dtypep} {} + +public: + ASTGEN_MEMBERS_DfgVertexBinary; + + // Named getter/setter for sources + DfgVertex* lhsp() const { return source<0>(); } + void lhsp(DfgVertex* vtxp) { relinkSource<0>(vtxp); } + DfgVertex* rhsp() const { return source<1>(); } + void rhsp(DfgVertex* vtxp) { relinkSource<1>(vtxp); } +}; + +class DfgVertexTernary VL_NOT_FINAL : public DfgVertexWithArity<3> { +protected: + DfgVertexTernary(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep) + : DfgVertexWithArity<3>{dfg, type, flp, dtypep} {} + +public: + ASTGEN_MEMBERS_DfgVertexTernary; +}; + +//------------------------------------------------------------------------------ +// Implementation of dataflow graph vertices with a variable number of sources +//------------------------------------------------------------------------------ + +class DfgVertexVariadic VL_NOT_FINAL : public DfgVertex { + DfgEdge* m_srcsp; // The source edges + uint32_t m_srcCnt = 0; // Number of sources used + uint32_t m_srcCap; // Number of sources allocated + + // Allocate a new source edge array + DfgEdge* allocSources(size_t n) { + DfgEdge* const srcsp = new DfgEdge[n]; + for (size_t i = 0; i < n; ++i) srcsp[i].init(this); + return srcsp; + } + + // Double the capacity of m_srcsp + void growSources() { + m_srcCap *= 2; + DfgEdge* const newsp = allocSources(m_srcCap); + for (size_t i = 0; i < m_srcCnt; ++i) { + DfgEdge* const oldp = m_srcsp + i; + // Skip over unlinked source edge + if (!oldp->sourcep()) continue; + // New edge driven from the same vertex as the old edge + newsp[i].relinkSource(oldp->sourcep()); + // Unlink the old edge, it will be deleted + oldp->unlinkSource(); + } + // Delete old source edges + delete[] m_srcsp; + // Keep hold of new source edges + m_srcsp = newsp; + } + +protected: + DfgVertexVariadic(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep, + uint32_t initialCapacity = 1) + : DfgVertex{dfg, type, flp, dtypep} + , m_srcsp{allocSources(initialCapacity)} + , m_srcCap{initialCapacity} {} + + ~DfgVertexVariadic() override { delete[] m_srcsp; }; + + DfgEdge* addSource() { + if (m_srcCnt == m_srcCap) growSources(); + return m_srcsp + m_srcCnt++; + } + + void resetSources() { + // #ifdef VL_DEBUG TODO: DEBUG ONLY + for (uint32_t i = 0; i < m_srcCnt; ++i) { + UASSERT_OBJ(!m_srcsp[i].sourcep(), m_srcsp[i].sourcep(), "Connected source"); + } + // #endif + m_srcCnt = 0; + } + +public: + ASTGEN_MEMBERS_DfgVertexVariadic; + + DfgEdge* sourceEdge(size_t idx) const { return &m_srcsp[idx]; } + DfgVertex* source(size_t idx) const { return m_srcsp[idx].sourcep(); } + + std::pair sourceEdges() override { return {m_srcsp, m_srcCnt}; } + std::pair sourceEdges() const override { return {m_srcsp, m_srcCnt}; } +}; + +// DfgVertex subclasses +#include "V3DfgVertices.h" + +// The rest of the DfgVertex subclasses are generated by 'astgen' from AstNodeMath nodes +#include "V3Dfg__gen_auto_classes.h" + bool DfgVertex::isZero() const { if (const DfgConst* const constp = cast()) return constp->isZero(); return false; diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index c63814f90..32344bdff 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -41,8 +41,8 @@ namespace { // Create a DfgVertex out of a AstNodeMath. For most AstNodeMath subtypes, this can be done // automatically. For the few special cases, we provide specializations below -template -Vertex* makeVertex(const AstForDfg* nodep, DfgGraph& dfg) { +template +Vertex* makeVertex(const Node* nodep, DfgGraph& dfg) { return new Vertex{dfg, nodep->fileline(), DfgVertex::dtypeFor(nodep)}; } @@ -51,22 +51,22 @@ Vertex* makeVertex(const AstForDfg* nodep, DfgGraph& dfg) { // LCOV_EXCL_START // AstCCast changes width, but should not exists where DFG optimization is currently invoked template <> -DfgCCast* makeVertex(const AstCCast*, DfgGraph&) { +DfgCCast* makeVertex(const AstCCast*, DfgGraph&) { return nullptr; } // Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway template <> -DfgAtoN* makeVertex(const AstAtoN*, DfgGraph&) { +DfgAtoN* makeVertex(const AstAtoN*, DfgGraph&) { return nullptr; } // Unhandled in DfgToAst, but also operates on strings which we don't optimize anyway template <> -DfgCompareNN* makeVertex(const AstCompareNN*, DfgGraph&) { +DfgCompareNN* makeVertex(const AstCompareNN*, DfgGraph&) { return nullptr; } // Unhandled in DfgToAst, but also operates on unpacked arrays which we don't optimize anyway template <> -DfgSliceSel* makeVertex(const AstSliceSel*, DfgGraph&) { +DfgSliceSel* makeVertex(const AstSliceSel*, DfgGraph&) { return nullptr; } // LCOV_EXCL_STOP @@ -105,11 +105,11 @@ class AstToDfgVisitor final : public VNVisitor { m_uncommittedVertices.clear(); } - DfgVertexLValue* getNet(AstVar* varp) { + DfgVertexVar* getNet(AstVar* varp) { if (!varp->user1p()) { - // Note DfgVertexLValue vertices are not added to m_uncommittedVertices, because we + // Note DfgVertexVar vertices are not added to m_uncommittedVertices, because we // want to hold onto them via AstVar::user1p, and the AstVar might be referenced via - // multiple AstVarRef instances, so we will never revert a DfgVertexLValue once + // multiple AstVarRef instances, so we will never revert a DfgVertexVar once // created. We will delete unconnected variable vertices at the end. if (VN_IS(varp->dtypep()->skipRefp(), UnpackArrayDType)) { DfgVarArray* const vtxp = new DfgVarArray{*m_dfgp, varp}; @@ -122,7 +122,7 @@ class AstToDfgVisitor final : public VNVisitor { varp->user1p(vtxp); } } - return varp->user1u().to(); + return varp->user1u().to(); } DfgVertex* getVertex(AstNode* nodep) { diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index 9666b203c..de08f1060 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -42,8 +42,8 @@ namespace { // Create an AstNodeMath out of a DfgVertex. For most AstNodeMath subtypes, this can be done // automatically. For the few special cases, we provide specializations below -template -Node* makeNode(const DfgForAst* vtxp, Ops... ops) { +template +Node* makeNode(const Vertex* vtxp, Ops... ops) { Node* const nodep = new Node{vtxp->fileline(), ops...}; UASSERT_OBJ(nodep->width() == static_cast(vtxp->width()), vtxp, "Incorrect width in AstNode created from DfgVertex " @@ -55,31 +55,31 @@ Node* makeNode(const DfgForAst* vtxp, Ops... ops) { // Vertices needing special conversion template <> -AstExtend* makeNode( // +AstExtend* makeNode( // const DfgExtend* vtxp, AstNodeMath* op1) { return new AstExtend{vtxp->fileline(), op1, static_cast(vtxp->width())}; } template <> -AstExtendS* makeNode( // +AstExtendS* makeNode( // const DfgExtendS* vtxp, AstNodeMath* op1) { return new AstExtendS{vtxp->fileline(), op1, static_cast(vtxp->width())}; } template <> -AstShiftL* makeNode( // +AstShiftL* makeNode( // const DfgShiftL* vtxp, AstNodeMath* op1, AstNodeMath* op2) { return new AstShiftL{vtxp->fileline(), op1, op2, static_cast(vtxp->width())}; } template <> -AstShiftR* makeNode( // +AstShiftR* makeNode( // const DfgShiftR* vtxp, AstNodeMath* op1, AstNodeMath* op2) { return new AstShiftR{vtxp->fileline(), op1, op2, static_cast(vtxp->width())}; } template <> -AstShiftRS* makeNode( // +AstShiftRS* makeNode( // const DfgShiftRS* vtxp, AstNodeMath* op1, AstNodeMath* op2) { return new AstShiftRS{vtxp->fileline(), op1, op2, static_cast(vtxp->width())}; } @@ -88,20 +88,21 @@ AstShiftRS* makeNode( // // Currently unhandled nodes - see corresponding AstToDfg functions // LCOV_EXCL_START template <> -AstCCast* makeNode(const DfgCCast* vtxp, AstNodeMath*) { +AstCCast* makeNode(const DfgCCast* vtxp, AstNodeMath*) { vtxp->v3fatalSrc("not implemented"); } template <> -AstAtoN* makeNode(const DfgAtoN* vtxp, AstNodeMath*) { +AstAtoN* makeNode(const DfgAtoN* vtxp, AstNodeMath*) { vtxp->v3fatalSrc("not implemented"); } template <> -AstCompareNN* makeNode(const DfgCompareNN* vtxp, +AstCompareNN* +makeNode(const DfgCompareNN* vtxp, AstNodeMath*, AstNodeMath*) { vtxp->v3fatalSrc("not implemented"); } template <> -AstSliceSel* makeNode( +AstSliceSel* makeNode( const DfgSliceSel* vtxp, AstNodeMath*, AstNodeMath*, AstNodeMath*) { vtxp->v3fatalSrc("not implemented"); } @@ -215,8 +216,7 @@ class DfgToAstVisitor final : DfgVisitor { // Inline vertices that drive only a single node, or are special if (!vtx.hasMultipleSinks()) return true; if (vtx.is()) return true; - if (vtx.is()) return true; - if (vtx.is()) return true; + if (vtx.is()) return true; if (const DfgArraySel* const selp = vtx.cast()) { return selp->bitp()->is(); } diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 0c975fa98..bd192416a 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -154,7 +154,7 @@ void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { void V3DfgPasses::removeUnused(DfgGraph& dfg) { const auto processVertex = [&](DfgVertex& vtx) { // Keep variables - if (vtx.is() || vtx.is()) return false; + if (vtx.is()) return false; // Keep if it has sinks if (vtx.hasSinks()) return false; // Unlink and delete vertex diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 8ba02b5e2..e02671d64 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -115,7 +115,7 @@ class V3DfgPeephole final : public DfgVisitor { DfgConst* makeZero(FileLine* flp, uint32_t width) { return makeConst(flp, width, 0); } // Transformations that apply to all commutative binary vertices - void commutativeBinary(DfgVertexWithArity<2>* vtxp) { + void commutativeBinary(DfgVertexBinary* vtxp) { DfgVertex* const lhsp = vtxp->source<0>(); DfgVertex* const rhsp = vtxp->source<1>(); // Ensure Const is on left-hand side to simplify other patterns @@ -138,9 +138,9 @@ class V3DfgPeephole final : public DfgVisitor { } // If both sides are variable references, order the side in some defined way. This allows // CSE to later merge 'a op b' with 'b op a'. - if (lhsp->is() && rhsp->is()) { - AstVar* const lVarp = lhsp->as()->varp(); - AstVar* const rVarp = rhsp->as()->varp(); + if (lhsp->is() && rhsp->is()) { + AstVar* const lVarp = lhsp->as()->varp(); + AstVar* const rVarp = rhsp->as()->varp(); if (lVarp->name() > rVarp->name()) { APPLYING(SWAP_VAR_IN_COMMUTATIVE_BINARY) { vtxp->lhsp(rhsp); @@ -152,12 +152,12 @@ class V3DfgPeephole final : public DfgVisitor { } // Transformations that apply to all associative binary vertices - void associativeBinary(DfgVertexWithArity<2>* vtxp) { + void associativeBinary(DfgVertexBinary* vtxp) { DfgVertex* const lhsp = vtxp->lhsp(); // Make associative trees right leaning (for better CSE opportunities) if (lhsp->type() == vtxp->type() && !lhsp->hasMultipleSinks()) { - DfgVertexWithArity<2>* const lBinp = static_cast*>(lhsp); + DfgVertexBinary* const lBinp = lhsp->as(); APPLYING(RIGHT_LEANING_ASSOC) { vtxp->replaceWith(lBinp); vtxp->lhsp(lBinp->rhsp()); @@ -242,7 +242,7 @@ class V3DfgPeephole final : public DfgVisitor { newRhsp->rhsp(concatp->rhsp()); // The replacement Vertex - DfgVertexWithArity<2>* const replacementp + DfgVertexBinary* const replacementp = std::is_same::value ? new DfgAnd{m_dfg, concatp->fileline(), m_bitDType} : nullptr; @@ -334,7 +334,7 @@ class V3DfgPeephole final : public DfgVisitor { rReducep->srcp(concatp->rhsp()); // Bitwise reduce the results - DfgVertexWithArity<2>* const replacementp = new Bitwise{m_dfg, flp, m_bitDType}; + Bitwise* const replacementp = new Bitwise{m_dfg, flp, m_bitDType}; replacementp->lhsp(lReducep); replacementp->rhsp(rReducep); vtxp->replaceWith(replacementp); @@ -362,7 +362,7 @@ class V3DfgPeephole final : public DfgVisitor { } } - void optimizeShiftRHS(DfgVertexWithArity<2>* vtxp) { + void optimizeShiftRHS(DfgVertexBinary* vtxp) { if (const DfgConcat* const concatp = vtxp->rhsp()->cast()) { if (concatp->lhsp()->isZero()) { // Drop redundant zero extension APPLYING(REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) { // @@ -1330,12 +1330,12 @@ class V3DfgPeephole final : public DfgVisitor { // Process one vertex. Return true if graph changed bool processVertex(DfgVertex& vtx) { - // Keep DfgVertexLValue vertices in this pass. We will remove them later if they become + // Keep DfgVertexVar vertices in this pass. We will remove them later if they become // redundant. We want to keep the original variables for non-var vertices that drive // multiple sinks (otherwise we would need to introduce a temporary, but it is better for // debugging to keep the original variable name, if one is available), so we can't remove // redundant variables here. - const bool keep = vtx.is() || vtx.is(); + const bool keep = vtx.is(); // If it has no sinks (unused), we can remove it if (!keep && !vtx.hasSinks()) { diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h new file mode 100644 index 000000000..f504bcbd3 --- /dev/null +++ b/src/V3DfgVertices.h @@ -0,0 +1,229 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: DfgVertex sub-classes +// +// 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 +// +//************************************************************************* +// +// This is a data-flow graph based representation of combinational logic, +// the main difference from a V3Graph is that DfgVertex owns the storage +// of it's input edges (operands/sources/arguments), and can access each +// input edge directly by indexing, making modifications more efficient +// than the linked list based structures used by V3Graph. +// +// A bulk of the DfgVertex sub-types are generated by astgen, and are +// analogous to the corresponding AstNode sub-types. +// +// See also the internals documentation docs/internals.rst +// +//************************************************************************* + +#ifndef VERILATOR_V3DFGVERTICES_H_ +#define VERILATOR_V3DFGVERTICES_H_ + +#ifndef VERILATOR_V3DFG_H_ +#error "Use V3Dfg.h as the include" +#include "V3Dfg.h" // This helps code analysis tools pick up symbols in V3Dfg.h +#define VL_NOT_FINAL // This #define fixes broken code folding in the CLion IDE +#endif + +// === Abstract base node types (DfgVertex*) =================================== + +class DfgVertexVar VL_NOT_FINAL : public DfgVertexVariadic { + AstVar* const m_varp; // The AstVar associated with this vertex (not owned by this vertex) + bool m_hasModRefs = false; // This AstVar is referenced outside the DFG, but in the module + bool m_hasExtRefs = false; // This AstVar is referenced from outside the module + +public: + DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp, uint32_t initialCapacity) + : DfgVertexVariadic{dfg, type, varp->fileline(), dtypeFor(varp), initialCapacity} + , m_varp{varp} {} + ASTGEN_MEMBERS_DfgVertexVar; + + AstVar* varp() const { return m_varp; } + bool hasModRefs() const { return m_hasModRefs; } + void setHasModRefs() { m_hasModRefs = true; } + bool hasExtRefs() const { return m_hasExtRefs; } + void setHasExtRefs() { m_hasExtRefs = true; } + bool hasRefs() const { return m_hasModRefs || m_hasExtRefs; } + + // Variable cannot be removed, even if redundant in the DfgGraph (might be used externally) + bool keep() const { + // Keep if referenced outside this module + if (hasExtRefs()) return true; + // Keep if traced + if (v3Global.opt.trace() && varp()->isTrace()) return true; + // Keep if public + if (varp()->isSigPublic()) return true; + // Otherwise it can be removed + return false; + } +}; + +// === Concrete node types ===================================================== + +// === DfgVertex === +class DfgConst final : public DfgVertex { + friend class DfgVertex; + friend class DfgVisitor; + + AstConst* const m_constp; // The AstConst associated with this vertex (owned by this vertex) + + bool selfEquals(const DfgVertex& that) const override; + V3Hash selfHash() const override; + +public: + DfgConst(DfgGraph& dfg, AstConst* constp) + : DfgVertex{dfg, dfgType(), constp->fileline(), dtypeFor(constp)} + , m_constp{constp} {} + ASTGEN_MEMBERS_DfgConst; + + ~DfgConst() override { VL_DO_DANGLING(m_constp->deleteTree(), m_constp); } + + AstConst* constp() const { return m_constp; } + V3Number& num() const { return m_constp->num(); } + + uint32_t toU32() const { return num().toUInt(); } + int32_t toI32() const { return num().toSInt(); } + + bool isZero() const { return num().isEqZero(); } + bool isOnes() const { return num().isEqAllOnes(width()); } + + std::pair sourceEdges() override { return {nullptr, 0}; } + std::pair sourceEdges() const override { return {nullptr, 0}; } + const string srcName(size_t) const override { // LCOV_EXCL_START + VL_UNREACHABLE; + return ""; + } // LCOV_EXCL_STOP +}; + +// === DfgVertexVar === +class DfgVarArray final : public DfgVertexVar { + friend class DfgVertex; + friend class DfgVisitor; + + using DriverData = std::pair; + + std::vector m_driverData; // Additional data associate with each driver + + bool selfEquals(const DfgVertex& that) const override; + V3Hash selfHash() const override; + +public: + DfgVarArray(DfgGraph& dfg, AstVar* varp) + : DfgVertexVar{dfg, dfgType(), varp, 4u} { + UASSERT_OBJ(VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType), varp, "Non array DfgVarArray"); + } + ASTGEN_MEMBERS_DfgVarArray; + + bool isDrivenByDfg() const { return arity() > 0; } + + void addDriver(FileLine* flp, uint32_t index, DfgVertex* vtxp) { + m_driverData.emplace_back(flp, index); + DfgVertexVariadic::addSource()->relinkSource(vtxp); + } + + void resetSources() { + m_driverData.clear(); + DfgVertexVariadic::resetSources(); + } + + // Remove undriven sources + void packSources() { + // Grab and reset the driver data + std::vector driverData{std::move(m_driverData)}; + + // Grab and unlink the sources + std::vector sources{arity()}; + forEachSourceEdge([&](DfgEdge& edge, size_t idx) { + sources[idx] = edge.sourcep(); + edge.unlinkSource(); + }); + DfgVertexVariadic::resetSources(); + + // Add back the driven sources + for (size_t i = 0; i < sources.size(); ++i) { + if (!sources[i]) continue; + addDriver(driverData[i].first, driverData[i].second, sources[i]); + } + } + + FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; } + uint32_t driverIndex(size_t idx) const { return m_driverData[idx].second; } + + DfgVertex* driverAt(size_t idx) const { + const DfgEdge* const edgep = findSourceEdge([=](const DfgEdge&, size_t i) { // + return driverIndex(i) == idx; + }); + return edgep ? edgep->sourcep() : nullptr; + } + + const string srcName(size_t idx) const override { return cvtToStr(driverIndex(idx)); } +}; +class DfgVarPacked final : public DfgVertexVar { + friend class DfgVertex; + friend class DfgVisitor; + + using DriverData = std::pair; + + std::vector m_driverData; // Additional data associate with each driver + + bool selfEquals(const DfgVertex& that) const override; + V3Hash selfHash() const override; + +public: + DfgVarPacked(DfgGraph& dfg, AstVar* varp) + : DfgVertexVar{dfg, dfgType(), varp, 1u} {} + ASTGEN_MEMBERS_DfgVarPacked; + + bool isDrivenByDfg() const { return arity() > 0; } + bool isDrivenFullyByDfg() const { return arity() == 1 && source(0)->dtypep() == dtypep(); } + + void addDriver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp) { + m_driverData.emplace_back(flp, lsb); + DfgVertexVariadic::addSource()->relinkSource(vtxp); + } + + void resetSources() { + m_driverData.clear(); + DfgVertexVariadic::resetSources(); + } + + // Remove undriven sources + void packSources() { + // Grab and reset the driver data + std::vector driverData{std::move(m_driverData)}; + + // Grab and unlink the sources + std::vector sources{arity()}; + forEachSourceEdge([&](DfgEdge& edge, size_t idx) { + sources[idx] = edge.sourcep(); + edge.unlinkSource(); + }); + DfgVertexVariadic::resetSources(); + + // Add back the driven sources + for (size_t i = 0; i < sources.size(); ++i) { + if (!sources[i]) continue; + addDriver(driverData[i].first, driverData[i].second, sources[i]); + } + } + + FileLine* driverFileLine(size_t idx) const { return m_driverData[idx].first; } + uint32_t driverLsb(size_t idx) const { return m_driverData[idx].second; } + + const string srcName(size_t idx) const override { + return isDrivenFullyByDfg() ? "" : cvtToStr(driverLsb(idx)); + } +}; + +#endif diff --git a/src/astgen b/src/astgen index f0883dd08..3348d0e44 100755 --- a/src/astgen +++ b/src/astgen @@ -8,12 +8,14 @@ import os import re import sys import textwrap + # from pprint import pprint, pformat +# This class is used to represents both AstNode and DfgVertex sub-types class Node: - def __init__(self, name, superClass, file, lineno): + def __init__(self, name, superClass, file=None, lineno=None): self._name = name self._superClass = superClass self._subClasses = [] # Initially list, but tuple after completion @@ -173,9 +175,11 @@ class Node: return self in other.allSubClasses -Nodes = {} -SortedNodes = None -DfgVertices = None +AstNodes = {} +AstNodeList = None + +DfgVertices = {} +DfgVertexList = None ClassRefs = {} Stages = {} @@ -278,7 +282,7 @@ class Cpt: self.error("Can't parse from function: " + func) typen = match.group(1) subnodes = match.group(2) - if Nodes[typen].isRoot: + if AstNodes[typen].isRoot: self.error("Unknown AstNode typen: " + typen + ": in " + func) mif = "" @@ -333,7 +337,7 @@ class Cpt: elif match_skip: typen = match_skip.group(1) self.tree_skip_visit[typen] = 1 - if typen not in Nodes: + if typen not in AstNodes: self.error("Unknown node type: " + typen) else: @@ -463,7 +467,7 @@ class Cpt: self.print( " // Bottom class up, as more simple transforms are generally better\n" ) - for node in SortedNodes: + for node in AstNodeList: out_for_type_sc = [] out_for_type = [] classes = list(node.allSuperClasses) @@ -502,7 +506,7 @@ class Cpt: "".join(out_for_type_sc)) if out_for_type[0]: self.print(" iterateAndNextNull(nodep->rhsp());\n") - if node.isSubClassOf(Nodes["NodeTriop"]): + if node.isSubClassOf(AstNodes["NodeTriop"]): self.print( " iterateAndNextNull(nodep->thsp());\n") self.print("".join(out_for_type) + " }\n") @@ -542,7 +546,7 @@ def parseOpType(string): return None -def read_types(filename): +def read_types(filename, Nodes, prefix): hasErrors = False def error(lineno, message): @@ -560,8 +564,9 @@ def read_types(filename): return if not hasAstgenMembers: error( - node.lineno, "'Ast" + node.name + - "' does not contain 'ASTGEN_MEMBERS_" + node.name + ";'") + node.lineno, + "'{p}{n}' does not contain 'ASTGEN_MEMBERS_{p}{n};'".format( + p=prefix, n=node.name)) hasAstgenMembers = False with open(filename) as fh: @@ -575,12 +580,12 @@ def read_types(filename): classn = match.group(2) match = re.search(r':\s*public\s+(\S+)', line) supern = match.group(1) if match else "" - if re.search(r'Ast', supern): - classn = re.sub(r'^Ast', '', classn) - supern = re.sub(r'^Ast', '', supern) + if re.search(prefix, supern): + classn = re.sub(r'^' + prefix, '', classn) + supern = re.sub(r'^' + prefix, '', supern) if not supern: - sys.exit("%Error: 'Ast{}' has no super-class".format( - classn)) + sys.exit("%Error: '{p}{c}' has no super-class".format( + p=prefix, c=classn)) checkFinishedNode(node) superClass = Nodes[supern] node = Node(classn, superClass, filename, lineno) @@ -589,8 +594,13 @@ def read_types(filename): if not node: continue - if re.match(r'^\s*ASTGEN_MEMBERS_' + node.name + ';', line): + if re.match(r'^\s*ASTGEN_MEMBERS_' + prefix + node.name + ';', + line): hasAstgenMembers = True + + if prefix != "Ast": + continue + match = re.match(r'^\s*//\s*@astgen\s+(.*)$', line) if match: decl = re.sub(r'//.*$', '', match.group(1)) @@ -650,6 +660,59 @@ def read_types(filename): sys.exit("%Error: Stopping due to errors reported above") +def check_types(sortedTypes, prefix, abstractPrefix): + baseClass = prefix + abstractPrefix + + # Check all leaf types are not AstNode* and non-leaves are AstNode* + for node in sortedTypes: + if re.match(r'^' + abstractPrefix, node.name): + if node.isLeaf: + sys.exit( + "%Error: Final {b} subclasses must not be named {b}*: {p}{n}" + .format(b=baseClass, p=prefix, n=node.name)) + else: + if not node.isLeaf: + sys.exit( + "%Error: Non-final {b} subclasses must be named {b}*: {p}{n}" + .format(b=baseClass, p=prefix, n=node.name)) + + # Check ordering of node definitions + hasOrderingError = False + + files = tuple( + sorted(set(_.file for _ in sortedTypes if _.file is not None))) + + for file in files: + nodes = tuple(filter(lambda _, f=file: _.file == f, sortedTypes)) + expectOrder = tuple(sorted(nodes, key=lambda _: (_.isLeaf, _.ordIdx))) + actualOrder = tuple(sorted(nodes, key=lambda _: _.lineno)) + expect = { + node: pred + for pred, node in zip((None, ) + expectOrder[:-1], expectOrder) + } + actual = { + node: pred + for pred, node in zip((None, ) + actualOrder[:-1], actualOrder) + } + for node in nodes: + if expect[node] != actual[node]: + hasOrderingError = True + pred = expect[node] + print( + "{file}:{lineno}: %Error: Definition of '{p}{n}' is out of order. Shold be {where}." + .format(file=file, + lineno=node.lineno, + p=prefix, + n=node.name, + where=("right after '" + prefix + pred.name + + "'" if pred else "first in file")), + file=sys.stderr) + + if hasOrderingError: + sys.exit( + "%Error: Stopping due to out of order definitions listed above") + + def read_stages(filename): with open(filename) as fh: n = 100 @@ -712,7 +775,7 @@ def write_report(filename): fh.write(" " + classn + "\n") fh.write("\nClasses:\n") - for node in SortedNodes: + for node in AstNodeList: fh.write(" class Ast%-17s\n" % node.name) fh.write(" arity: {}\n".format(node.arity)) fh.write(" parent: ") @@ -741,97 +804,110 @@ def write_report(filename): fh.write("\n") -def write_classes(filename): - with open_file(filename) as fh: - fh.write("class AstNode;\n") - for node in SortedNodes: - fh.write("class Ast%-17s // " % (node.name + ";")) +################################################################################ +# Common code genaration +################################################################################ + + +def write_forward_class_decls(prefix, nodeList): + with open_file("V3{p}__gen_forward_class_decls.h".format(p=prefix)) as fh: + for node in nodeList: + fh.write("class {p}{n:<17} // ".format(p=prefix, + n=node.name + ";")) for superClass in node.allSuperClasses: - fh.write("Ast%-12s " % superClass.name) + fh.write("{p}{n:<12} ".format(p=prefix, n=superClass.name)) fh.write("\n") -def write_visitor_decls(filename): - with open_file(filename) as fh: - for node in SortedNodes: +def write_visitor_decls(prefix, nodeList): + with open_file("V3{p}__gen_visitor_decls.h".format(p=prefix)) as fh: + for node in nodeList: if not node.isRoot: - fh.write("virtual void visit(Ast" + node.name + "*);\n") + fh.write("virtual void visit({p}{n}*);\n".format(p=prefix, + n=node.name)) -def write_visitor_defns(filename): - with open_file(filename) as fh: - for node in SortedNodes: +def write_visitor_defns(prefix, nodeList, visitor): + with open_file("V3{p}__gen_visitor_defns.h".format(p=prefix)) as fh: + variable = "nodep" if prefix == "Ast" else "vtxp" + for node in nodeList: base = node.superClass if base is not None: - fh.write("void VNVisitor::visit(Ast" + node.name + - "* nodep) { visit(static_cast(nodep)); }\n") + fh.write( + "void {c}::visit({p}{n}* {v}) {{ visit(static_cast<{p}{b}*>({v})); }}\n" + .format(c=visitor, + p=prefix, + n=node.name, + b=base.name, + v=variable)) -def write_impl(filename): - with open_file(filename) as fh: - fh.write("\n") - fh.write("// For internal use. They assume argument is not nullptr.\n") - for node in SortedNodes: - fh.write("template<> inline bool AstNode::privateTypeTest(const AstNode* nodep) { ") - if node.isRoot: - fh.write("return true; ") - else: - fh.write("return ") - if not node.isLeaf: - fh.write( - "static_cast(nodep->type()) >= static_cast(VNType::first" - + node.name + ") && ") - fh.write( - "static_cast(nodep->type()) <= static_cast(VNType::last" - + node.name + "); ") - else: - fh.write("nodep->type() == VNType::at" + node.name + "; ") - fh.write("}\n") +def write_type_enum(prefix, nodeList): + root = next(_ for _ in nodeList if _.isRoot) + with open_file("V3{p}__gen_type_enum.h".format(p=prefix)) as fh: - -def write_types(filename): - with open_file(filename) as fh: fh.write(" enum en : uint16_t {\n") - for node in sorted(filter(lambda _: _.isLeaf, SortedNodes), + for node in sorted(filter(lambda _: _.isLeaf, nodeList), key=lambda _: _.typeId): - fh.write(" at" + node.name + " = " + str(node.typeId) + - ",\n") - fh.write(" _ENUM_END = " + str(Nodes["Node"].typeIdMax + 1) + - "\n") + fh.write(" at{t} = {n},\n".format(t=node.name, + n=node.typeId)) + fh.write(" _ENUM_END = {n}\n".format(n=root.typeIdMax + 1)) fh.write(" };\n") fh.write(" enum bounds : uint16_t {\n") - for node in sorted(filter(lambda _: not _.isLeaf, SortedNodes), + for node in sorted(filter(lambda _: not _.isLeaf, nodeList), key=lambda _: _.typeIdMin): - fh.write(" first" + node.name + " = " + - str(node.typeIdMin) + ",\n") - fh.write(" last" + node.name + " = " + str(node.typeIdMax) + - ",\n") + fh.write(" first{t} = {n},\n".format(t=node.name, + n=node.typeIdMin)) + fh.write(" last{t} = {n},\n".format(t=node.name, + n=node.typeIdMax)) fh.write(" _BOUNDS_END\n") fh.write(" };\n") fh.write(" const char* ascii() const {\n") fh.write(" static const char* const names[_ENUM_END + 1] = {\n") - for node in sorted(filter(lambda _: _.isLeaf, SortedNodes), + for node in sorted(filter(lambda _: _.isLeaf, nodeList), key=lambda _: _.typeId): - fh.write(" \"" + node.name.upper() + "\",\n") + fh.write(' "{T}",\n'.format(T=node.name.upper())) fh.write(" \"_ENUM_END\"\n") fh.write(" };\n") fh.write(" return names[m_e];\n") fh.write(" }\n") -def write_yystype(filename): - with open_file(filename) as fh: - for node in SortedNodes: - fh.write("Ast{t}* {m}p;\n".format(t=node.name, - m=node.name[0].lower() + - node.name[1:])) +def write_type_tests(prefix, nodeList): + with open_file("V3{p}__gen_type_tests.h".format(p=prefix)) as fh: + fh.write("// For internal use. They assume argument is not nullptr.\n") + if prefix == "Ast": + base = "AstNode" + variable = "nodep" + enum = "VNType" + elif prefix == "Dfg": + base = "DfgVertex" + variable = "vtxp" + enum = "VDfgType" + for node in nodeList: + fh.write( + "template<> inline bool {b}::privateTypeTest<{p}{n}>(const {b}* {v}) {{ " + .format(b=base, p=prefix, n=node.name, v=variable)) + if node.isRoot: + fh.write("return true;") + elif not node.isLeaf: + fh.write( + "return static_cast({v}->type()) >= static_cast({e}::first{t}) && static_cast({v}->type()) <= static_cast({e}::last{t});" + .format(v=variable, e=enum, t=node.name)) + else: + fh.write("return {v}->type() == {e}::at{t};".format( + v=variable, e=enum, t=node.name)) + fh.write(" }\n") -def write_macros(filename): +################################################################################ +# Ast code genaration +################################################################################ + + +def write_ast_macros(filename): with open_file(filename) as fh: def emitBlock(pattern, **fmt): @@ -839,8 +915,8 @@ def write_macros(filename): textwrap.indent(textwrap.dedent(pattern), " ").format(**fmt).replace("\n", " \\\n")) - for node in SortedNodes: - fh.write("#define ASTGEN_MEMBERS_{t} \\\n".format(t=node.name)) + for node in AstNodeList: + fh.write("#define ASTGEN_MEMBERS_Ast{t} \\\n".format(t=node.name)) emitBlock('''\ static Ast{t}* cloneTreeNull(Ast{t}* nodep, bool cloneNextLink) {{ return nodep ? nodep->cloneTree(cloneNextLink) : nullptr; @@ -860,7 +936,7 @@ def write_macros(filename): ''', t=node.name) - for n in (1, 2, 3, 4): + for n in range(1, 5): op = node.getOp(n) if not op: continue @@ -907,7 +983,15 @@ def write_macros(filename): fh.write("\n") -def write_op_checks(filename): +def write_ast_yystype(filename): + with open_file(filename) as fh: + for node in AstNodeList: + fh.write("Ast{t}* {m}p;\n".format(t=node.name, + m=node.name[0].lower() + + node.name[1:])) + + +def write_ast_op_checks(filename): with open_file(filename) as fh: indent = "" @@ -917,7 +1001,7 @@ def write_op_checks(filename): textwrap.indent(textwrap.dedent(pattern), indent).format(**fmt)) - for node in SortedNodes: + for node in AstNodeList: if not node.isLeaf: continue @@ -991,102 +1075,98 @@ def write_op_checks(filename): ''') -def write_dfg_vertex_classes(filename): +################################################################################ +# DFG code genaration +################################################################################ + + +def write_dfg_macros(filename): with open_file(filename) as fh: - fh.write("\n") - for node in DfgVertices: - fh.write("class Dfg{} final : public DfgVertexWithArity<{}> {{\n". - format(node.name, node.arity)) - fh.write(" friend class DfgVertex;\n") - fh.write(" friend class DfgVisitor;\n") - fh.write(" void accept(DfgVisitor& visitor) override;\n") + + def emitBlock(pattern, **fmt): fh.write( - " static constexpr DfgType dfgType() {{ return DfgType::at{t}; }};\n" - .format(t=node.name)) - fh.write("public:\n") - fh.write( - " Dfg{t}(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep) : DfgVertexWithArity<{a}>{{dfg, flp, dtypep, dfgType()}} {{}}\n" - .format(t=node.name, a=node.arity)) - # Accessors + textwrap.indent(textwrap.dedent(pattern), + " ").format(**fmt).replace("\n", " \\\n")) + + for node in DfgVertexList: + fh.write("#define ASTGEN_MEMBERS_Dfg{t} \\\n".format(t=node.name)) + + if node.isLeaf: + emitBlock('''\ + static constexpr VDfgType dfgType() {{ return VDfgType::at{t}; }}; + void accept(DfgVisitor& v) override {{ v.visit(this); }} + ''', + t=node.name) + + for n in range(1, node.arity + 1): + name, _, _ = node.getOp(n) + emitBlock('''\ + DfgVertex* {name}() const {{ return source<{n}>(); }} + void {name}(DfgVertex* vtxp) {{ relinkSource<{n}>(vtxp); }} + ''', + name=name, + n=n - 1) + operandNames = tuple( node.getOp(n)[0] for n in range(1, node.arity + 1)) - assert not operandNames or len(operandNames) == node.arity - for i, n in enumerate(operandNames): - fh.write( - " DfgVertex* {n}() const {{ return source<{i}>(); }}\n". - format(n=n, i=i)) - for i, n in enumerate(operandNames): - fh.write( - " void {n}(DfgVertex* vtxp) {{ relinkSource<{i}>(vtxp); }}\n" - .format(n=n, i=i)) if operandNames: - names = ", ".join(map(lambda _: '"' + _ + '"', operandNames)) - fh.write( - " const string srcName(size_t idx) const override {\n") - fh.write( - " static const char* names[{a}] = {{ {ns} }};\n". - format(a=node.arity, ns=names)) - fh.write(" return names[idx];\n") - fh.write(" }\n") - fh.write("};\n") - fh.write("\n") - fh.write("\n") - - fh.write("\n\ntemplate\n") - fh.write("struct DfgForAstImpl;\n\n") - for node in DfgVertices: - fh.write("template <>\n") + emitBlock('''\ + const std::string srcName(size_t idx) const override {{ + static const char* names[{a}] = {{ {ns} }}; + return names[idx]; + }} + ''', + a=node.arity, + ns=", ".join( + map(lambda _: '"' + _ + '"', operandNames))) fh.write( - "struct DfgForAstImpl {{\n".format(name=node.name)) - fh.write(" using type = Dfg{name};\n".format(name=node.name)) - fh.write("};\n") - fh.write("\ntemplate\n") - fh.write("using DfgForAst = typename DfgForAstImpl::type;\n") - - fh.write("\n\ntemplate\n") - fh.write("struct AstForDfgImpl;\n\n") - for node in DfgVertices: - fh.write("template <>\n") - fh.write( - "struct AstForDfgImpl {{\n".format(name=node.name)) - fh.write(" using type = Ast{name};\n".format(name=node.name)) - fh.write("};\n") - fh.write("\ntemplate\n") - fh.write("using AstForDfg = typename AstForDfgImpl::type;\n") + " static_assert(true, \"\")\n") # Swallowing the semicolon -def write_dfg_visitor_decls(filename): +def write_dfg_auto_classes(filename): with open_file(filename) as fh: - fh.write("\n") - fh.write("virtual void visit(DfgVertex*) = 0;\n") - for node in DfgVertices: - fh.write("virtual void visit(Dfg{}*);\n".format(node.name)) + def emitBlock(pattern, **fmt): + fh.write(textwrap.dedent(pattern).format(**fmt)) -def write_dfg_definitions(filename): - with open_file(filename) as fh: + for node in DfgVertexList: + # Only generate code for automatically derieved leaf nodes + if (node.file is not None) or not node.isLeaf: + continue + + emitBlock('''\ + class Dfg{t} final : public Dfg{s} {{ + public: + Dfg{t}(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep) + : Dfg{s}{{dfg, dfgType(), flp, dtypep}} {{}} + ASTGEN_MEMBERS_Dfg{t}; + }}; + ''', + t=node.name, + s=node.superClass.name) fh.write("\n") - for node in DfgVertices: - fh.write( - "void Dfg{}::accept(DfgVisitor& visitor) {{ visitor.visit(this); }}\n" - .format(node.name)) - fh.write("\n") - for node in DfgVertices: - fh.write( - "void DfgVisitor::visit(Dfg{}* vtxp) {{ visit(static_cast(vtxp)); }}\n" - .format(node.name)) def write_dfg_ast_to_dfg(filename): with open_file(filename) as fh: - fh.write("\n") - for node in DfgVertices: + for node in DfgVertexList: + # Only generate code for automatically derieved leaf nodes + if (node.file is not None) or (not node.isLeaf): + continue + fh.write( "void visit(Ast{t}* nodep) override {{\n".format(t=node.name)) fh.write( - ' UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");\n' + ' UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex");\n\n' ) - fh.write(" if (unhandled(nodep)) return;\n") + fh.write(" if (unhandled(nodep)) return;\n\n") + for i in range(node.arity): + fh.write(" iterate(nodep->op{j}p());\n".format(j=i + 1)) + fh.write(" if (m_foundUnhandled) return;\n") + fh.write( + ' UASSERT_OBJ(nodep->op{j}p()->user1p(), nodep, "Child {j} missing Dfg vertex");\n' + .format(j=i + 1)) + fh.write("\n") fh.write( " Dfg{t}* const vtxp = makeVertex(nodep, *m_dfgp);\n" .format(t=node.name)) @@ -1095,24 +1175,23 @@ def write_dfg_ast_to_dfg(filename): fh.write(" ++m_ctx.m_nonRepNode;\n") fh.write(" return;\n") fh.write(" }\n\n") - fh.write(" m_uncommittedVertices.push_back(vtxp);\n") for i in range(node.arity): - fh.write(" iterate(nodep->op{j}p());\n".format(j=i + 1)) - fh.write(" if (m_foundUnhandled) return;\n") fh.write( - ' UASSERT_OBJ(nodep->op{j}p()->user1p(), nodep, "Child {j} missing Dfg vertex");\n' - .format(j=i + 1)) - fh.write( - " vtxp->relinkSource<{i}>(nodep->op{j}p()->user1u().to());\n\n" + " vtxp->relinkSource<{i}>(nodep->op{j}p()->user1u().to());\n" .format(i=i, j=i + 1)) + fh.write("\n") + fh.write(" m_uncommittedVertices.push_back(vtxp);\n") fh.write(" nodep->user1p(vtxp);\n") fh.write("}\n") def write_dfg_dfg_to_ast(filename): with open_file(filename) as fh: - fh.write("\n") - for node in DfgVertices: + for node in DfgVertexList: + # Only generate code for automatically derieved leaf nodes + if (node.file is not None) or (not node.isLeaf): + continue + fh.write( "void visit(Dfg{t}* vtxp) override {{\n".format(t=node.name)) for i in range(node.arity): @@ -1146,6 +1225,9 @@ parser.add_argument('-I', action='store', help='source code include directory') parser.add_argument('--astdef', action='append', help='add AST definition file (relative to -I)') +parser.add_argument('--dfgdef', + action='append', + help='add DFG definition file (relative to -I)') parser.add_argument('--classes', action='store_true', help='makes class declaration files') @@ -1155,66 +1237,86 @@ parser.add_argument('infiles', nargs='*', help='list of input .cpp filenames') Args = parser.parse_args() +############################################################################### +# Read AstNode definitions +############################################################################### + # Set up the root AstNode type. It is standalone so we don't need to parse the # sources for this. -Nodes["Node"] = Node("Node", None, "AstNode", 1) +AstNodes["Node"] = Node("Node", None) -# Read Ast node definitions +# Read AstNode definitions for filename in Args.astdef: - read_types(os.path.join(Args.I, filename)) + read_types(os.path.join(Args.I, filename), AstNodes, "Ast") # Compute derived properties over the whole AstNode hierarchy -Nodes["Node"].complete() +AstNodes["Node"].complete() -SortedNodes = tuple(map(lambda _: Nodes[_], sorted(Nodes.keys()))) +AstNodeList = tuple(map(lambda _: AstNodes[_], sorted(AstNodes.keys()))) -for node in SortedNodes: - # Check all leaves are not AstNode* and non-leaves are AstNode* - if re.match(r'^Node', node.name): - if node.isLeaf: - sys.exit( - "%Error: Final AstNode subclasses must not be named AstNode*: Ast" - + node.name) +check_types(AstNodeList, "Ast", "Node") + +############################################################################### +# Read and generate DfgVertex definitions +############################################################################### + +# Set up the root DfgVertex type and some other hand-written base types. +# These are standalone so we don't need to parse the sources for this. +DfgVertices["Vertex"] = Node("Vertex", None) +DfgVertices["VertexUnary"] = Node("VertexUnary", DfgVertices["Vertex"]) +DfgVertices["Vertex"].addSubClass(DfgVertices["VertexUnary"]) +DfgVertices["VertexBinary"] = Node("VertexBinary", DfgVertices["Vertex"]) +DfgVertices["Vertex"].addSubClass(DfgVertices["VertexBinary"]) +DfgVertices["VertexTernary"] = Node("VertexTernary", DfgVertices["Vertex"]) +DfgVertices["Vertex"].addSubClass(DfgVertices["VertexTernary"]) +DfgVertices["VertexVariadic"] = Node("VertexVariadic", DfgVertices["Vertex"]) +DfgVertices["Vertex"].addSubClass(DfgVertices["VertexVariadic"]) + +# Read DfgVertex definitions +for filename in Args.dfgdef: + read_types(os.path.join(Args.I, filename), DfgVertices, "Dfg") + +# Add the DfgVertex sub-types automatically derived from AstNode sub-types +for node in AstNodeList: + # Ignore the hierarchy for now + if not node.isLeaf: + continue + + # Ignore any explicitly defined vertex + if node.name in DfgVertices: + continue + + if node.isSubClassOf(AstNodes["NodeUniop"]): + base = DfgVertices["VertexUnary"] + elif node.isSubClassOf(AstNodes["NodeBiop"]): + base = DfgVertices["VertexBinary"] + elif node.isSubClassOf(AstNodes["NodeTriop"]): + base = DfgVertices["VertexTernary"] else: - if not node.isLeaf: - sys.exit( - "%Error: Non-final AstNode subclasses must be named AstNode*: Ast" - + node.name) + continue -DfgBases = (Nodes["NodeUniop"], Nodes["NodeBiop"], Nodes["NodeTriop"]) -DfgVertices = tuple( - node for node in SortedNodes - if node.isLeaf and any(node.isSubClassOf(base) for base in DfgBases)) + vertex = Node(node.name, base) + DfgVertices[node.name] = vertex + base.addSubClass(vertex) -# Check ordering of node definitions -files = tuple(sorted(set(_.file for _ in SortedNodes))) + for n in range(1, node.arity + 1): + op = node.getOp(n) + if op is not None: + name, monad, kind = op + assert monad == "", "Cannot represnt AstNode as DfgVertex" + vertex.addOp(n, name, "", "") -hasOrderingError = False -for file in files: - nodes = tuple(filter(lambda _, f=file: _.file == f, SortedNodes)) - expectOrder = tuple(sorted(nodes, key=lambda _: (_.isLeaf, _.ordIdx))) - actualOrder = tuple(sorted(nodes, key=lambda _: _.lineno)) - expect = { - node: pred - for pred, node in zip((None, ) + expectOrder[:-1], expectOrder) - } - actual = { - node: pred - for pred, node in zip((None, ) + actualOrder[:-1], actualOrder) - } - for node in nodes: - if expect[node] != actual[node]: - hasOrderingError = True - pred = expect[node] - print(file + ":" + str(node.lineno) + - ": %Error: Definition of 'Ast" + node.name + - "' is out of order. Should be " + - ("right after 'Ast" + pred.name + - "'" if pred else "first in file") + ".", - file=sys.stderr) +# Compute derived properties over the whole DfgVertex hierarchy +DfgVertices["Vertex"].complete() -if hasOrderingError: - sys.exit("%Error: Stopping due to out of order definitions listed above") +DfgVertexList = tuple(map(lambda _: DfgVertices[_], + sorted(DfgVertices.keys()))) + +check_types(DfgVertexList, "Dfg", "Vertex") + +############################################################################### +# Read additional files +############################################################################### read_stages(Args.I + "/Verilator.cpp") @@ -1224,19 +1326,29 @@ source_files.extend(glob.glob(Args.I + "/*.cpp")) for filename in source_files: read_refs(filename) +############################################################################### +# Generate output +############################################################################### + if Args.classes: write_report("V3Ast__gen_report.txt") - write_classes("V3Ast__gen_classes.h") - write_visitor_decls("V3Ast__gen_visitor_decls.h") - write_visitor_defns("V3Ast__gen_visitor_defns.h") - write_impl("V3Ast__gen_impl.h") - write_types("V3Ast__gen_types.h") - write_yystype("V3Ast__gen_yystype.h") - write_macros("V3Ast__gen_macros.h") - write_op_checks("V3Ast__gen_op_checks.h") - write_dfg_vertex_classes("V3Dfg__gen_vertex_classes.h") - write_dfg_visitor_decls("V3Dfg__gen_visitor_decls.h") - write_dfg_definitions("V3Dfg__gen_definitions.h") + # Write Ast code + write_forward_class_decls("Ast", AstNodeList) + write_visitor_decls("Ast", AstNodeList) + write_visitor_defns("Ast", AstNodeList, "VNVisitor") + write_type_enum("Ast", AstNodeList) + write_type_tests("Ast", AstNodeList) + write_ast_macros("V3Ast__gen_macros.h") + write_ast_yystype("V3Ast__gen_yystype.h") + write_ast_op_checks("V3Ast__gen_op_checks.h") + # Write Dfg code + write_forward_class_decls("Dfg", DfgVertexList) + write_visitor_decls("Dfg", DfgVertexList) + write_visitor_defns("Dfg", DfgVertexList, "DfgVisitor") + write_type_enum("Dfg", DfgVertexList) + write_type_tests("Dfg", DfgVertexList) + write_dfg_macros("V3Dfg__gen_macros.h") + write_dfg_auto_classes("V3Dfg__gen_auto_classes.h") write_dfg_ast_to_dfg("V3Dfg__gen_ast_to_dfg.h") write_dfg_dfg_to_ast("V3Dfg__gen_dfg_to_ast.h") From f23f3ca90773f0e791607fd4969baa7e1975374e Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 4 Oct 2022 18:26:53 +0100 Subject: [PATCH 080/177] Try to ensure DFG peephole patterns don't grow the graph Some optimizations are only a net win if they help us remove a graph node (or at least ensure they don't grow the graph), or yields otherwise special logic, so try to apply them only in these cases. --- src/V3DfgPeephole.cpp | 230 +++++++++++++++++--------------- test_regress/t/t_dfg_peephole.v | 23 ++-- 2 files changed, 135 insertions(+), 118 deletions(-) diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index e02671d64..9e8a761ed 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -268,7 +268,8 @@ class V3DfgPeephole final : public DfgVisitor { if (Reduction* const rRedp = vtxp->rhsp()->template cast()) { DfgVertex* const lSrcp = lRedp->srcp(); DfgVertex* const rSrcp = rRedp->srcp(); - if (lSrcp->dtypep() == rSrcp->dtypep()) { + if (lSrcp->dtypep() == rSrcp->dtypep() && lSrcp->width() <= 64 + && !lSrcp->hasMultipleSinks() && !rSrcp->hasMultipleSinks()) { APPLYING(PUSH_BITWISE_THROUGH_REDUCTION) { FileLine* const flp = vtxp->fileline(); Bitwise* const bwp = new Bitwise{m_dfg, flp, lSrcp->dtypep()}; @@ -326,23 +327,25 @@ class V3DfgPeephole final : public DfgVisitor { } if (DfgConcat* const concatp = srcp->cast()) { - APPLYING(PUSH_REDUCTION_THROUGH_CONCAT) { - // Reduce the parts of the concatenation - Reduction* const lReducep = new Reduction{m_dfg, concatp->fileline(), m_bitDType}; - lReducep->srcp(concatp->lhsp()); - Reduction* const rReducep = new Reduction{m_dfg, concatp->fileline(), m_bitDType}; - rReducep->srcp(concatp->rhsp()); + if (!concatp->hasMultipleSinks()) { + APPLYING(PUSH_REDUCTION_THROUGH_CONCAT) { + // Reduce the parts of the concatenation + Reduction* const lRedp = new Reduction{m_dfg, srcp->fileline(), m_bitDType}; + lRedp->srcp(concatp->lhsp()); + Reduction* const rRedp = new Reduction{m_dfg, srcp->fileline(), m_bitDType}; + rRedp->srcp(concatp->rhsp()); - // Bitwise reduce the results - Bitwise* const replacementp = new Bitwise{m_dfg, flp, m_bitDType}; - replacementp->lhsp(lReducep); - replacementp->rhsp(rReducep); - vtxp->replaceWith(replacementp); + // Bitwise reduce the results + Bitwise* const replacementp = new Bitwise{m_dfg, flp, m_bitDType}; + replacementp->lhsp(lRedp); + replacementp->rhsp(rRedp); + vtxp->replaceWith(replacementp); - // Optimize the new reductions - optimizeReduction(lReducep); - optimizeReduction(rReducep); - return; + // Optimize the new reductions + optimizeReduction(lRedp); + optimizeReduction(rRedp); + return; + } } } @@ -433,25 +436,28 @@ class V3DfgPeephole final : public DfgVisitor { } } - // Not of Eq - if (DfgEq* const eqp = vtxp->srcp()->cast()) { - APPLYING(REPLACE_NOT_EQ) { - DfgNeq* const replacementp = new DfgNeq{m_dfg, eqp->fileline(), vtxp->dtypep()}; - replacementp->lhsp(eqp->lhsp()); - replacementp->rhsp(eqp->rhsp()); - vtxp->replaceWith(replacementp); - return; + if (!vtxp->srcp()->hasMultipleSinks()) { + // Not of Eq + if (DfgEq* const eqp = vtxp->srcp()->cast()) { + APPLYING(REPLACE_NOT_EQ) { + DfgNeq* const replacementp + = new DfgNeq{m_dfg, eqp->fileline(), vtxp->dtypep()}; + replacementp->lhsp(eqp->lhsp()); + replacementp->rhsp(eqp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } } - } - // Not of Neq - if (DfgNeq* const neqp = vtxp->srcp()->cast()) { - APPLYING(REPLACE_NOT_NEQ) { - DfgEq* const replacementp = new DfgEq{m_dfg, neqp->fileline(), vtxp->dtypep()}; - replacementp->lhsp(neqp->lhsp()); - replacementp->rhsp(neqp->rhsp()); - vtxp->replaceWith(replacementp); - return; + // Not of Neq + if (DfgNeq* const neqp = vtxp->srcp()->cast()) { + APPLYING(REPLACE_NOT_NEQ) { + DfgEq* const replacementp = new DfgEq{m_dfg, neqp->fileline(), vtxp->dtypep()}; + replacementp->lhsp(neqp->lhsp()); + replacementp->rhsp(neqp->rhsp()); + vtxp->replaceWith(replacementp); + return; + } } } @@ -478,30 +484,32 @@ class V3DfgPeephole final : public DfgVisitor { FileLine* const flp = vtxp->fileline(); // Bubble pushing - if (DfgNot* const lhsNotp = lhsp->cast()) { - if (DfgNot* const rhsNotp = rhsp->cast()) { - APPLYING(REPLACE_AND_OF_NOT_AND_NOT) { - DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()}; - orp->lhsp(lhsNotp->srcp()); - orp->rhsp(rhsNotp->srcp()); - DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; - notp->srcp(orp); - vtxp->replaceWith(notp); - return; + if (!vtxp->hasMultipleSinks() && !lhsp->hasMultipleSinks() && !rhsp->hasMultipleSinks()) { + if (DfgNot* const lhsNotp = lhsp->cast()) { + if (DfgNot* const rhsNotp = rhsp->cast()) { + APPLYING(REPLACE_AND_OF_NOT_AND_NOT) { + DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()}; + orp->lhsp(lhsNotp->srcp()); + orp->rhsp(rhsNotp->srcp()); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(orp); + vtxp->replaceWith(notp); + return; + } } - } - if (DfgNeq* const rhsNeqp = rhsp->cast()) { - APPLYING(REPLACE_AND_OF_NOT_AND_NEQ) { - DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()}; - orp->lhsp(lhsNotp->srcp()); - DfgEq* const newRhsp = new DfgEq{m_dfg, rhsp->fileline(), rhsp->dtypep()}; - newRhsp->lhsp(rhsNeqp->lhsp()); - newRhsp->rhsp(rhsNeqp->rhsp()); - orp->rhsp(newRhsp); - DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; - notp->srcp(orp); - vtxp->replaceWith(notp); - return; + if (DfgNeq* const rhsNeqp = rhsp->cast()) { + APPLYING(REPLACE_AND_OF_NOT_AND_NEQ) { + DfgOr* const orp = new DfgOr{m_dfg, flp, vtxp->dtypep()}; + orp->lhsp(lhsNotp->srcp()); + DfgEq* const newRhsp = new DfgEq{m_dfg, rhsp->fileline(), rhsp->dtypep()}; + newRhsp->lhsp(rhsNeqp->lhsp()); + newRhsp->rhsp(rhsNeqp->rhsp()); + orp->rhsp(newRhsp); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(orp); + vtxp->replaceWith(notp); + return; + } } } } @@ -561,30 +569,32 @@ class V3DfgPeephole final : public DfgVisitor { FileLine* const flp = vtxp->fileline(); // Bubble pushing - if (DfgNot* const lhsNotp = lhsp->cast()) { - if (DfgNot* const rhsNotp = rhsp->cast()) { - APPLYING(REPLACE_OR_OF_NOT_AND_NOT) { - DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()}; - andp->lhsp(lhsNotp->srcp()); - andp->rhsp(rhsNotp->srcp()); - DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; - notp->srcp(andp); - vtxp->replaceWith(notp); - return; + if (!vtxp->hasMultipleSinks() && !lhsp->hasMultipleSinks() && !rhsp->hasMultipleSinks()) { + if (DfgNot* const lhsNotp = lhsp->cast()) { + if (DfgNot* const rhsNotp = rhsp->cast()) { + APPLYING(REPLACE_OR_OF_NOT_AND_NOT) { + DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()}; + andp->lhsp(lhsNotp->srcp()); + andp->rhsp(rhsNotp->srcp()); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(andp); + vtxp->replaceWith(notp); + return; + } } - } - if (DfgNeq* const rhsNeqp = rhsp->cast()) { - APPLYING(REPLACE_OR_OF_NOT_AND_NEQ) { - DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()}; - andp->lhsp(lhsNotp->srcp()); - DfgEq* const newRhsp = new DfgEq{m_dfg, rhsp->fileline(), rhsp->dtypep()}; - newRhsp->lhsp(rhsNeqp->lhsp()); - newRhsp->rhsp(rhsNeqp->rhsp()); - andp->rhsp(newRhsp); - DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; - notp->srcp(andp); - vtxp->replaceWith(notp); - return; + if (DfgNeq* const rhsNeqp = rhsp->cast()) { + APPLYING(REPLACE_OR_OF_NOT_AND_NEQ) { + DfgAnd* const andp = new DfgAnd{m_dfg, flp, vtxp->dtypep()}; + andp->lhsp(lhsNotp->srcp()); + DfgEq* const newRhsp = new DfgEq{m_dfg, rhsp->fileline(), rhsp->dtypep()}; + newRhsp->lhsp(rhsNeqp->lhsp()); + newRhsp->rhsp(rhsNeqp->rhsp()); + andp->rhsp(newRhsp); + DfgNot* const notp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + notp->srcp(andp); + vtxp->replaceWith(notp); + return; + } } } } @@ -802,10 +812,11 @@ class V3DfgPeephole final : public DfgVisitor { vtxp->lsbp(makeI32(flp, lsb - rhsp->width())); } } else if (lsb == 0 || msb == concatp->width() - 1 // - || lhsp->is() || rhsp->is()) { + || lhsp->is() || rhsp->is() // + || !concatp->hasMultipleSinks()) { // If the select straddles both sides, but at least one of the sides is wholly - // selected, or at least one of the sides is a Const, then push the Sel past - // the Concat + // selected, or at least one of the sides is a Const, or this concat has no other + // use, then push the Sel past the Concat APPLYING(PUSH_SEL_THROUGH_CONCAT) { const uint32_t rSelWidth = rhsp->width() - lsb; const uint32_t lSelWidth = width - rSelWidth; @@ -1071,7 +1082,7 @@ class V3DfgPeephole final : public DfgVisitor { { DfgNot* const lNot = lhsp->cast(); DfgNot* const rNot = rhsp->cast(); - if (lNot && rNot) { + if (lNot && rNot && !lNot->hasMultipleSinks() && !rNot->hasMultipleSinks()) { APPLYING(PUSH_CONCAT_THROUGH_NOTS) { vtxp->lhsp(lNot->srcp()); vtxp->rhsp(rNot->srcp()); @@ -1177,38 +1188,45 @@ class V3DfgPeephole final : public DfgVisitor { } if (DfgNot* const condNotp = condp->cast()) { - APPLYING(SWAP_COND_WITH_NOT_CONDITION) { - vtxp->condp(condNotp->srcp()); - vtxp->thenp(elsep); - vtxp->elsep(thenp); - visit(vtxp); - return; + if (!condp->hasMultipleSinks() || condNotp->hasMultipleSinks()) { + APPLYING(SWAP_COND_WITH_NOT_CONDITION) { + vtxp->condp(condNotp->srcp()); + vtxp->thenp(elsep); + vtxp->elsep(thenp); + visit(vtxp); + return; + } } } if (DfgNeq* const condNeqp = condp->cast()) { - APPLYING(SWAP_COND_WITH_NEQ_CONDITION) { - DfgEq* const newCondp = new DfgEq{m_dfg, condp->fileline(), condp->dtypep()}; - newCondp->lhsp(condNeqp->lhsp()); - newCondp->rhsp(condNeqp->rhsp()); - vtxp->condp(newCondp); - vtxp->thenp(elsep); - vtxp->elsep(thenp); - visit(vtxp); - return; + if (!condp->hasMultipleSinks()) { + APPLYING(SWAP_COND_WITH_NEQ_CONDITION) { + DfgEq* const newCondp = new DfgEq{m_dfg, condp->fileline(), condp->dtypep()}; + newCondp->lhsp(condNeqp->lhsp()); + newCondp->rhsp(condNeqp->rhsp()); + vtxp->condp(newCondp); + vtxp->thenp(elsep); + vtxp->elsep(thenp); + visit(vtxp); + return; + } } } if (DfgNot* const thenNotp = thenp->cast()) { if (DfgNot* const elseNotp = elsep->cast()) { - APPLYING(PULL_NOTS_THROUGH_COND) { - DfgNot* const replacementp - = new DfgNot{m_dfg, thenp->fileline(), vtxp->dtypep()}; - vtxp->thenp(thenNotp->srcp()); - vtxp->elsep(elseNotp->srcp()); - vtxp->replaceWith(replacementp); - replacementp->srcp(vtxp); - return; + if ((!thenp->hasMultipleSinks() || thenNotp->hasMultipleSinks()) + && (!elsep->hasMultipleSinks() || elsep->hasMultipleSinks())) { + APPLYING(PULL_NOTS_THROUGH_COND) { + DfgNot* const replacementp + = new DfgNot{m_dfg, thenp->fileline(), vtxp->dtypep()}; + vtxp->thenp(thenNotp->srcp()); + vtxp->elsep(elseNotp->srcp()); + vtxp->replaceWith(replacementp); + replacementp->srcp(vtxp); + return; + } } } } diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index f7defa9b4..bc8251345 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -40,7 +40,6 @@ module t ( `signal(PUSH_BITWISE_OP_THROUGH_CONCAT, 32'h12345678 ^ {8'h0, rand_a[23:0]}); `signal(PUSH_BITWISE_OP_THROUGH_CONCAT_2, 32'h12345678 ^ {rand_b[7:0], rand_a[23:0]}); `signal(PUSH_COMPARE_OP_THROUGH_CONCAT, 4'b1011 == {2'b10, rand_a[1:0]}); - `signal(REMOVE_WIDTH_ONE_REDUCTION, &`DFG(rand_a[0])); `signal(PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH, |(rand_a[32] ? rand_a[3:0] : 4'h0)); `signal(REPLACE_REDUCTION_OF_CONST_AND, &const_a); `signal(REPLACE_REDUCTION_OF_CONST_OR, |const_a); @@ -51,14 +50,14 @@ module t ( `signal(REPLACE_NOT_NEQ, ~`DFG(rand_a != rand_b)); `signal(REPLACE_NOT_EQ, ~`DFG(rand_a == rand_b)); `signal(REPLACE_NOT_OF_CONST, ~4'd0); - `signal(REPLACE_AND_OF_NOT_AND_NOT, ~rand_a[0] & ~rand_b[0]); - `signal(REPLACE_AND_OF_NOT_AND_NEQ, ~rand_a[0] & (rand_b != 64'd2)); + `signal(REPLACE_AND_OF_NOT_AND_NOT, ~rand_a[1] & ~rand_b[1]); + `signal(REPLACE_AND_OF_NOT_AND_NEQ, ~rand_a[2] & (rand_b != 64'd2)); `signal(REPLACE_AND_OF_CONST_AND_CONST, const_a & const_b); `signal(REPLACE_AND_WITH_ZERO, `ZERO & rand_a); `signal(REMOVE_AND_WITH_ONES, `ONES & rand_a); `signal(REPLACE_CONTRADICTORY_AND, rand_a & ~rand_a); - `signal(REPLACE_OR_OF_NOT_AND_NOT, ~rand_a[0] | ~rand_b[0]); - `signal(REPLACE_OR_OF_NOT_AND_NEQ, ~rand_a[0] | (rand_b != 64'd2)); + `signal(REPLACE_OR_OF_NOT_AND_NOT, ~rand_a[3] | ~rand_b[3]); + `signal(REPLACE_OR_OF_NOT_AND_NEQ, ~rand_a[4] | (rand_b != 64'd3)); `signal(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO, {2'd0, rand_a[1:0]} | {rand_b[1:0], 2'd0}); `signal(REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS, {rand_a[1:0], 2'd0} | {2'd0, rand_b[1:0]}); `signal(REPLACE_OR_OF_CONST_AND_CONST, const_a | const_b); @@ -80,7 +79,7 @@ module t ( `signal(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS, {const_a, `DFG({const_b, rand_a})}); `signal(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR, {62'd0, rand_a[63:62]}); `signal(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL, {rand_a[1:0], 62'd0}); - `signal(PUSH_CONCAT_THROUGH_NOTS, {~rand_a, ~rand_b} ); + `signal(PUSH_CONCAT_THROUGH_NOTS, {~(rand_a+64'd101), ~(rand_b+64'd101)} ); `signal(REMOVE_CONCAT_OF_ADJOINING_SELS, {`DFG(rand_a[10:3]), `DFG(rand_a[2:1])}); `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS, {rand_a[10:3], {rand_a[2:1], rand_b}}); `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS, {`DFG({rand_b, rand_a[10:3]}), rand_a[2:1]}); @@ -97,12 +96,12 @@ module t ( `signal(PUSH_BITWISE_THROUGH_REDUCTION_AND, (&rand_a) & (&rand_b)); `signal(PUSH_BITWISE_THROUGH_REDUCTION_OR, (|rand_a) | (|rand_b)); `signal(PUSH_BITWISE_THROUGH_REDUCTION_XOR, (^rand_a) ^ (^rand_b)); - `signal(PUSH_REDUCTION_THROUGH_CONCAT_AND, &`DFG({rand_a, rand_b})); - `signal(PUSH_REDUCTION_THROUGH_CONCAT_OR, |`DFG({rand_a, rand_b})); - `signal(PUSH_REDUCTION_THROUGH_CONCAT_XOR, ^`DFG({rand_a, rand_b})); - `signal(REMOVE_WIDTH_ONE_REDUCTION_AND, &`DFG({randbit_a, rand_b})); - `signal(REMOVE_WIDTH_ONE_REDUCTION_OR, |`DFG({randbit_a, rand_b})); - `signal(REMOVE_WIDTH_ONE_REDUCTION_XOR, ^`DFG({randbit_a, rand_b})); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_AND, &`DFG({(rand_a + 64'd102), rand_b})); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_OR, |`DFG({(rand_a + 64'd103), rand_b})); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_XOR, ^`DFG({(rand_a + 64'd104), rand_b})); + `signal(REMOVE_WIDTH_ONE_REDUCTION_AND, &`DFG({randbit_a ^ rand_a[0], rand_b})); + `signal(REMOVE_WIDTH_ONE_REDUCTION_OR, |`DFG({randbit_a ^ rand_a[1], rand_b})); + `signal(REMOVE_WIDTH_ONE_REDUCTION_XOR, ^`DFG({randbit_a ^ rand_a[2], rand_b})); `signal(REMOVE_XOR_WITH_ZERO, `ZERO ^ rand_a); `signal(REMOVE_XOR_WITH_ONES, `ONES ^ rand_a); `signal(REPLACE_COND_DEC, randbit_a ? rand_b - 64'b1 : rand_b); From f87fe4c3b4123a4c828fbf4c2874bad1ce149f28 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 4 Oct 2022 21:18:39 +0100 Subject: [PATCH 081/177] DfgPeephole: add constant folding for all integer types Also added a testing only -fno-const-before-dfg option, as otherwise V3Const eats up a lot of the simple inputs. A lot of the things V3Const swallows in the simple cases can make it to DFG in complex cases, or DFG itself can create them during optimization. In any case to save complexity of testing DFG constant folding, we use this option to turn off V3Const prior to the DFG passes in the relevant test. --- docs/guide/exe_verilator.rst | 6 + src/V3DfgPeephole.cpp | 841 +++++++++++++++++++------------ src/V3DfgPeephole.h | 12 +- src/V3Options.cpp | 1 + src/V3Options.h | 2 + src/Verilator.cpp | 4 +- test_regress/t/t_dfg_peephole.pl | 1 + test_regress/t/t_dfg_peephole.v | 129 +++-- 8 files changed, 636 insertions(+), 360 deletions(-) diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index d06d8ae12..4b1e0a458 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -501,6 +501,12 @@ Summary: .. option:: -fno-const +.. options: -fno-const-before-dfg + + Do not apply any global expression folding prior to the DFG pass. This + option is solely for the purpose of DFG testing and should not be used + otherwise. + .. option:: -fno-const-bit-op-tree .. option:: -fno-dedup diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 9e8a761ed..ca8d5b95e 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -78,6 +78,58 @@ template <> struct BitwiseToReductionImpl { using type = DfgRedOr; }; template <> struct BitwiseToReductionImpl { using type = DfgRedXor; }; template using BitwiseToReduction = typename BitwiseToReductionImpl::type; + +namespace { +template void foldOp(V3Number& out, const V3Number& src); +template <> void foldOp (V3Number& out, const V3Number& src) { out.opCLog2(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opCountOnes(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opAssign(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opExtendS(src, src.width()); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opLogNot(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opNegate(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opNot(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opOneHot(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opOneHot0(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opRedAnd(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opRedOr(src); } +template <> void foldOp (V3Number& out, const V3Number& src) { out.opRedXor(src); } + +template void foldOp(V3Number& out, const V3Number& lhs, const V3Number& rhs); +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opAdd(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opAnd(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opConcat(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opDiv(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opDivS(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opEq(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opGt(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opGtS(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opGte(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opGteS(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLogAnd(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLogEq(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLogIf(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLogOr(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLt(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLtS(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLte(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opLtS(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opModDiv(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opModDivS(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opMul(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opMulS(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opNeq(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opOr(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opPow(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opPowSS(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opPowSU(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opPowUS(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opRepl(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opShiftL(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opShiftR(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opShiftRS(lhs, rhs, lhs.width()); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opSub(lhs, rhs); } +template <> void foldOp (V3Number& out, const V3Number& lhs, const V3Number& rhs) { out.opXor(lhs, rhs); } +} // clang-format on class V3DfgPeephole final : public DfgVisitor { @@ -114,6 +166,40 @@ class V3DfgPeephole final : public DfgVisitor { // Create a new DfgConst vertex with the given width and value zero DfgConst* makeZero(FileLine* flp, uint32_t width) { return makeConst(flp, width, 0); } + // Constant fold unary vertex, return true if folded + template + bool foldUnary(Vertex* vtxp) { + static_assert(std::is_base_of::value, "Must invoke on unary"); + static_assert(std::is_final::value, "Must invoke on final class"); + if (DfgConst* const srcp = vtxp->srcp()->template cast()) { + APPLYING(FOLD_UNARY) { + DfgConst* const resultp = makeZero(vtxp->fileline(), vtxp->width()); + foldOp(resultp->num(), srcp->num()); + vtxp->replaceWith(resultp); + return true; + } + } + return false; + } + + // Constant fold binary vertex, return true if folded + template + bool foldBinary(Vertex* vtxp) { + static_assert(std::is_base_of::value, "Must invoke on binary"); + static_assert(std::is_final::value, "Must invoke on final class"); + if (DfgConst* const lhsp = vtxp->lhsp()->template cast()) { + if (DfgConst* const rhsp = vtxp->rhsp()->template cast()) { + APPLYING(FOLD_BINARY) { + DfgConst* const resultp = makeZero(vtxp->fileline(), vtxp->width()); + foldOp(resultp->num(), lhsp->num(), rhsp->num()); + vtxp->replaceWith(resultp); + return true; + } + } + } + return false; + } + // Transformations that apply to all commutative binary vertices void commutativeBinary(DfgVertexBinary* vtxp) { DfgVertex* const lhsp = vtxp->source<0>(); @@ -291,6 +377,8 @@ class V3DfgPeephole final : public DfgVisitor { void optimizeReduction(Reduction* vtxp) { using Bitwise = ReductionToBitwise; + if (foldUnary(vtxp)) return; + DfgVertex* const srcp = vtxp->srcp(); FileLine* const flp = vtxp->fileline(); @@ -348,21 +436,6 @@ class V3DfgPeephole final : public DfgVisitor { } } } - - if (DfgConst* const constp = srcp->cast()) { - APPLYING(REPLACE_REDUCTION_OF_CONST) { - DfgConst* const replacementp = makeZero(flp, 1); - if VL_CONSTEXPR_CXX17 (std::is_same::value) { - replacementp->num().opRedAnd(constp->num()); - } else if VL_CONSTEXPR_CXX17 (std::is_same::value) { - replacementp->num().opRedOr(constp->num()); - } else { - replacementp->num().opRedXor(constp->num()); - } - vtxp->replaceWith(replacementp); - return; - } - } } void optimizeShiftRHS(DfgVertexBinary* vtxp) { @@ -379,26 +452,57 @@ class V3DfgPeephole final : public DfgVisitor { void visit(DfgVertex*) override {} + //========================================================================= + // DfgVertexUnary + //========================================================================= + + void visit(DfgCLog2* vtxp) override { + if (foldUnary(vtxp)) return; + } + + void visit(DfgCountOnes* vtxp) override { + if (foldUnary(vtxp)) return; + } + void visit(DfgExtend* vtxp) override { - const uint32_t extension = vtxp->width() - vtxp->srcp()->width(); - UASSERT_OBJ(extension > 0, vtxp, "Useless Extend"); + UASSERT_OBJ(vtxp->width() > vtxp->srcp()->width(), vtxp, "Invalid zero extend"); - FileLine* const flp = vtxp->fileline(); + if (foldUnary(vtxp)) return; - // Convert Extend into Concat with zeros. This simplifies other patterns as they only + // Convert all Extend into Concat with zeros. This simplifies other patterns as they only // need to handle Concat, which is more generic, and don't need special cases for // Extend. APPLYING(REPLACE_EXTEND) { + FileLine* const flp = vtxp->fileline(); DfgConcat* const replacementp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; - replacementp->lhsp(makeZero(flp, extension)); + replacementp->lhsp(makeZero(flp, vtxp->width() - vtxp->srcp()->width())); replacementp->rhsp(vtxp->srcp()); vtxp->replaceWith(replacementp); } } + void visit(DfgExtendS* vtxp) override { + UASSERT_OBJ(vtxp->width() > vtxp->srcp()->width(), vtxp, "Invalid sign extend"); + + if (foldUnary(vtxp)) return; + } + + void visit(DfgLogNot* vtxp) override { + UASSERT_OBJ(vtxp->width() == 1, vtxp, "Incorrect width"); + + if (foldUnary(vtxp)) return; + } + + void visit(DfgNegate* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->srcp()->width(), vtxp, "Mismatched width"); + + if (foldUnary(vtxp)) return; + } + void visit(DfgNot* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->srcp()->width(), vtxp, - "Mismatched width: " << vtxp->width() << " != " << vtxp->srcp()->width()); + UASSERT_OBJ(vtxp->width() == vtxp->srcp()->width(), vtxp, "Mismatched width"); + + if (foldUnary(vtxp)) return; // Not of Cond if (DfgCond* const condp = vtxp->srcp()->cast()) { @@ -460,22 +564,32 @@ class V3DfgPeephole final : public DfgVisitor { } } } - - // Not of Const - if (DfgConst* const constp = vtxp->srcp()->cast()) { - APPLYING(REPLACE_NOT_OF_CONST) { - DfgConst* const replacementp = makeZero(vtxp->fileline(), vtxp->width()); - replacementp->num().opNot(constp->num()); - vtxp->replaceWith(replacementp); - return; - } - } } + void visit(DfgOneHot* vtxp) override { + if (foldUnary(vtxp)) return; + } + + void visit(DfgOneHot0* vtxp) override { + if (foldUnary(vtxp)) return; + } + + void visit(DfgRedOr* vtxp) override { optimizeReduction(vtxp); } + + void visit(DfgRedAnd* vtxp) override { optimizeReduction(vtxp); } + + void visit(DfgRedXor* vtxp) override { optimizeReduction(vtxp); } + + //========================================================================= + // DfgVertexBinary - bitwise + //========================================================================= + void visit(DfgAnd* vtxp) override { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + if (foldBinary(vtxp)) return; + commutativeBinary(vtxp); associativeBinary(vtxp); @@ -515,15 +629,6 @@ class V3DfgPeephole final : public DfgVisitor { } if (DfgConst* const lhsConstp = lhsp->cast()) { - if (DfgConst* const rhsConstp = rhsp->cast()) { - APPLYING(REPLACE_AND_OF_CONST_AND_CONST) { - DfgConst* const replacementp = makeZero(flp, vtxp->width()); - replacementp->num().opAnd(lhsConstp->num(), rhsConstp->num()); - vtxp->replaceWith(replacementp); - return; - } - } - if (lhsConstp->isZero()) { APPLYING(REPLACE_AND_WITH_ZERO) { vtxp->replaceWith(lhsConstp); @@ -561,6 +666,8 @@ class V3DfgPeephole final : public DfgVisitor { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + if (foldBinary(vtxp)) return; + commutativeBinary(vtxp); associativeBinary(vtxp); @@ -627,15 +734,6 @@ class V3DfgPeephole final : public DfgVisitor { } if (DfgConst* const lhsConstp = lhsp->cast()) { - if (DfgConst* const rhsConstp = rhsp->cast()) { - APPLYING(REPLACE_OR_OF_CONST_AND_CONST) { - DfgConst* const replacementp = makeZero(flp, vtxp->width()); - replacementp->num().opOr(lhsConstp->num(), rhsConstp->num()); - vtxp->replaceWith(replacementp); - return; - } - } - if (lhsConstp->isZero()) { APPLYING(REMOVE_OR_WITH_ZERO) { vtxp->replaceWith(rhsp); @@ -674,6 +772,8 @@ class V3DfgPeephole final : public DfgVisitor { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + if (foldBinary(vtxp)) return; + commutativeBinary(vtxp); associativeBinary(vtxp); @@ -700,266 +800,44 @@ class V3DfgPeephole final : public DfgVisitor { tryPushBitwiseOpThroughConcat(vtxp, lConstp, rConcatp); return; } - if (DfgConst* const rConstp = rhsp->cast()) { - APPLYING(REPLACE_XOR_OF_CONST_AND_CONST) { - DfgConst* const replacementp = makeZero(flp, vtxp->width()); - replacementp->num().opXor(lConstp->num(), rConstp->num()); - vtxp->replaceWith(replacementp); - return; - } - } } if (tryPushBitwiseOpThroughReductions(vtxp)) return; } + //========================================================================= + // DfgVertexBinary - other + //========================================================================= + void visit(DfgAdd* vtxp) override { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + if (foldBinary(vtxp)) return; + commutativeBinary(vtxp); associativeBinary(vtxp); } - void visit(DfgSub* vtxp) override { - DfgVertex* const lhsp = vtxp->lhsp(); - DfgVertex* const rhsp = vtxp->rhsp(); - UASSERT_OBJ(lhsp->width() == rhsp->width(), vtxp, "Width mismatch"); - UASSERT_OBJ(lhsp->width() == vtxp->width(), vtxp, "Width mismatch"); - if (DfgConst* const rConstp = rhsp->cast()) { - if (rConstp->isZero()) { - APPLYING(REMOVE_SUB_ZERO) { - vtxp->replaceWith(lhsp); - return; - } - } - if (vtxp->width() == 1 && rConstp->toU32() == 1) { - APPLYING(REPLACE_SUB_WITH_NOT) { - DfgNot* const replacementp = new DfgNot{m_dfg, vtxp->fileline(), m_bitDType}; - replacementp->srcp(lhsp); - vtxp->replaceWith(replacementp); - return; - } - } - } - } - - void visit(DfgShiftL* vtxp) override { optimizeShiftRHS(vtxp); } - void visit(DfgShiftR* vtxp) override { optimizeShiftRHS(vtxp); } - void visit(DfgShiftRS* vtxp) override { optimizeShiftRHS(vtxp); } - - void visit(DfgEq* vtxp) override { - commutativeBinary(vtxp); - - DfgVertex* const lhsp = vtxp->lhsp(); - DfgVertex* const rhsp = vtxp->rhsp(); - - if (DfgConst* const lhsConstp = lhsp->cast()) { - if (DfgConst* const rhsConstp = rhsp->cast()) { - APPLYING(REPLACE_EQ_OF_CONST_AND_CONST) { - DfgConst* const replacementp = makeZero(vtxp->fileline(), 1); - replacementp->num().opEq(lhsConstp->num(), rhsConstp->num()); - vtxp->replaceWith(replacementp); - return; - } - } - - if (DfgConcat* const rhsConcatp = rhsp->cast()) { - if (tryPushCompareOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; - } - } - } - - void visit(DfgSel* vtxp) override { - DfgVertex* const fromp = vtxp->fromp(); - DfgConst* const lsbp = vtxp->lsbp()->cast(); - DfgConst* const widthp = vtxp->widthp()->cast(); - if (!lsbp || !widthp) return; - - FileLine* const flp = vtxp->fileline(); - - UASSERT_OBJ(lsbp->toI32() >= 0, vtxp, "Negative LSB in Sel"); - - const uint32_t lsb = lsbp->toU32(); - const uint32_t width = widthp->toU32(); - const uint32_t msb = lsb + width - 1; - - UASSERT_OBJ(width == vtxp->width(), vtxp, "Incorrect Sel width"); - - // Full width select, replace with the source. - if (fromp->width() == width) { - UASSERT_OBJ(lsb == 0, fromp, "OOPS"); - APPLYING(REMOVE_FULL_WIDTH_SEL) { - vtxp->replaceWith(fromp); - return; - } - } - - // Sel from Concat - if (DfgConcat* const concatp = fromp->cast()) { - DfgVertex* const lhsp = concatp->lhsp(); - DfgVertex* const rhsp = concatp->rhsp(); - - if (msb < rhsp->width()) { - // If the select is entirely from rhs, then replace with sel from rhs - APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) { // - vtxp->fromp(rhsp); - } - } else if (lsb >= rhsp->width()) { - // If the select is entirely from the lhs, then replace with sel from lhs - APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) { - vtxp->fromp(lhsp); - vtxp->lsbp(makeI32(flp, lsb - rhsp->width())); - } - } else if (lsb == 0 || msb == concatp->width() - 1 // - || lhsp->is() || rhsp->is() // - || !concatp->hasMultipleSinks()) { - // If the select straddles both sides, but at least one of the sides is wholly - // selected, or at least one of the sides is a Const, or this concat has no other - // use, then push the Sel past the Concat - APPLYING(PUSH_SEL_THROUGH_CONCAT) { - const uint32_t rSelWidth = rhsp->width() - lsb; - const uint32_t lSelWidth = width - rSelWidth; - - // The new Lhs vertex - DfgSel* const newLhsp = new DfgSel{m_dfg, flp, dtypeForWidth(lSelWidth)}; - newLhsp->fromp(lhsp); - newLhsp->lsbp(makeI32(lsbp->fileline(), 0)); - newLhsp->widthp(makeI32(widthp->fileline(), lSelWidth)); - - // The new Rhs vertex - DfgSel* const newRhsp = new DfgSel{m_dfg, flp, dtypeForWidth(rSelWidth)}; - newRhsp->fromp(rhsp); - newRhsp->lsbp(makeI32(lsbp->fileline(), lsb)); - newRhsp->widthp(makeI32(widthp->fileline(), rSelWidth)); - - // The replacement Concat vertex - DfgConcat* const newConcat - = new DfgConcat{m_dfg, concatp->fileline(), vtxp->dtypep()}; - newConcat->lhsp(newLhsp); - newConcat->rhsp(newRhsp); - - // Replace this vertex - vtxp->replaceWith(newConcat); - return; - } - } - } - - if (DfgReplicate* const repp = fromp->cast()) { - // If the Sel is wholly into the source of the Replicate, push the Sel through the - // Replicate and apply it directly to the source of the Replicate. - const uint32_t srcWidth = repp->srcp()->width(); - if (width <= srcWidth) { - const uint32_t newLsb = lsb % srcWidth; - if (newLsb + width <= srcWidth) { - APPLYING(PUSH_SEL_THROUGH_REPLICATE) { - vtxp->fromp(repp->srcp()); - vtxp->lsbp(makeI32(flp, newLsb)); + void visit(DfgArraySel* vtxp) override { + if (DfgConst* const idxp = vtxp->bitp()->cast()) { + if (DfgVarArray* const varp = vtxp->fromp()->cast()) { + const uint32_t idx = idxp->toU32(); + if (DfgVertex* const driverp = varp->driverAt(idx)) { + APPLYING(INLINE_ARRAYSEL) { + vtxp->replaceWith(driverp); + return; } } } } - - // Sel from Not - if (DfgNot* const notp = fromp->cast()) { - // Replace "Sel from Not" with "Not of Sel" - if (!notp->hasMultipleSinks()) { - UASSERT_OBJ(notp->srcp()->width() == notp->width(), notp, "Mismatched widths"); - APPLYING(PUSH_SEL_THROUGH_NOT) { - // Make Sel select from source of Not - vtxp->fromp(notp->srcp()); - // Add Not after Sel - DfgNot* const replacementp - = new DfgNot{m_dfg, notp->fileline(), vtxp->dtypep()}; - vtxp->replaceWith(replacementp); - replacementp->srcp(vtxp); - } - } - } - - // Sel from Sel - if (DfgSel* const selp = fromp->cast()) { - UASSERT_OBJ(widthp->toU32() <= selp->width(), vtxp, "Out of bound Sel"); - if (DfgConst* const sourceLsbp = selp->lsbp()->cast()) { - UASSERT_OBJ(sourceLsbp->toI32() >= 0, selp, "negative"); - UASSERT_OBJ(selp->widthp()->as()->toU32() >= widthp->toU32(), selp, - "negative"); - APPLYING(REPLACE_SEL_FROM_SEL) { - // Make this Sel select from the source of the source Sel - vtxp->fromp(selp->fromp()); - // Adjust LSB - vtxp->lsbp(makeI32(flp, lsb + sourceLsbp->toU32())); - } - } - } - - // Sel from Cond - if (DfgCond* const condp = fromp->cast()) { - // If at least one of the branches are a constant, push the select past the cond - if (condp->thenp()->is() || condp->elsep()->is()) { - APPLYING(PUSH_SEL_THROUGH_COND) { - // The new 'then' vertex - DfgSel* const newThenp = new DfgSel{m_dfg, flp, vtxp->dtypep()}; - newThenp->fromp(condp->thenp()); - newThenp->lsbp(makeI32(lsbp->fileline(), lsb)); - newThenp->widthp(makeI32(widthp->fileline(), width)); - - // The new 'else' vertex - DfgSel* const newElsep = new DfgSel{m_dfg, flp, vtxp->dtypep()}; - newElsep->fromp(condp->elsep()); - newElsep->lsbp(makeI32(lsbp->fileline(), lsb)); - newElsep->widthp(makeI32(widthp->fileline(), width)); - - // The replacement Cond vertex - DfgCond* const newCondp - = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()}; - newCondp->condp(condp->condp()); - newCondp->thenp(newThenp); - newCondp->elsep(newElsep); - - // Replace this vertex - vtxp->replaceWith(newCondp); - return; - } - } - } - - // Sel from ShiftL - if (DfgShiftL* const shiftLp = fromp->cast()) { - // If selecting bottom bits of left shift, push the Sel before the shift - if (lsb == 0) { - UASSERT_OBJ(shiftLp->lhsp()->width() >= width, vtxp, "input of shift narrow"); - APPLYING(PUSH_SEL_THROUGH_SHIFTL) { - vtxp->fromp(shiftLp->lhsp()); - DfgShiftL* const newShiftLp - = new DfgShiftL{m_dfg, shiftLp->fileline(), vtxp->dtypep()}; - vtxp->replaceWith(newShiftLp); - newShiftLp->lhsp(vtxp); - newShiftLp->rhsp(shiftLp->rhsp()); - } - } - } - - // Sel from Const - if (DfgConst* const constp = fromp->cast()) { - APPLYING(REPLACE_SEL_FROM_CONST) { - DfgConst* const replacementp = makeZero(flp, width); - replacementp->num().opSel(constp->num(), msb, lsb); - vtxp->replaceWith(replacementp); - return; - } - } } - void visit(DfgRedOr* vtxp) override { optimizeReduction(vtxp); } - void visit(DfgRedAnd* vtxp) override { optimizeReduction(vtxp); } - void visit(DfgRedXor* vtxp) override { optimizeReduction(vtxp); } - void visit(DfgConcat* vtxp) override { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width() + vtxp->rhsp()->width(), vtxp, - "Incorrect Concat width: " << vtxp->width() << " != " << vtxp->lhsp()->width() - << " + " << vtxp->rhsp()->width()); + "Inconsisend Concat"); + + if (foldBinary(vtxp)) return; DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); @@ -995,13 +873,6 @@ class V3DfgPeephole final : public DfgVisitor { DfgConst* const lConstp = lhsp->cast(); DfgConst* const rConstp = rhsp->cast(); - if (lConstp && rConstp) { - APPLYING(REPLACE_CONCAT_OF_CONSTS) { - vtxp->replaceWith(joinConsts(lConstp, rConstp, flp)); - return; - } - } - if (lConstp) { if (DfgConcat* const rConcatp = rhsp->cast()) { if (DfgConst* const rlConstp = rConcatp->lhsp()->cast()) { @@ -1162,17 +1033,371 @@ class V3DfgPeephole final : public DfgVisitor { } } + void visit(DfgDiv* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgDivS* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgEq* vtxp) override { + if (foldBinary(vtxp)) return; + + commutativeBinary(vtxp); + + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + + if (DfgConst* const lhsConstp = lhsp->cast()) { + if (DfgConcat* const rhsConcatp = rhsp->cast()) { + if (tryPushCompareOpThroughConcat(vtxp, lhsConstp, rhsConcatp)) return; + } + } + } + + void visit(DfgGt* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgGtS* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgGte* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgGteS* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgLogAnd* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgLogEq* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgLogIf* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgLogOr* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgLt* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgLtS* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgLte* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgLteS* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgModDiv* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgModDivS* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgMul* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgMulS* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgNeq* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgPow* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgPowSS* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgPowSU* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgPowUS* vtxp) override { + if (foldBinary(vtxp)) return; + } + + void visit(DfgReplicate* vtxp) override { + if (vtxp->width() == vtxp->srcp()->width()) { + APPLYING(REMOVE_REPLICATE_ONCE) { + vtxp->replaceWith(vtxp->srcp()); + return; + } + } + + if (foldBinary(vtxp)) return; + } + + void visit(DfgShiftL* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched width"); + + if (foldBinary(vtxp)) return; + + optimizeShiftRHS(vtxp); + } + + void visit(DfgShiftR* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched width"); + + if (foldBinary(vtxp)) return; + + optimizeShiftRHS(vtxp); + } + + void visit(DfgShiftRS* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched width"); + + if (foldBinary(vtxp)) return; + + optimizeShiftRHS(vtxp); + } + + void visit(DfgSub* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + + if (foldBinary(vtxp)) return; + + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + + if (DfgConst* const rConstp = rhsp->cast()) { + if (rConstp->isZero()) { + APPLYING(REMOVE_SUB_ZERO) { + vtxp->replaceWith(lhsp); + return; + } + } + if (vtxp->width() == 1 && rConstp->toU32() == 1) { + APPLYING(REPLACE_SUB_WITH_NOT) { + DfgNot* const replacementp = new DfgNot{m_dfg, vtxp->fileline(), m_bitDType}; + replacementp->srcp(lhsp); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + + //========================================================================= + // DfgVertexTernary + //========================================================================= + + void visit(DfgSel* vtxp) override { + DfgVertex* const fromp = vtxp->fromp(); + DfgConst* const lsbp = vtxp->lsbp()->cast(); + DfgConst* const widthp = vtxp->widthp()->cast(); + if (!lsbp || !widthp) return; + + FileLine* const flp = vtxp->fileline(); + + UASSERT_OBJ(lsbp->toI32() >= 0, vtxp, "Negative LSB in Sel"); + + const uint32_t lsb = lsbp->toU32(); + const uint32_t width = widthp->toU32(); + const uint32_t msb = lsb + width - 1; + + UASSERT_OBJ(width == vtxp->width(), vtxp, "Incorrect Sel width"); + + if (DfgConst* const constp = fromp->cast()) { + APPLYING(FOLD_SEL) { + DfgConst* const replacementp = makeZero(flp, width); + replacementp->num().opSel(constp->num(), msb, lsb); + vtxp->replaceWith(replacementp); + return; + } + } + + // Full width select, replace with the source. + if (fromp->width() == width) { + UASSERT_OBJ(lsb == 0, fromp, "OOPS"); + APPLYING(REMOVE_FULL_WIDTH_SEL) { + vtxp->replaceWith(fromp); + return; + } + } + + // Sel from Concat + if (DfgConcat* const concatp = fromp->cast()) { + DfgVertex* const lhsp = concatp->lhsp(); + DfgVertex* const rhsp = concatp->rhsp(); + + if (msb < rhsp->width()) { + // If the select is entirely from rhs, then replace with sel from rhs + APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) { // + vtxp->fromp(rhsp); + } + } else if (lsb >= rhsp->width()) { + // If the select is entirely from the lhs, then replace with sel from lhs + APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) { + vtxp->fromp(lhsp); + vtxp->lsbp(makeI32(flp, lsb - rhsp->width())); + } + } else if (lsb == 0 || msb == concatp->width() - 1 // + || lhsp->is() || rhsp->is() // + || !concatp->hasMultipleSinks()) { + // If the select straddles both sides, but at least one of the sides is wholly + // selected, or at least one of the sides is a Const, or this concat has no other + // use, then push the Sel past the Concat + APPLYING(PUSH_SEL_THROUGH_CONCAT) { + const uint32_t rSelWidth = rhsp->width() - lsb; + const uint32_t lSelWidth = width - rSelWidth; + + // The new Lhs vertex + DfgSel* const newLhsp = new DfgSel{m_dfg, flp, dtypeForWidth(lSelWidth)}; + newLhsp->fromp(lhsp); + newLhsp->lsbp(makeI32(lsbp->fileline(), 0)); + newLhsp->widthp(makeI32(widthp->fileline(), lSelWidth)); + + // The new Rhs vertex + DfgSel* const newRhsp = new DfgSel{m_dfg, flp, dtypeForWidth(rSelWidth)}; + newRhsp->fromp(rhsp); + newRhsp->lsbp(makeI32(lsbp->fileline(), lsb)); + newRhsp->widthp(makeI32(widthp->fileline(), rSelWidth)); + + // The replacement Concat vertex + DfgConcat* const newConcat + = new DfgConcat{m_dfg, concatp->fileline(), vtxp->dtypep()}; + newConcat->lhsp(newLhsp); + newConcat->rhsp(newRhsp); + + // Replace this vertex + vtxp->replaceWith(newConcat); + return; + } + } + } + + if (DfgReplicate* const repp = fromp->cast()) { + // If the Sel is wholly into the source of the Replicate, push the Sel through the + // Replicate and apply it directly to the source of the Replicate. + const uint32_t srcWidth = repp->srcp()->width(); + if (width <= srcWidth) { + const uint32_t newLsb = lsb % srcWidth; + if (newLsb + width <= srcWidth) { + APPLYING(PUSH_SEL_THROUGH_REPLICATE) { + vtxp->fromp(repp->srcp()); + vtxp->lsbp(makeI32(flp, newLsb)); + } + } + } + } + + // Sel from Not + if (DfgNot* const notp = fromp->cast()) { + // Replace "Sel from Not" with "Not of Sel" + if (!notp->hasMultipleSinks()) { + UASSERT_OBJ(notp->srcp()->width() == notp->width(), notp, "Mismatched widths"); + APPLYING(PUSH_SEL_THROUGH_NOT) { + // Make Sel select from source of Not + vtxp->fromp(notp->srcp()); + // Add Not after Sel + DfgNot* const replacementp + = new DfgNot{m_dfg, notp->fileline(), vtxp->dtypep()}; + vtxp->replaceWith(replacementp); + replacementp->srcp(vtxp); + } + } + } + + // Sel from Sel + if (DfgSel* const selp = fromp->cast()) { + UASSERT_OBJ(widthp->toU32() <= selp->width(), vtxp, "Out of bound Sel"); + if (DfgConst* const sourceLsbp = selp->lsbp()->cast()) { + UASSERT_OBJ(sourceLsbp->toI32() >= 0, selp, "negative"); + UASSERT_OBJ(selp->widthp()->as()->toU32() >= widthp->toU32(), selp, + "negative"); + APPLYING(REPLACE_SEL_FROM_SEL) { + // Make this Sel select from the source of the source Sel + vtxp->fromp(selp->fromp()); + // Adjust LSB + vtxp->lsbp(makeI32(flp, lsb + sourceLsbp->toU32())); + } + } + } + + // Sel from Cond + if (DfgCond* const condp = fromp->cast()) { + // If at least one of the branches are a constant, push the select past the cond + if (condp->thenp()->is() || condp->elsep()->is()) { + APPLYING(PUSH_SEL_THROUGH_COND) { + // The new 'then' vertex + DfgSel* const newThenp = new DfgSel{m_dfg, flp, vtxp->dtypep()}; + newThenp->fromp(condp->thenp()); + newThenp->lsbp(makeI32(lsbp->fileline(), lsb)); + newThenp->widthp(makeI32(widthp->fileline(), width)); + + // The new 'else' vertex + DfgSel* const newElsep = new DfgSel{m_dfg, flp, vtxp->dtypep()}; + newElsep->fromp(condp->elsep()); + newElsep->lsbp(makeI32(lsbp->fileline(), lsb)); + newElsep->widthp(makeI32(widthp->fileline(), width)); + + // The replacement Cond vertex + DfgCond* const newCondp + = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()}; + newCondp->condp(condp->condp()); + newCondp->thenp(newThenp); + newCondp->elsep(newElsep); + + // Replace this vertex + vtxp->replaceWith(newCondp); + return; + } + } + } + + // Sel from ShiftL + if (DfgShiftL* const shiftLp = fromp->cast()) { + // If selecting bottom bits of left shift, push the Sel before the shift + if (lsb == 0) { + UASSERT_OBJ(shiftLp->lhsp()->width() >= width, vtxp, "input of shift narrow"); + APPLYING(PUSH_SEL_THROUGH_SHIFTL) { + vtxp->fromp(shiftLp->lhsp()); + DfgShiftL* const newShiftLp + = new DfgShiftL{m_dfg, shiftLp->fileline(), vtxp->dtypep()}; + vtxp->replaceWith(newShiftLp); + newShiftLp->lhsp(vtxp); + newShiftLp->rhsp(shiftLp->rhsp()); + } + } + } + } + void visit(DfgCond* vtxp) override { + UASSERT_OBJ(vtxp->width() == vtxp->thenp()->width(), vtxp, "Width mismatch"); + UASSERT_OBJ(vtxp->width() == vtxp->elsep()->width(), vtxp, "Width mismatch"); + DfgVertex* const condp = vtxp->condp(); DfgVertex* const thenp = vtxp->thenp(); DfgVertex* const elsep = vtxp->elsep(); - UASSERT_OBJ(vtxp->width() == thenp->width(), vtxp, "Width mismatch"); - UASSERT_OBJ(vtxp->width() == elsep->width(), vtxp, "Width mismatch"); + FileLine* const flp = vtxp->fileline(); if (condp->width() != 1) return; - FileLine* const flp = vtxp->fileline(); - if (condp->isOnes()) { APPLYING(REMOVE_COND_WITH_TRUE_CONDITION) { vtxp->replaceWith(thenp); @@ -1319,19 +1544,9 @@ class V3DfgPeephole final : public DfgVisitor { } } - void visit(DfgArraySel* vtxp) override { - if (DfgConst* const idxp = vtxp->bitp()->cast()) { - if (DfgVarArray* const varp = vtxp->fromp()->cast()) { - const uint32_t idx = idxp->toU32(); - if (DfgVertex* const driverp = varp->driverAt(idx)) { - APPLYING(INLINE_ARRAYSEL) { - vtxp->replaceWith(driverp); - return; - } - } - } - } - } + //========================================================================= + // DfgVertexVar + //========================================================================= void visit(DfgVarPacked* vtxp) override { // Inline variables fully driven by the logic represented by the DFG diff --git a/src/V3DfgPeephole.h b/src/V3DfgPeephole.h index 5d346456d..bec8a5c8f 100644 --- a/src/V3DfgPeephole.h +++ b/src/V3DfgPeephole.h @@ -26,6 +26,9 @@ // Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests). // clang-format off #define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_SEL) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_UNARY) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_VAR) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \ @@ -49,16 +52,15 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_NOT_NOT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_OR_WITH_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_REPLICATE_ONCE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_LHS_OF_CONCAT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SEL_FROM_RHS_OF_CONCAT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_SUB_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_WIDTH_ONE_REDUCTION) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REMOVE_XOR_WITH_ZERO) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_CONST_AND_CONST) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_NOT_AND_NEQ) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_OF_NOT_AND_NOT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_AND_WITH_ZERO) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_OF_CONSTS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \ @@ -68,7 +70,6 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_WITH_THEN_BRANCH_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONTRADICTORY_AND) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EQ_OF_CONST_AND_CONST) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EXTEND) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS) \ @@ -76,19 +77,14 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_EQ) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_NEQ) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_OF_CONST) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONST_AND_CONST) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NEQ) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_NOT_AND_NOT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_WITH_ONES) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_REDUCTION_OF_CONST) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_CONST) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SEL_FROM_SEL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_SUB_WITH_NOT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_TAUTOLOGICAL_OR) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_XOR_OF_CONST_AND_CONST) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_XOR_WITH_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, RIGHT_LEANING_ASSOC) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, RIGHT_LEANING_CONCAT) \ diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 08e5902db..a13c15c48 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1132,6 +1132,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char DECL_OPTION("-fcase", FOnOff, &m_fCase); DECL_OPTION("-fcombine", FOnOff, &m_fCombine); DECL_OPTION("-fconst", FOnOff, &m_fConst); + DECL_OPTION("-fconst-before-dfg", FOnOff, &m_fConstBeforeDfg); DECL_OPTION("-fconst-bit-op-tree", FOnOff, &m_fConstBitOpTree); DECL_OPTION("-fdedup", FOnOff, &m_fDedupe); DECL_OPTION("-fdfg", CbFOnOff, [this](bool flag) { diff --git a/src/V3Options.h b/src/V3Options.h index 8eeff57b4..7503408ad 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -349,6 +349,7 @@ private: bool m_fCase; // main switch: -fno-case: case tree conversion bool m_fCombine; // main switch: -fno-combine: common icode packing bool m_fConst; // main switch: -fno-const: constant folding + bool m_fConstBeforeDfg = true; // main switch: -fno-const-before-dfg for testing only! bool m_fConstBitOpTree; // main switch: -fno-const-bit-op-tree constant bit op tree bool m_fDedupe; // main switch: -fno-dedupe: logic deduplication bool m_fDfgPeephole = true; // main switch: -fno-dfg-peephole @@ -598,6 +599,7 @@ public: bool fCase() const { return m_fCase; } bool fCombine() const { return m_fCombine; } bool fConst() const { return m_fConst; } + bool fConstBeforeDfg() const { return m_fConstBeforeDfg; } bool fConstBitOpTree() const { return m_fConstBitOpTree; } bool fDedupe() const { return m_fDedupe; } bool fDfgPeephole() const { return m_fDfgPeephole; } diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 2bd4798a7..aaf666ea0 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -189,7 +189,7 @@ static void process() { // Push constants, but only true constants preserving liveness // so V3Undriven sees variables to be eliminated, ie "if (0 && foo) ..." - V3Const::constifyAllLive(v3Global.rootp()); + if (v3Global.opt.fConstBeforeDfg()) V3Const::constifyAllLive(v3Global.rootp()); // Signal based lint checks, no change to structures // Must be before first constification pass drops dead code @@ -209,7 +209,7 @@ static void process() { } // Propagate constants into expressions - V3Const::constifyAllLint(v3Global.rootp()); + if (v3Global.opt.fConstBeforeDfg()) V3Const::constifyAllLint(v3Global.rootp()); if (!(v3Global.opt.xmlOnly() && !v3Global.opt.flatten())) { // Split packed variables into multiple pieces to resolve UNOPTFLAT. diff --git a/test_regress/t/t_dfg_peephole.pl b/test_regress/t/t_dfg_peephole.pl index c09227ce8..952898fbb 100755 --- a/test_regress/t/t_dfg_peephole.pl +++ b/test_regress/t/t_dfg_peephole.pl @@ -67,6 +67,7 @@ compile( compile( verilator_flags2 => ["--stats", "--build", "--exe", "+incdir+$Self->{obj_dir}", "-Mdir", "$Self->{obj_dir}/obj_opt", "--prefix", "Vopt", + "-fno-const-before-dfg", # Otherwise V3Const makes testing painful "--dump-dfg", # To fill code coverage "-CFLAGS \"-I .. -I ../obj_ref\"", "../obj_ref/Vref__ALL.a", diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index bc8251345..bf7a1a721 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -4,7 +4,7 @@ // any use, without warranty, 2022 by Geza Lore. // SPDX-License-Identifier: CC0-1.0 -`define signal(name, expr) wire [$bits(expr)-1:0] ``name = expr; +`define signal(name, expr) wire [$bits(expr)-1:0] ``name = expr module t ( `include "portlist.vh" // Boilerplate generated by t_dfg_peephole.pl @@ -23,16 +23,69 @@ module t ( wire logic [127:0] rand_aa = {2{rand_a}}; wire logic [63:0] const_a; wire logic [63:0] const_b; + wire logic signed [63:0] sconst_a; + wire logic signed [63:0] sconst_b; wire logic [63:0] array [3:0]; assign array[0] = (rand_a << 32) | (rand_a >> 32); assign array[1] = (rand_a << 16) | (rand_a >> 48); - // 64 bit all 0 but don't tell V3Const -`define ZERO (const_a & ~const_a) - // 64 bit all 1 but don't tell V3Const -`define ONES (const_a | ~const_a) - // x, but in a way only DFG understands -`define DFG(x) ((|`ONES) ? (x) : (~x)) + // x, but with evaluation slightly delayed in DfgPeephole +`define DFG(x) (&16'hffff ? (x) : (~x)) + + `signal(FOLD_UNARY_CLog2, $clog2(const_a)); + `signal(FOLD_UNARY_CountOnes, $countones(const_a)); + `signal(FOLD_UNARY_IsUnknown, $isunknown(const_a)); + `signal(FOLD_UNARY_LogNot, !const_a[0]); + `signal(FOLD_UNARY_Negate, -const_a); + `signal(FOLD_UNARY_Not, ~const_a); + `signal(FOLD_UNARY_OneHot, $onehot(const_a)); + `signal(FOLD_UNARY_OneHot0, $onehot0(const_a)); + `signal(FOLD_UNARY_RedAnd, &const_a); + `signal(FOLD_UNARY_RedOr, |const_a); + `signal(FOLD_UNARY_RedXor, ^const_a); + // verilator lint_off WIDTH + wire logic [79:0] tmp_FOLD_UNARY_Extend = const_a; + wire logic signed [79:0] tmp_FOLD_UNARY_ExtendS = sconst_a; + //verilator lint_on WIDTH + `signal(FOLD_UNARY_Extend, tmp_FOLD_UNARY_Extend); + `signal(FOLD_UNARY_ExtendS, tmp_FOLD_UNARY_ExtendS); + + `signal(FOLD_BINARY_Add, const_a + const_b); + `signal(FOLD_BINARY_And, const_a & const_b); + `signal(FOLD_BINARY_Concat, {const_a, const_b}); + `signal(FOLD_BINARY_Div, const_a / 64'd3); + `signal(FOLD_BINARY_DivS, sconst_a / 64'sd3); + `signal(FOLD_BINARY_Eq, const_a == const_b); + `signal(FOLD_BINARY_Gt, const_a > const_b); + `signal(FOLD_BINARY_GtS, sconst_a > sconst_b); + `signal(FOLD_BINARY_Gte, const_a >= const_b); + `signal(FOLD_BINARY_GteS, sconst_a >= sconst_b); + `signal(FOLD_BINARY_LogAnd, const_a[0] && const_b[0]); + `signal(FOLD_BINARY_LogEq, const_a[0] <-> const_b[0]); + `signal(FOLD_BINARY_LogIf, const_a[0] -> const_b[0]); + `signal(FOLD_BINARY_LogOr, const_a[0] || const_b[0]); + `signal(FOLD_BINARY_Lt, const_a < const_b); + `signal(FOLD_BINARY_LtS, sconst_a < sconst_b); + `signal(FOLD_BINARY_Lte, const_a <= const_b); + `signal(FOLD_BINARY_LteS, sconst_a <= sconst_b); + `signal(FOLD_BINARY_ModDiv, const_a % 64'd3); + `signal(FOLD_BINARY_ModDivS, sconst_a % 64'sd3); + `signal(FOLD_BINARY_Mul, const_a * 64'd3); + `signal(FOLD_BINARY_MulS, sconst_a * 64'sd3); + `signal(FOLD_BINARY_Neq, const_a != const_b); + `signal(FOLD_BINARY_Or, const_a | const_b); + `signal(FOLD_BINARY_Pow, const_a ** 64'd2); + `signal(FOLD_BINARY_PowSS, sconst_a ** 64'sd2); + `signal(FOLD_BINARY_PowSU, sconst_a ** 64'd2); + `signal(FOLD_BINARY_PowUS, const_a ** 64'sd2); + `signal(FOLD_BINARY_Replicate, {2{const_a}}); + `signal(FOLD_BINARY_ShiftL, const_a << 2); + `signal(FOLD_BINARY_ShiftR, const_a >> 2); + `signal(FOLD_BINARY_ShiftRS, sconst_a >>> 2); + `signal(FOLD_BINARY_Sub, const_a - const_b); + `signal(FOLD_BINARY_Xor, const_a ^ const_b); + + `signal(FOLD_SEL, const_a[3:1]); `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, rand_a + const_a); `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, rand_a + ~rand_a); @@ -46,25 +99,25 @@ module t ( `signal(REPLACE_REDUCTION_OF_CONST_XOR, ^const_a); `signal(REPLACE_EXTEND, 4'(rand_a[0])); `signal(PUSH_NOT_THROUGH_COND, ~(rand_a[0] ? rand_a[4:0] : 5'hb)); - `signal(REMOVE_NOT_NOT, ~`DFG(~`DFG(rand_a))); - `signal(REPLACE_NOT_NEQ, ~`DFG(rand_a != rand_b)); - `signal(REPLACE_NOT_EQ, ~`DFG(rand_a == rand_b)); + `signal(REMOVE_NOT_NOT, ~~rand_a); + `signal(REPLACE_NOT_NEQ, ~(rand_a != rand_b)); + `signal(REPLACE_NOT_EQ, ~(rand_a == rand_b)); `signal(REPLACE_NOT_OF_CONST, ~4'd0); `signal(REPLACE_AND_OF_NOT_AND_NOT, ~rand_a[1] & ~rand_b[1]); `signal(REPLACE_AND_OF_NOT_AND_NEQ, ~rand_a[2] & (rand_b != 64'd2)); `signal(REPLACE_AND_OF_CONST_AND_CONST, const_a & const_b); - `signal(REPLACE_AND_WITH_ZERO, `ZERO & rand_a); - `signal(REMOVE_AND_WITH_ONES, `ONES & rand_a); + `signal(REPLACE_AND_WITH_ZERO, 64'd0 & rand_a); + `signal(REMOVE_AND_WITH_ONES, -64'd1 & rand_a); `signal(REPLACE_CONTRADICTORY_AND, rand_a & ~rand_a); `signal(REPLACE_OR_OF_NOT_AND_NOT, ~rand_a[3] | ~rand_b[3]); `signal(REPLACE_OR_OF_NOT_AND_NEQ, ~rand_a[4] | (rand_b != 64'd3)); `signal(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO, {2'd0, rand_a[1:0]} | {rand_b[1:0], 2'd0}); `signal(REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS, {rand_a[1:0], 2'd0} | {2'd0, rand_b[1:0]}); `signal(REPLACE_OR_OF_CONST_AND_CONST, const_a | const_b); - `signal(REMOVE_OR_WITH_ZERO, `ZERO | rand_a); - `signal(REPLACE_OR_WITH_ONES, `ONES | rand_a); + `signal(REMOVE_OR_WITH_ZERO, 64'd0 | rand_a); + `signal(REPLACE_OR_WITH_ONES, -64'd1 | rand_a); `signal(REPLACE_TAUTOLOGICAL_OR, rand_a | ~rand_a); - `signal(REMOVE_SUB_ZERO, rand_a - `ZERO); + `signal(REMOVE_SUB_ZERO, rand_a - 64'd0); `signal(REPLACE_SUB_WITH_NOT, rand_a[0] - 1'b1); `signal(REMOVE_REDUNDANT_ZEXT_ON_RHS_OF_SHIFT, rand_a << {2'b0, rand_a[2:0]}); `signal(REPLACE_EQ_OF_CONST_AND_CONST, 4'd0 == 4'd1); @@ -80,30 +133,30 @@ module t ( `signal(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR, {62'd0, rand_a[63:62]}); `signal(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL, {rand_a[1:0], 62'd0}); `signal(PUSH_CONCAT_THROUGH_NOTS, {~(rand_a+64'd101), ~(rand_b+64'd101)} ); - `signal(REMOVE_CONCAT_OF_ADJOINING_SELS, {`DFG(rand_a[10:3]), `DFG(rand_a[2:1])}); - `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS, {rand_a[10:3], {rand_a[2:1], rand_b}}); + `signal(REMOVE_CONCAT_OF_ADJOINING_SELS, {rand_a[10:3], rand_a[2:1]}); + `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS, {rand_a[10:3], `DFG({rand_a[2:1], rand_b})}); `signal(REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS, {`DFG({rand_b, rand_a[10:3]}), rand_a[2:1]}); - `signal(REMOVE_COND_WITH_FALSE_CONDITION, &`ZERO ? rand_a : rand_b); - `signal(REMOVE_COND_WITH_TRUE_CONDITION, |`ONES ? rand_a : rand_b); - `signal(SWAP_COND_WITH_NOT_CONDITION, (~rand_a[0] & |`ONES) ? rand_a : rand_b); + `signal(REMOVE_COND_WITH_FALSE_CONDITION, 1'd0 ? rand_a : rand_b); + `signal(REMOVE_COND_WITH_TRUE_CONDITION, 1'd1 ? rand_a : rand_b); + `signal(SWAP_COND_WITH_NOT_CONDITION, (~rand_a[0] & 1'd1) ? rand_a : rand_b); `signal(SWAP_COND_WITH_NEQ_CONDITION, rand_b != rand_a ? rand_a : rand_b); `signal(PULL_NOTS_THROUGH_COND, rand_a[0] ? ~rand_a[4:0] : ~rand_b[4:0]); - `signal(REPLACE_COND_WITH_THEN_BRANCH_ZERO, rand_a[0] ? |`ZERO : rand_a[1]); - `signal(REPLACE_COND_WITH_THEN_BRANCH_ONES, rand_a[0] ? |`ONES : rand_a[1]); - `signal(REPLACE_COND_WITH_ELSE_BRANCH_ZERO, rand_a[0] ? rand_a[1] : |`ZERO); - `signal(REPLACE_COND_WITH_ELSE_BRANCH_ONES, rand_a[0] ? rand_a[1] : |`ONES); + `signal(REPLACE_COND_WITH_THEN_BRANCH_ZERO, rand_a[0] ? 1'd0 : rand_a[1]); + `signal(REPLACE_COND_WITH_THEN_BRANCH_ONES, rand_a[0] ? 1'd1 : rand_a[1]); + `signal(REPLACE_COND_WITH_ELSE_BRANCH_ZERO, rand_a[0] ? rand_a[1] : 1'd0); + `signal(REPLACE_COND_WITH_ELSE_BRANCH_ONES, rand_a[0] ? rand_a[1] : 1'd1); `signal(INLINE_ARRAYSEL, array[0]); - `signal(PUSH_BITWISE_THROUGH_REDUCTION_AND, (&rand_a) & (&rand_b)); - `signal(PUSH_BITWISE_THROUGH_REDUCTION_OR, (|rand_a) | (|rand_b)); - `signal(PUSH_BITWISE_THROUGH_REDUCTION_XOR, (^rand_a) ^ (^rand_b)); - `signal(PUSH_REDUCTION_THROUGH_CONCAT_AND, &`DFG({(rand_a + 64'd102), rand_b})); - `signal(PUSH_REDUCTION_THROUGH_CONCAT_OR, |`DFG({(rand_a + 64'd103), rand_b})); - `signal(PUSH_REDUCTION_THROUGH_CONCAT_XOR, ^`DFG({(rand_a + 64'd104), rand_b})); - `signal(REMOVE_WIDTH_ONE_REDUCTION_AND, &`DFG({randbit_a ^ rand_a[0], rand_b})); - `signal(REMOVE_WIDTH_ONE_REDUCTION_OR, |`DFG({randbit_a ^ rand_a[1], rand_b})); - `signal(REMOVE_WIDTH_ONE_REDUCTION_XOR, ^`DFG({randbit_a ^ rand_a[2], rand_b})); - `signal(REMOVE_XOR_WITH_ZERO, `ZERO ^ rand_a); - `signal(REMOVE_XOR_WITH_ONES, `ONES ^ rand_a); + `signal(PUSH_BITWISE_THROUGH_REDUCTION_AND, (&(rand_a + 64'd105)) & (&(rand_b + 64'd108))); + `signal(PUSH_BITWISE_THROUGH_REDUCTION_OR, (|(rand_a + 64'd106)) | (|(rand_b + 64'd109))); + `signal(PUSH_BITWISE_THROUGH_REDUCTION_XOR, (^(rand_a + 64'd107)) ^ (^(rand_b + 64'd110))); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_AND, &{(rand_a + 64'd102), rand_b}); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_OR, |{(rand_a + 64'd103), rand_b}); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_XOR, ^{(rand_a + 64'd104), rand_b}); + `signal(REMOVE_WIDTH_ONE_REDUCTION_AND, &{randbit_a ^ rand_a[0], rand_b}); + `signal(REMOVE_WIDTH_ONE_REDUCTION_OR, |{randbit_a ^ rand_a[1], rand_b}); + `signal(REMOVE_WIDTH_ONE_REDUCTION_XOR, ^{randbit_a ^ rand_a[2], rand_b}); + `signal(REMOVE_XOR_WITH_ZERO, 64'd0 ^ rand_a); + `signal(REMOVE_XOR_WITH_ONES, -64'd1 ^ rand_a); `signal(REPLACE_COND_DEC, randbit_a ? rand_b - 64'b1 : rand_b); `signal(REPLACE_COND_INC, randbit_a ? rand_b + 64'b1 : rand_b); `signal(RIGHT_LEANING_ASSOC, (((rand_a + rand_b) + rand_a) + rand_b)); @@ -125,6 +178,8 @@ module t ( always @(posedge randbit_a) if ($c(0)) $display(sel_from_not); // Do not remove signal // Assigned at the end to avoid inlining by other passes - assign const_a = (rand_a | ~rand_a) & 64'h0123456789abcdef; - assign const_b = ~(rand_a & ~rand_a) & 64'h98badefc10325647; + assign const_a = 64'h0123456789abcdef; + assign const_b = 64'h98badefc10325647; + assign sconst_a = 64'hfedcba9876543210; + assign sconst_b = 64'hba0123456789cdef; endmodule From 1a8188e1b4af8f35a53521be393fdec07cb1f04f Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Thu, 6 Oct 2022 00:16:05 +0200 Subject: [PATCH 082/177] Fix linker errors in user-facing timing functions (#3657) Before this change, a design verilated with `--timing` that does not actually use timing features would be emitted with `eventsPending` and `nextTimeSlot` declared in the top class. However, their definitions would be missing, leading to linker errors during design compilation. This patch makes Verilator always emit the definitions, which prevents linker errors. Trying to use `nextTimeSlot` without delays in the design will result in an error at runtime. --- src/V3EmitCModel.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 0be57f9c9..f3425caff 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -439,22 +439,20 @@ class EmitCModel final : public EmitCFunc { puts("}\n"); } - if (v3Global.usesTiming()) { - putSectionDelimiter("Events and timing"); - if (auto* const delaySchedp = v3Global.rootp()->delaySchedulerp()) { - puts("bool " + topClassName() + "::eventsPending() { return !vlSymsp->TOP."); - puts(delaySchedp->nameProtect()); - puts(".empty(); }\n\n"); - puts("uint64_t " + topClassName() + "::nextTimeSlot() { return vlSymsp->TOP."); - puts(delaySchedp->nameProtect()); - puts(".nextTimeSlot(); }\n"); - } else { - puts("bool " + topClassName() + "::eventsPending() { return false; }\n\n"); - puts("uint64_t " + topClassName() + "::nextTimeSlot() {\n"); - puts("VL_FATAL_MT(__FILE__, __LINE__, \"\", \"%Error: No delays in the " - "design\");\n"); - puts("return 0;\n}\n"); - } + putSectionDelimiter("Events and timing"); + if (auto* const delaySchedp = v3Global.rootp()->delaySchedulerp()) { + puts("bool " + topClassName() + "::eventsPending() { return !vlSymsp->TOP."); + puts(delaySchedp->nameProtect()); + puts(".empty(); }\n\n"); + puts("uint64_t " + topClassName() + "::nextTimeSlot() { return vlSymsp->TOP."); + puts(delaySchedp->nameProtect()); + puts(".nextTimeSlot(); }\n"); + } else { + puts("bool " + topClassName() + "::eventsPending() { return false; }\n\n"); + puts("uint64_t " + topClassName() + "::nextTimeSlot() {\n"); + puts("VL_FATAL_MT(__FILE__, __LINE__, \"\", \"%Error: No delays in the " + "design\");\n"); + puts("return 0;\n}\n"); } putSectionDelimiter("Utilities"); From 22fcd616aa919f5b8def25d671feaf1090c59e0c Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 5 Oct 2022 22:54:36 +0100 Subject: [PATCH 083/177] DfgPeephole: Further restrict PUSH_REDUCTION_THROUGH_CONCAT Only apply when there is guaranteed to be a subsequent constant folding and elimination of some of the expression, otherwise this sometimes interferes with the simplification of concatenations and harms overall performance. --- src/V3DfgPeephole.cpp | 9 +++++---- test_regress/t/t_dfg_peephole.v | 12 ++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index ca8d5b95e..79c7cd0c6 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -415,12 +415,12 @@ class V3DfgPeephole final : public DfgVisitor { } if (DfgConcat* const concatp = srcp->cast()) { - if (!concatp->hasMultipleSinks()) { + if (concatp->lhsp()->is() || concatp->rhsp()->is()) { APPLYING(PUSH_REDUCTION_THROUGH_CONCAT) { // Reduce the parts of the concatenation - Reduction* const lRedp = new Reduction{m_dfg, srcp->fileline(), m_bitDType}; + Reduction* const lRedp = new Reduction{m_dfg, concatp->fileline(), m_bitDType}; lRedp->srcp(concatp->lhsp()); - Reduction* const rRedp = new Reduction{m_dfg, srcp->fileline(), m_bitDType}; + Reduction* const rRedp = new Reduction{m_dfg, concatp->fileline(), m_bitDType}; rRedp->srcp(concatp->rhsp()); // Bitwise reduce the results @@ -429,9 +429,10 @@ class V3DfgPeephole final : public DfgVisitor { replacementp->rhsp(rRedp); vtxp->replaceWith(replacementp); - // Optimize the new reductions + // Optimize the new terms optimizeReduction(lRedp); optimizeReduction(rRedp); + iterate(replacementp); return; } } diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index bf7a1a721..7f9acc62b 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -149,12 +149,12 @@ module t ( `signal(PUSH_BITWISE_THROUGH_REDUCTION_AND, (&(rand_a + 64'd105)) & (&(rand_b + 64'd108))); `signal(PUSH_BITWISE_THROUGH_REDUCTION_OR, (|(rand_a + 64'd106)) | (|(rand_b + 64'd109))); `signal(PUSH_BITWISE_THROUGH_REDUCTION_XOR, (^(rand_a + 64'd107)) ^ (^(rand_b + 64'd110))); - `signal(PUSH_REDUCTION_THROUGH_CONCAT_AND, &{(rand_a + 64'd102), rand_b}); - `signal(PUSH_REDUCTION_THROUGH_CONCAT_OR, |{(rand_a + 64'd103), rand_b}); - `signal(PUSH_REDUCTION_THROUGH_CONCAT_XOR, ^{(rand_a + 64'd104), rand_b}); - `signal(REMOVE_WIDTH_ONE_REDUCTION_AND, &{randbit_a ^ rand_a[0], rand_b}); - `signal(REMOVE_WIDTH_ONE_REDUCTION_OR, |{randbit_a ^ rand_a[1], rand_b}); - `signal(REMOVE_WIDTH_ONE_REDUCTION_XOR, ^{randbit_a ^ rand_a[2], rand_b}); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_AND, &{1'd1, rand_b}); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_OR, |{1'd1, rand_b}); + `signal(PUSH_REDUCTION_THROUGH_CONCAT_XOR, ^{1'd1, rand_b}); + `signal(REMOVE_WIDTH_ONE_REDUCTION_AND, &rand_a[0]); + `signal(REMOVE_WIDTH_ONE_REDUCTION_OR, |rand_a[0]); + `signal(REMOVE_WIDTH_ONE_REDUCTION_XOR, ^rand_a[0]); `signal(REMOVE_XOR_WITH_ZERO, 64'd0 ^ rand_a); `signal(REMOVE_XOR_WITH_ONES, -64'd1 ^ rand_a); `signal(REPLACE_COND_DEC, randbit_a ? rand_b - 64'b1 : rand_b); From a83043d7355b97d92659ca10a1106dd26200c4c9 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 5 Oct 2022 10:36:28 +0100 Subject: [PATCH 084/177] DfgPeephole: Rework folding of associative operations Allow constant folding through adjacent nodes of all associative operations, for example '((a & 2) & 3)' or '(3 & (2 & a))' can now be folded into '(a & 2)' and '(2 & a)' respectively. Also improve speed of making associative expression trees right leaning by using rotation of the existing vertices whenever instead of allocation of new nodes. --- src/V3Dfg.h | 3 +- src/V3DfgPeephole.cpp | 319 +++++++++++++++++------------- src/V3DfgPeephole.h | 6 +- test_regress/t/t_dfg_peephole.cpp | 3 + test_regress/t/t_dfg_peephole.v | 26 ++- 5 files changed, 210 insertions(+), 147 deletions(-) diff --git a/src/V3Dfg.h b/src/V3Dfg.h index e0745dad2..e4a6d308f 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -226,7 +226,7 @@ class DfgVertex VL_NOT_FINAL { protected: DfgEdge* m_sinksp = nullptr; // List of sinks of this vertex FileLine* const m_filelinep; // Source location - AstNodeDType* m_dtypep = nullptr; // Data type of the result of this vertex + AstNodeDType* m_dtypep; // Data type of the result of this vertex - mutable for efficiency const VDfgType m_type; // CONSTRUCTOR @@ -297,6 +297,7 @@ public: FileLine* fileline() const { return m_filelinep; } // The data type of the result of the nodes AstNodeDType* dtypep() const { return m_dtypep; } + void dtypep(AstNodeDType* nodep) { m_dtypep = nodep; } // The type of this vertex VDfgType type() const { return m_type; } diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 79c7cd0c6..6a6542a25 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -200,6 +200,122 @@ class V3DfgPeephole final : public DfgVisitor { return false; } + // Rotate the expression tree rooted at 'vtxp' to the right ('vtxp->lhsp()' becomes root, + // producing a right-leaning tree). Warning: only valid for associative operations. + template + void rotateRight(Vertex* vtxp) { + static_assert(std::is_base_of::value, "Must invoke on binary"); + static_assert(std::is_final::value, "Must invoke on final class"); + DfgVertexBinary* const ap = vtxp; + DfgVertexBinary* const bp = vtxp->lhsp()->template as(); + UASSERT_OBJ(!bp->hasMultipleSinks(), vtxp, "Can't rotate a non-tree"); + ap->replaceWith(bp); + ap->lhsp(bp->rhsp()); + bp->rhsp(ap); + // Concatenation dtypes need to be fixed up, other associative nodes preserve types + if VL_CONSTEXPR_CXX17 (std::is_same::value) { + ap->dtypep(dtypeForWidth(ap->lhsp()->width() + ap->rhsp()->width())); + bp->dtypep(dtypeForWidth(bp->lhsp()->width() + bp->rhsp()->width())); + } + } + + // Transformations that apply to all associative binary vertices. + // Returns true if vtxp was replaced. + template + bool associativeBinary(Vertex* vtxp) { + static_assert(std::is_base_of::value, "Must invoke on binary"); + static_assert(std::is_final::value, "Must invoke on final class"); + + DfgVertex* const lhsp = vtxp->lhsp(); + DfgVertex* const rhsp = vtxp->rhsp(); + FileLine* const flp = vtxp->fileline(); + + DfgConst* const lConstp = lhsp->cast(); + DfgConst* const rConstp = rhsp->cast(); + + if (lConstp && rConstp) { + APPLYING(FOLD_ASSOC_BINARY) { + DfgConst* const resultp = makeZero(flp, vtxp->width()); + foldOp(resultp->num(), lConstp->num(), rConstp->num()); + vtxp->replaceWith(resultp); + return true; + } + } + + if (lConstp) { + if (Vertex* const rVtxp = rhsp->cast()) { + if (DfgConst* const rlConstp = rVtxp->lhsp()->template cast()) { + APPLYING(FOLD_ASSOC_BINARY_LHS_OF_RHS) { + // Fold constants + const uint32_t width = std::is_same::value + ? lConstp->width() + rlConstp->width() + : vtxp->width(); + DfgConst* const constp = makeZero(flp, width); + foldOp(constp->num(), lConstp->num(), rlConstp->num()); + + // Replace vertex + if VL_CONSTEXPR_CXX17 (!std::is_same::value) { + rVtxp->lhsp(constp); + vtxp->replaceWith(rVtxp); + } else if (!rVtxp->hasMultipleSinks()) { + rVtxp->lhsp(constp); + rVtxp->dtypep(vtxp->dtypep()); + vtxp->replaceWith(rVtxp); + } else { + Vertex* const resp = new Vertex{m_dfg, flp, vtxp->dtypep()}; + resp->lhsp(constp); + resp->rhsp(rVtxp->rhsp()); + vtxp->replaceWith(resp); + } + return true; + } + } + } + } + + if (rConstp) { + if (Vertex* const lVtxp = lhsp->cast()) { + if (DfgConst* const lrConstp = lVtxp->rhsp()->template cast()) { + APPLYING(FOLD_ASSOC_BINARY_RHS_OF_LHS) { + // Fold constants + const uint32_t width = std::is_same::value + ? lrConstp->width() + rConstp->width() + : vtxp->width(); + DfgConst* const constp = makeZero(flp, width); + foldOp(constp->num(), lrConstp->num(), rConstp->num()); + + // Replace vertex + if VL_CONSTEXPR_CXX17 (!std::is_same::value) { + lVtxp->rhsp(constp); + vtxp->replaceWith(lVtxp); + } else if (!lVtxp->hasMultipleSinks()) { + lVtxp->rhsp(constp); + lVtxp->dtypep(vtxp->dtypep()); + vtxp->replaceWith(lVtxp); + } else { + Vertex* const resp = new Vertex{m_dfg, flp, vtxp->dtypep()}; + resp->lhsp(lVtxp->lhsp()); + resp->rhsp(constp); + vtxp->replaceWith(resp); + } + return true; + } + } + } + } + + // Make associative trees right leaning to reduce pattern variations, and for better CSE + while (vtxp->lhsp()->template is() && !vtxp->lhsp()->hasMultipleSinks()) { + APPLYING(RIGHT_LEANING_ASSOC) { + rotateRight(vtxp); + continue; + } + break; + } + + return false; + } + // Transformations that apply to all commutative binary vertices void commutativeBinary(DfgVertexBinary* vtxp) { DfgVertex* const lhsp = vtxp->source<0>(); @@ -237,22 +353,6 @@ class V3DfgPeephole final : public DfgVisitor { } } - // Transformations that apply to all associative binary vertices - void associativeBinary(DfgVertexBinary* vtxp) { - DfgVertex* const lhsp = vtxp->lhsp(); - - // Make associative trees right leaning (for better CSE opportunities) - if (lhsp->type() == vtxp->type() && !lhsp->hasMultipleSinks()) { - DfgVertexBinary* const lBinp = lhsp->as(); - APPLYING(RIGHT_LEANING_ASSOC) { - vtxp->replaceWith(lBinp); - vtxp->lhsp(lBinp->rhsp()); - lBinp->rhsp(vtxp); - return; - } - } - } - // Bitwise operation with one side Const, and the other side a Concat template bool tryPushBitwiseOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) { @@ -589,10 +689,9 @@ class V3DfgPeephole final : public DfgVisitor { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); - if (foldBinary(vtxp)) return; + if (associativeBinary(vtxp)) return; commutativeBinary(vtxp); - associativeBinary(vtxp); DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); @@ -667,10 +766,9 @@ class V3DfgPeephole final : public DfgVisitor { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); - if (foldBinary(vtxp)) return; + if (associativeBinary(vtxp)) return; commutativeBinary(vtxp); - associativeBinary(vtxp); DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); @@ -773,10 +871,9 @@ class V3DfgPeephole final : public DfgVisitor { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); - if (foldBinary(vtxp)) return; + if (associativeBinary(vtxp)) return; commutativeBinary(vtxp); - associativeBinary(vtxp); DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); @@ -814,10 +911,9 @@ class V3DfgPeephole final : public DfgVisitor { UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); - if (foldBinary(vtxp)) return; + if (associativeBinary(vtxp)) return; commutativeBinary(vtxp); - associativeBinary(vtxp); } void visit(DfgArraySel* vtxp) override { @@ -835,133 +931,70 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgConcat* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width() + vtxp->rhsp()->width(), vtxp, - "Inconsisend Concat"); + if (associativeBinary(vtxp)) return; - if (foldBinary(vtxp)) return; + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width() + vtxp->rhsp()->width(), vtxp, + "Inconsistent Concat"); DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); FileLine* const flp = vtxp->fileline(); - // Make concat trees right leaning (for better CSE opportunities) - if (DfgConcat* const lConcatp = lhsp->cast()) { - if (!lConcatp->hasMultipleSinks()) { - APPLYING(RIGHT_LEANING_CONCAT) { - const uint32_t topWidth = lConcatp->rhsp()->width() + rhsp->width(); - DfgConcat* const topp = new DfgConcat{m_dfg, flp, dtypeForWidth(topWidth)}; - DfgConcat* const botp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; - topp->rhsp(rhsp); - topp->lhsp(lConcatp->rhsp()); - botp->rhsp(topp); - botp->lhsp(lConcatp->lhsp()); - vtxp->replaceWith(botp); - return; - } - } - } - - { - const auto joinConsts - = [this](DfgConst* lConstp, DfgConst* rConstp, FileLine* flp) -> DfgConst* { - DfgConst* const newConstp = makeZero(flp, lConstp->width() + rConstp->width()); - newConstp->num().opSelInto(rConstp->num(), 0, rConstp->width()); - newConstp->num().opSelInto(lConstp->num(), rConstp->width(), lConstp->width()); - return newConstp; - }; - - DfgConst* const lConstp = lhsp->cast(); - DfgConst* const rConstp = rhsp->cast(); - - if (lConstp) { - if (DfgConcat* const rConcatp = rhsp->cast()) { - if (DfgConst* const rlConstp = rConcatp->lhsp()->cast()) { - APPLYING(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS) { - DfgConst* const joinedConstp = joinConsts(lConstp, rlConstp, flp); - DfgConcat* const replacementp - = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; - replacementp->lhsp(joinedConstp); - replacementp->rhsp(rConcatp->rhsp()); + if (lhsp->isZero()) { + DfgConst* const lConstp = lhsp->as(); + if (DfgSel* const rSelp = rhsp->cast()) { + if (DfgConst* const rSelLsbConstp = rSelp->lsbp()->cast()) { + if (vtxp->width() == rSelp->fromp()->width() + && rSelLsbConstp->toU32() == lConstp->width()) { + const uint32_t rSelWidth = rSelp->widthp()->as()->toU32(); + UASSERT_OBJ(lConstp->width() + rSelWidth == vtxp->width(), vtxp, + "Inconsistent"); + APPLYING(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) { + DfgShiftR* const replacementp + = new DfgShiftR{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(rSelp->fromp()); + replacementp->rhsp(makeI32(flp, lConstp->width())); vtxp->replaceWith(replacementp); return; } } } - - if (lConstp->isZero()) { - if (DfgSel* const rSelp = rhsp->cast()) { - if (DfgConst* const rSelLsbConstp = rSelp->lsbp()->cast()) { - if (vtxp->width() == rSelp->fromp()->width() - && rSelLsbConstp->toU32() == lConstp->width()) { - const uint32_t rSelWidth - = rSelp->widthp()->as()->toU32(); - UASSERT_OBJ(lConstp->width() + rSelWidth == vtxp->width(), vtxp, - "Inconsistent"); - APPLYING(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) { - DfgShiftR* const replacementp - = new DfgShiftR{m_dfg, flp, vtxp->dtypep()}; - replacementp->lhsp(rSelp->fromp()); - replacementp->rhsp(makeI32(flp, lConstp->width())); - vtxp->replaceWith(replacementp); - return; - } - } - } - } - } - } - - if (rConstp) { - if (DfgConcat* const lConcatp = lhsp->cast()) { - if (DfgConst* const lrConstp = lConcatp->rhsp()->cast()) { - APPLYING(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS) { - DfgConst* const joinedConstp = joinConsts(lrConstp, rConstp, flp); - DfgConcat* const replacementp - = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; - replacementp->lhsp(lConcatp->lhsp()); - replacementp->rhsp(joinedConstp); - vtxp->replaceWith(replacementp); - return; - } - } - } - - if (rConstp->isZero()) { - if (DfgSel* const lSelp = lhsp->cast()) { - if (DfgConst* const lSelLsbConstp = lSelp->lsbp()->cast()) { - if (vtxp->width() == lSelp->fromp()->width() - && lSelLsbConstp->toU32() == 0) { - const uint32_t lSelWidth - = lSelp->widthp()->as()->toU32(); - UASSERT_OBJ(lSelWidth + rConstp->width() == vtxp->width(), vtxp, - "Inconsistent"); - APPLYING(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) { - DfgShiftL* const replacementp - = new DfgShiftL{m_dfg, flp, vtxp->dtypep()}; - replacementp->lhsp(lSelp->fromp()); - replacementp->rhsp(makeI32(flp, rConstp->width())); - vtxp->replaceWith(replacementp); - return; - } - } - } - } - } } } - { - DfgNot* const lNot = lhsp->cast(); - DfgNot* const rNot = rhsp->cast(); - if (lNot && rNot && !lNot->hasMultipleSinks() && !rNot->hasMultipleSinks()) { - APPLYING(PUSH_CONCAT_THROUGH_NOTS) { - vtxp->lhsp(lNot->srcp()); - vtxp->rhsp(rNot->srcp()); - DfgNot* const replacementp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; - vtxp->replaceWith(replacementp); - replacementp->srcp(vtxp); - return; + if (rhsp->isZero()) { + DfgConst* const rConstp = rhsp->as(); + if (DfgSel* const lSelp = lhsp->cast()) { + if (DfgConst* const lSelLsbConstp = lSelp->lsbp()->cast()) { + if (vtxp->width() == lSelp->fromp()->width() && lSelLsbConstp->toU32() == 0) { + const uint32_t lSelWidth = lSelp->widthp()->as()->toU32(); + UASSERT_OBJ(lSelWidth + rConstp->width() == vtxp->width(), vtxp, + "Inconsistent"); + APPLYING(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) { + DfgShiftL* const replacementp + = new DfgShiftL{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(lSelp->fromp()); + replacementp->rhsp(makeI32(flp, rConstp->width())); + vtxp->replaceWith(replacementp); + return; + } + } + } + } + } + + if (DfgNot* const lNot = lhsp->cast()) { + if (DfgNot* const rNot = rhsp->cast()) { + if (!lNot->hasMultipleSinks() && !rNot->hasMultipleSinks()) { + APPLYING(PUSH_CONCAT_THROUGH_NOTS) { + vtxp->lhsp(lNot->srcp()); + vtxp->rhsp(rNot->srcp()); + DfgNot* const replacementp = new DfgNot{m_dfg, flp, vtxp->dtypep()}; + vtxp->replaceWith(replacementp); + replacementp->srcp(vtxp); + return; + } } } } @@ -1114,11 +1147,21 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgMul* vtxp) override { - if (foldBinary(vtxp)) return; + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + + if (associativeBinary(vtxp)) return; + + commutativeBinary(vtxp); } void visit(DfgMulS* vtxp) override { - if (foldBinary(vtxp)) return; + UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + + if (associativeBinary(vtxp)) return; + + commutativeBinary(vtxp); } void visit(DfgNeq* vtxp) override { diff --git a/src/V3DfgPeephole.h b/src/V3DfgPeephole.h index bec8a5c8f..2f64da014 100644 --- a/src/V3DfgPeephole.h +++ b/src/V3DfgPeephole.h @@ -26,6 +26,9 @@ // Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests). // clang-format off #define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_LHS_OF_RHS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_SEL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_UNARY) \ @@ -73,8 +76,6 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_EXTEND) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_ADJOINING_SELS_ON_RHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_EQ) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_NOT_NEQ) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_OR_OF_CONCAT_LHS_ZERO_AND_CONCAT_ZERO_RHS) \ @@ -87,7 +88,6 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_TAUTOLOGICAL_OR) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_XOR_WITH_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, RIGHT_LEANING_ASSOC) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, RIGHT_LEANING_CONCAT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NEQ_CONDITION) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_COND_WITH_NOT_CONDITION) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, SWAP_CONST_IN_COMMUTATIVE_BINARY) \ diff --git a/test_regress/t/t_dfg_peephole.cpp b/test_regress/t/t_dfg_peephole.cpp index 7a9d9cac4..6a6bf3440 100644 --- a/test_regress/t/t_dfg_peephole.cpp +++ b/test_regress/t/t_dfg_peephole.cpp @@ -29,15 +29,18 @@ int main(int, char**) { uint64_t rand_a = 0x5aef0c8dd70a4497; uint64_t rand_b = 0xf0c0a8dd75ae4497; + uint64_t srand_a = 0x00fa8dcc7ae4957; for (size_t n = 0; n < 200000; ++n) { // Update rngs rngUpdate(rand_a); rngUpdate(rand_b); + rngUpdate(srand_a); // Assign inputs ref.rand_a = opt.rand_a = rand_a; ref.rand_b = opt.rand_b = rand_b; + ref.srand_a = opt.srand_a = srand_a; // Evaluate both models ref.eval(); diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 7f9acc62b..675fd9194 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -8,15 +8,17 @@ module t ( `include "portlist.vh" // Boilerplate generated by t_dfg_peephole.pl - rand_a, rand_b + rand_a, rand_b, srand_a ); `include "portdecl.vh" // Boilerplate generated by t_dfg_peephole.pl input rand_a; input rand_b; - wire [63:0] rand_a; - wire [63:0] rand_b; + input srand_a; + wire logic [63:0] rand_a; + wire logic [63:0] rand_b; + wire logic signed [63:0] srand_a; wire logic randbit_a = rand_a[0]; wire logic [127:0] rand_ba = {rand_b, rand_a}; @@ -85,6 +87,22 @@ module t ( `signal(FOLD_BINARY_Sub, const_a - const_b); `signal(FOLD_BINARY_Xor, const_a ^ const_b); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_And, (const_a & (const_b & rand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_Or, (const_a | (const_b | rand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_Xor, (const_a ^ (const_b ^ rand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_Add, (const_a + (const_b + rand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_Mul, (const_a * (const_b * rand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_MulS, (sconst_a * (sconst_b * srand_a))); + `signal(FOLD_ASSOC_BINARY_LHS_OF_RHS_Concat, {const_a, {const_b, rand_a}}); + + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_And, ((rand_a & const_b) & const_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_Or, ((rand_a | const_b) | const_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_Xor, ((rand_a ^ const_b) ^ const_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_Add, ((rand_a + const_b) + const_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_Mul, ((rand_a * const_b) * const_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_MulS, ((srand_a * sconst_b) * sconst_a)); + `signal(FOLD_ASSOC_BINARY_RHS_OF_LHS_Concat, {{rand_a, const_b}, const_a}); + `signal(FOLD_SEL, const_a[3:1]); `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, rand_a + const_a); @@ -128,8 +146,6 @@ module t ( `signal(PUSH_SEL_THROUGH_REPLICATE, rand_aa[0]); `signal(REPLACE_SEL_FROM_CONST, const_a[2]); `signal(REPLACE_CONCAT_OF_CONSTS, {const_a, const_b}); - `signal(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_RHS, {`DFG({rand_a, const_a}), const_b}); - `signal(REPLACE_NESTED_CONCAT_OF_CONSTS_ON_LHS, {const_a, `DFG({const_b, rand_a})}); `signal(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR, {62'd0, rand_a[63:62]}); `signal(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL, {rand_a[1:0], 62'd0}); `signal(PUSH_CONCAT_THROUGH_NOTS, {~(rand_a+64'd101), ~(rand_b+64'd101)} ); From 5b742571d37dfdea90031fb0c77d10efc7cc0648 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 6 Oct 2022 09:31:53 +0100 Subject: [PATCH 085/177] DFG: run removeVars after CSE This enables removing some more redundant variables. --- src/V3DfgPasses.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index bd192416a..3b08c3121 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -188,8 +188,8 @@ void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { if (v3Global.opt.fDfgPeephole()) { apply(4, "peephole ", [&]() { peephole(dfg, ctx.m_peepholeContext); }); // Without peephole no variables will be redundant, and we just did CSE, so skip these - apply(4, "removeVars ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); }); apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext1); }); + apply(4, "removeVars ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); }); } apply(3, "optimized ", [&]() { removeUnused(dfg); }); if (dumpDfg() >= 8) dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "optimized"); From 97add4d57a496170732e2f4f49fc9cb7ca57cde0 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Thu, 6 Oct 2022 15:38:59 +0200 Subject: [PATCH 086/177] Fix null access on optimized-out fork statements (#3658) `V3SchedTiming` currently assumes that if a fork still exists, it must have statements within it (otherwise it would have been deleted by `V3Timing`). However, in a case like this: ``` module t; reg a; initial fork a = 1; join endmodule ``` the assignment in the fork is optimized out by `V3Dead` after `V3Timing`. This leads to `V3SchedTiming` accessing fork's `stmtsp` pointer, which at this point is null. This patch addresses that issue. Signed-off-by: Krzysztof Bieganski --- src/V3SchedTiming.cpp | 6 ++++-- test_regress/t/t_timing_debug2.out | 2 ++ test_regress/t/t_timing_fork_join.v | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index 5423ec629..6fc2e0656 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -323,8 +323,10 @@ void transformForks(AstNetlist* const netlistp) { iterateChildrenConst(nodep); // Const, so we don't iterate the calls twice // Replace self with the function calls (no co_await, as we don't want the main // process to suspend whenever any of the children do) - nodep->replaceWith(nodep->stmtsp()->unlinkFrBackWithNext()); - VL_DO_DANGLING(nodep->deleteTree(), nodep); + // V3Dead could have removed all statements from the fork, so guard against it + AstNode* const stmtsp = nodep->stmtsp(); + if (stmtsp) nodep->addNextHere(stmtsp->unlinkFrBackWithNext()); + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } void visit(AstBegin* nodep) override { UASSERT_OBJ(m_forkp, nodep, "Begin outside of a fork"); diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out index 0972ba27c..0e3f6d873 100644 --- a/test_regress/t/t_timing_debug2.out +++ b/test_regress/t/t_timing_debug2.out @@ -15,6 +15,8 @@ -V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__5 -V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:13 -V{t#,#}+ Vt_timing_debug2___024root___eval_initial__TOP__1 +-V{t#,#}+ Vt_timing_debug2___024root___eval_initial__TOP__2 +-V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:83 -V{t#,#}+ Vt_timing_debug2___024root___eval_settle -V{t#,#}+ Eval -V{t#,#}+ Vt_timing_debug2___024root___eval diff --git a/test_regress/t/t_timing_fork_join.v b/test_regress/t/t_timing_fork_join.v index fb5b6c60d..12ec14ef6 100644 --- a/test_regress/t/t_timing_fork_join.v +++ b/test_regress/t/t_timing_fork_join.v @@ -77,4 +77,8 @@ module t; $finish; end initial #100 $stop; // timeout + + // Test optimized-out fork statements: + reg a; + initial fork a = 1; join endmodule From 4f0158b5e0d3cc2fbf26308db8a40cb9f2dadbe6 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 6 Oct 2022 11:26:11 +0100 Subject: [PATCH 087/177] Speed up Dfg common sub-expression elimination Added a DfgVertex::user() mechanism for storing data in vertices. Similar in spirit to AstNode user data, but the generation counter is stored in the DfgGraph the vertex is held under. Use this to cache DfgVertex::hash results, and also speed up DfgVertex hashing in general. Use these and additional improvements to speed up CSE. --- src/V3Dfg.cpp | 54 +++++++++++++------------- src/V3Dfg.h | 90 +++++++++++++++++++++++++++++++++++-------- src/V3DfgDfgToAst.cpp | 9 +++-- src/V3DfgPasses.cpp | 36 ++++++++++------- src/V3DfgVertices.h | 9 ++--- 5 files changed, 132 insertions(+), 66 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index e353c5b5c..c5be6ba5f 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -864,9 +864,10 @@ bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const { const auto key = (this < &that) ? EqualsCache::key_type{this, &that} // : EqualsCache::key_type{&that, this}; - const auto pair = cache.emplace(key, true); - bool& result = pair.first->second; - if (pair.second) { + // Note: the recursive invocation can cause a re-hash of the cache which invalidates iterators + uint8_t result = cache[key]; + if (!result) { + result = 2; // Assume equals auto thisPair = this->sourceEdges(); const DfgEdge* const thisSrcEdgesp = thisPair.first; const size_t thisArity = thisPair.second; @@ -879,24 +880,30 @@ bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const { const DfgVertex* const thatSrcVtxp = thatSrcEdgesp[i].m_sourcep; if (thisSrcVtxp == thatSrcVtxp) continue; if (!thisSrcVtxp || !thatSrcVtxp || !thisSrcVtxp->equals(*thatSrcVtxp, cache)) { - result = false; + result = 1; // Mark not equal break; } } + cache[key] = result; } - return result; + return result >> 1; } -V3Hash DfgVertex::hash(HashCache& cache) const { - const auto pair = cache.emplace(this, V3Hash{}); - V3Hash& result = pair.first->second; - if (pair.second) { - result += selfHash(); +V3Hash DfgVertex::hash() { + V3Hash& result = user(); + if (!result.value()) { + V3Hash hash; + hash += selfHash(); // Variables are defined by themselves, so there is no need to hash the sources. This // enables sound hashing of graphs circular only through variables, which we rely on. if (!is()) { - forEachSource([&result, &cache](const DfgVertex& src) { result += src.hash(cache); }); + const auto pair = sourceEdges(); + const DfgEdge* const edgesp = pair.first; + const size_t arity = pair.second; + // Sources must always be connected in well-formed graphs + for (size_t i = 0; i < arity; ++i) hash += edgesp[i].m_sourcep->hash(); } + result = hash; } return result; } @@ -926,30 +933,23 @@ void DfgVertex::replaceWith(DfgVertex* newSorucep) { // Vertex classes //------------------------------------------------------------------------------ -// DfgVarPacked ---------- +// DfgVertexVar ---------- -bool DfgVarPacked::selfEquals(const DfgVertex& that) const { - if (const DfgVarPacked* otherp = that.cast()) { +bool DfgVertexVar::selfEquals(const DfgVertex& that) const { + if (const DfgVertexVar* otherp = that.cast()) { UASSERT_OBJ(varp() != otherp->varp(), this, "There should only be one DfgVarPacked for a given AstVar"); } return false; } -V3Hash DfgVarPacked::selfHash() const { return V3Hasher::uncachedHash(varp()); } - -// DfgVarPacked ---------- - -bool DfgVarArray::selfEquals(const DfgVertex& that) const { - if (const DfgVarArray* otherp = that.cast()) { - UASSERT_OBJ(varp() != otherp->varp(), this, - "There should only be one DfgVarArray for a given AstVar"); - } - return false; +V3Hash DfgVertexVar::selfHash() const { + V3Hash hash; + hash += m_varp->name(); + hash += m_varp->varType(); + return hash; } -V3Hash DfgVarArray::selfHash() const { return V3Hasher::uncachedHash(varp()); } - // DfgConst ---------- bool DfgConst::selfEquals(const DfgVertex& that) const { @@ -959,7 +959,7 @@ bool DfgConst::selfEquals(const DfgVertex& that) const { return false; } -V3Hash DfgConst::selfHash() const { return V3Hasher::uncachedHash(m_constp); } +V3Hash DfgConst::selfHash() const { return m_constp->num().toHash(); } //------------------------------------------------------------------------------ // DfgVisitor diff --git a/src/V3Dfg.h b/src/V3Dfg.h index e4a6d308f..e434c5b12 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -36,7 +36,6 @@ #include "V3Ast.h" #include "V3Error.h" #include "V3Hash.h" -#include "V3Hasher.h" #include "V3List.h" #include "V3Dfg__gen_forward_class_decls.h" // From ./astgen @@ -44,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -94,9 +94,36 @@ inline std::ostream& operator<<(std::ostream& os, const VDfgType& t) { return os class DfgGraph final { friend class DfgVertex; + // TYPES + + // RAII handle for DfgVertex user data + class UserDataInUse final { + DfgGraph* m_graphp; // The referenced graph + + public: + UserDataInUse(DfgGraph* graphp) + : m_graphp{graphp} {} + VL_UNCOPYABLE(UserDataInUse); + UserDataInUse(UserDataInUse&& that) { + UASSERT(that.m_graphp, "Moving from empty"); + m_graphp = vlstd::exchange(that.m_graphp, nullptr); + } + UserDataInUse& operator=(UserDataInUse&& that) { + UASSERT(that.m_graphp, "Moving from empty"); + m_graphp = vlstd::exchange(that.m_graphp, nullptr); + return *this; + } + + ~UserDataInUse() { + if (m_graphp) m_graphp->m_userCurrent = 0; + } + }; + // MEMBERS - size_t m_size = 0; // Number of vertices in the graph V3List m_vertices; // The vertices in the graph + size_t m_size = 0; // Number of vertices in the graph + uint32_t m_userCurrent = 0; // Vertex user data generation number currently in use + uint32_t m_userCnt = 0; // Vertex user data generation counter // Parent of the graph (i.e.: the module containing the logic represented by this graph). AstModule* const m_modulep; const string m_name; // Name of graph (for debugging) @@ -120,6 +147,19 @@ public: // Name of this graph const string& name() const { return m_name; } + // Reset Vertex user data + UserDataInUse userDataInUse() { + UASSERT(!m_userCurrent, "Conflicting use of DfgVertex user data"); + ++m_userCnt; + UASSERT(m_userCnt, "'m_userCnt' overflow"); + m_userCurrent = m_userCnt; + return UserDataInUse{this}; + } + + // Access to vertex list for faster iteration in important contexts + DfgVertex* verticesBegin() const { return m_vertices.begin(); } + DfgVertex* verticesRbegin() const { return m_vertices.rbegin(); } + // Calls given function 'f' for each vertex in the graph. It is safe to manipulate any vertices // in the graph, or to delete/unlink the vertex passed to 'f' during iteration. It is however // not safe to delete/unlink any vertex in the same graph other than the one passed to 'f'. @@ -221,13 +261,18 @@ class DfgVertex VL_NOT_FINAL { friend class DfgEdge; friend class DfgVisitor; + using UserDataStorage = void*; // Storage allocated for user data + // STATE V3ListEnt m_verticesEnt; // V3List handle of this vertex, kept under the DfgGraph protected: DfgEdge* m_sinksp = nullptr; // List of sinks of this vertex FileLine* const m_filelinep; // Source location AstNodeDType* m_dtypep; // Data type of the result of this vertex - mutable for efficiency - const VDfgType m_type; + DfgGraph* m_graphp; // The containing DfgGraph + const VDfgType m_type; // Vertex type tag + uint32_t m_userCnt = 0; // User data generation number + UserDataStorage m_userDataStorage; // User data storage // CONSTRUCTOR DfgVertex(DfgGraph& dfg, VDfgType type, FileLine* flp, AstNodeDType* dtypep); @@ -301,6 +346,23 @@ public: // The type of this vertex VDfgType type() const { return m_type; } + template + T& user() { + static_assert(sizeof(T) <= sizeof(UserDataStorage), + "Size of user data type 'T' is too large for allocated storage"); + static_assert(alignof(T) <= alignof(UserDataStorage), + "Alignment of user data type 'T' is larger than allocated storage"); + T* const storagep = reinterpret_cast(&m_userDataStorage); + const uint32_t userCurrent = m_graphp->m_userCurrent; + UDEBUGONLY(UASSERT_OBJ(userCurrent, this, "DfgVertex user data used without reserving");); + if (m_userCnt != userCurrent) { + m_userCnt = userCurrent; + VL_ATTR_UNUSED T* const resultp = new (storagep) T{}; + UDEBUGONLY(UASSERT_OBJ(resultp == storagep, this, "Something is odd");); + } + return *storagep; + } + // Width of result uint32_t width() const { // Everything supported is packed now, so we can just do this: @@ -309,7 +371,7 @@ public: } // Cache type for 'equals' below - using EqualsCache = std::unordered_map, bool>; + using EqualsCache = std::unordered_map, uint8_t>; // Vertex equality (based on this vertex and all upstream vertices feeding into this vertex). // Returns true, if the vertices can be substituted for each other without changing the @@ -324,19 +386,9 @@ public: return equals(that, cache); } - // Cache type for 'hash' below - using HashCache = std::unordered_map; - // Hash of vertex (depends on this vertex and all upstream vertices feeding into this vertex). - // The 'cache' argument is used to store results to avoid repeat evaluations, but it requires - // that the upstream sources of the vertex do not change between invocations. - V3Hash hash(HashCache& cache) const; - - // Uncached version of 'hash' - V3Hash hash() const { - HashCache cache; // Still cache recursive calls within this invocation - return hash(cache); - } + // Uses user data for caching hashes + V3Hash hash(); // Source edges of this vertex virtual std::pair sourceEdges() = 0; @@ -362,6 +414,10 @@ public: // Relink all sinks to be driven from the given new source void replaceWith(DfgVertex* newSourcep); + // Access to vertex list for faster iteration in important contexts + DfgVertex* verticesNext() const { return m_verticesEnt.nextp(); } + DfgVertex* verticesPrev() const { return m_verticesEnt.prevp(); } + // Calls given function 'f' for each source vertex of this vertex // Unconnected source edges are not iterated. inline void forEachSource(std::function f) const; @@ -488,11 +544,13 @@ public: void DfgGraph::addVertex(DfgVertex& vtx) { ++m_size; vtx.m_verticesEnt.pushBack(m_vertices, &vtx); + vtx.m_graphp = this; } void DfgGraph::removeVertex(DfgVertex& vtx) { --m_size; vtx.m_verticesEnt.unlink(m_vertices, &vtx); + vtx.m_graphp = nullptr; } void DfgGraph::forEachVertex(std::function f) { diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index de08f1060..2cab9eaec 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -126,7 +126,6 @@ class DfgToAstVisitor final : DfgVisitor { // Map from an AstVar, to the canonical AstVar that can be substituted for that AstVar std::unordered_map m_canonVars; V3UniqueNames m_tmpNames{"_VdfgTmp"}; // For generating temporary names - DfgVertex::HashCache m_hashCache; // For caching hashes // METHODS @@ -167,7 +166,7 @@ class DfgToAstVisitor final : DfgVisitor { // Given a DfgVertex, return an AstVar that will hold the value of the given DfgVertex once we // are done with converting this Dfg into Ast form. - AstVar* getResultVar(const DfgVertex* vtxp) { + AstVar* getResultVar(DfgVertex* vtxp) { const auto pair = m_resultVars.emplace(vtxp, nullptr); AstVar*& varp = pair.first->second; if (pair.second) { @@ -187,7 +186,7 @@ class DfgToAstVisitor final : DfgVisitor { } else { // No DfgVarPacked driven fully by this node. Create a temporary. // TODO: should we reuse parts when the AstVar is used as an rvalue? - const string name = m_tmpNames.get(vtxp->hash(m_hashCache).toString()); + const string name = m_tmpNames.get(vtxp->hash().toString()); // Note: It is ok for these temporary variables to be always unsigned. They are // read only by other expressions within the graph and all expressions interpret // their operands based on the expression type, not the operand type. @@ -330,6 +329,10 @@ class DfgToAstVisitor final : DfgVisitor { explicit DfgToAstVisitor(DfgGraph& dfg, V3DfgOptimizationContext& ctx) : m_modp{dfg.modulep()} , m_ctx{ctx} { + + // Used by DfgVertex::hash + const auto userDataInUse = dfg.userDataInUse(); + // We can eliminate some variables completely std::vector redundantVarps; diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 3b08c3121..571bdb489 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -77,28 +77,36 @@ V3DfgOptimizationContext::~V3DfgOptimizationContext() { // Common subexpression elimination void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) { - DfgVertex::HashCache hashCache; DfgVertex::EqualsCache equalsCache; - std::unordered_multimap verticesWithEqualHashes; + std::unordered_map> verticesWithEqualHashes; + verticesWithEqualHashes.reserve(dfg.size()); + + // Used by DfgVertex::hash + const auto userDataInUse = dfg.userDataInUse(); // In reverse, as the graph is sometimes in reverse topological order already - dfg.forEachVertexInReverse([&](DfgVertex& vtx) { + for (DfgVertex *vtxp = dfg.verticesRbegin(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesPrev(); + if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp); + // Don't merge constants - if (vtx.is()) return; + if (vtxp->is()) continue; + // For everything else... - const V3Hash hash = vtx.hash(hashCache); - auto pair = verticesWithEqualHashes.equal_range(hash); - for (auto it = pair.first, end = pair.second; it != end; ++it) { - DfgVertex* const candidatep = it->second; - if (candidatep->equals(vtx, equalsCache)) { + std::vector& vec = verticesWithEqualHashes[vtxp->hash()]; + bool replaced = false; + for (DfgVertex* const candidatep : vec) { + if (candidatep->equals(*vtxp, equalsCache)) { ++ctx.m_eliminated; - vtx.replaceWith(candidatep); - vtx.unlinkDelete(dfg); - return; + vtxp->replaceWith(candidatep); + vtxp->unlinkDelete(dfg); + replaced = true; + break; } } - verticesWithEqualHashes.emplace(hash, &vtx); - }); + if (replaced) continue; + vec.push_back(vtxp); + } } void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index f504bcbd3..b9cd763fe 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -43,6 +43,9 @@ class DfgVertexVar VL_NOT_FINAL : public DfgVertexVariadic { bool m_hasModRefs = false; // This AstVar is referenced outside the DFG, but in the module bool m_hasExtRefs = false; // This AstVar is referenced from outside the module + bool selfEquals(const DfgVertex& that) const final; + V3Hash selfHash() const final; + public: DfgVertexVar(DfgGraph& dfg, VDfgType type, AstVar* varp, uint32_t initialCapacity) : DfgVertexVariadic{dfg, type, varp->fileline(), dtypeFor(varp), initialCapacity} @@ -115,9 +118,6 @@ class DfgVarArray final : public DfgVertexVar { std::vector m_driverData; // Additional data associate with each driver - bool selfEquals(const DfgVertex& that) const override; - V3Hash selfHash() const override; - public: DfgVarArray(DfgGraph& dfg, AstVar* varp) : DfgVertexVar{dfg, dfgType(), varp, 4u} { @@ -177,9 +177,6 @@ class DfgVarPacked final : public DfgVertexVar { std::vector m_driverData; // Additional data associate with each driver - bool selfEquals(const DfgVertex& that) const override; - V3Hash selfHash() const override; - public: DfgVarPacked(DfgGraph& dfg, AstVar* varp) : DfgVertexVar{dfg, dfgType(), varp, 1u} {} From 6fa14bf029ce3e651d3d1c4bcdb8ee6a4efa57f3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 6 Oct 2022 12:02:46 +0100 Subject: [PATCH 088/177] Speed up DfgPeephole in various ways --- src/V3Dfg.h | 4 +- src/V3DfgPeephole.cpp | 143 +++++++++++++++++++++++++----------------- 2 files changed, 87 insertions(+), 60 deletions(-) diff --git a/src/V3Dfg.h b/src/V3Dfg.h index e434c5b12..d56ac6f85 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -365,8 +365,8 @@ public: // Width of result uint32_t width() const { - // Everything supported is packed now, so we can just do this: - UASSERT_OBJ(VN_IS(dtypep(), BasicDType), this, "'width()' called on unpacked value"); + // This is a hot enough function that this is an expensive check, so in debug build only. + UDEBUGONLY(UASSERT_OBJ(VN_IS(dtypep(), BasicDType), this, "non-packed has no 'width()'");); return dtypep()->width(); } diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 6a6542a25..05210b43f 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -356,14 +356,15 @@ class V3DfgPeephole final : public DfgVisitor { // Bitwise operation with one side Const, and the other side a Concat template bool tryPushBitwiseOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) { - UASSERT_OBJ(constp->width() == concatp->width(), vtxp, "Mismatched widths"); + UASSERT_OBJ(constp->dtypep() == concatp->dtypep(), vtxp, "Mismatched widths"); FileLine* const flp = vtxp->fileline(); // If at least one of the sides of the Concat constant, or width 1 (i.e.: can be // further simplified), then push the Vertex past the Concat if (concatp->lhsp()->is() || concatp->rhsp()->is() // - || concatp->lhsp()->width() == 1 || concatp->rhsp()->width() == 1) { + || concatp->lhsp()->dtypep() == m_bitDType + || concatp->rhsp()->dtypep() == m_bitDType) { APPLYING(PUSH_BITWISE_OP_THROUGH_CONCAT) { const uint32_t width = concatp->width(); AstNodeDType* const lDtypep = concatp->lhsp()->dtypep(); @@ -401,7 +402,7 @@ class V3DfgPeephole final : public DfgVisitor { template bool tryPushCompareOpThroughConcat(Vertex* vtxp, DfgConst* constp, DfgConcat* concatp) { - UASSERT_OBJ(constp->width() == concatp->width(), vtxp, "Mismatched widths"); + UASSERT_OBJ(constp->dtypep() == concatp->dtypep(), vtxp, "Mismatched widths"); FileLine* const flp = vtxp->fileline(); @@ -483,7 +484,7 @@ class V3DfgPeephole final : public DfgVisitor { FileLine* const flp = vtxp->fileline(); // Reduction of 1-bit value - if (srcp->width() == 1) { + if (srcp->dtypep() == m_bitDType) { APPLYING(REMOVE_WIDTH_ONE_REDUCTION) { vtxp->replaceWith(srcp); return; @@ -589,19 +590,19 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgLogNot* vtxp) override { - UASSERT_OBJ(vtxp->width() == 1, vtxp, "Incorrect width"); + UASSERT_OBJ(vtxp->dtypep() == m_bitDType, vtxp, "Incorrect width"); if (foldUnary(vtxp)) return; } void visit(DfgNegate* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->srcp()->width(), vtxp, "Mismatched width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->srcp()->dtypep(), vtxp, "Mismatched width"); if (foldUnary(vtxp)) return; } void visit(DfgNot* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->srcp()->width(), vtxp, "Mismatched width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->srcp()->dtypep(), vtxp, "Mismatched width"); if (foldUnary(vtxp)) return; @@ -634,7 +635,7 @@ class V3DfgPeephole final : public DfgVisitor { // Not of Not if (DfgNot* const notp = vtxp->srcp()->cast()) { - UASSERT_OBJ(vtxp->width() == notp->srcp()->width(), vtxp, "Width mismatch"); + UASSERT_OBJ(vtxp->dtypep() == notp->srcp()->dtypep(), vtxp, "Width mismatch"); APPLYING(REMOVE_NOT_NOT) { vtxp->replaceWith(notp->srcp()); return; @@ -686,8 +687,8 @@ class V3DfgPeephole final : public DfgVisitor { //========================================================================= void visit(DfgAnd* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); - UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width"); if (associativeBinary(vtxp)) return; @@ -763,8 +764,8 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgOr* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); - UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width"); if (associativeBinary(vtxp)) return; @@ -807,7 +808,7 @@ class V3DfgPeephole final : public DfgVisitor { if (DfgConcat* const lhsConcatp = lhsp->cast()) { if (DfgConcat* const rhsConcatp = rhsp->cast()) { - if (lhsConcatp->lhsp()->width() == rhsConcatp->lhsp()->width()) { + if (lhsConcatp->lhsp()->dtypep() == rhsConcatp->lhsp()->dtypep()) { if (lhsConcatp->lhsp()->isZero() && rhsConcatp->rhsp()->isZero()) { APPLYING(REPLACE_OR_OF_CONCAT_ZERO_LHS_AND_CONCAT_RHS_ZERO) { DfgConcat* const replacementp @@ -868,8 +869,8 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgXor* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); - UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width"); if (associativeBinary(vtxp)) return; @@ -908,8 +909,8 @@ class V3DfgPeephole final : public DfgVisitor { //========================================================================= void visit(DfgAdd* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); - UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width"); if (associativeBinary(vtxp)) return; @@ -931,11 +932,11 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgConcat* vtxp) override { - if (associativeBinary(vtxp)) return; - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width() + vtxp->rhsp()->width(), vtxp, "Inconsistent Concat"); + if (associativeBinary(vtxp)) return; + DfgVertex* const lhsp = vtxp->lhsp(); DfgVertex* const rhsp = vtxp->rhsp(); @@ -945,7 +946,7 @@ class V3DfgPeephole final : public DfgVisitor { DfgConst* const lConstp = lhsp->as(); if (DfgSel* const rSelp = rhsp->cast()) { if (DfgConst* const rSelLsbConstp = rSelp->lsbp()->cast()) { - if (vtxp->width() == rSelp->fromp()->width() + if (vtxp->dtypep() == rSelp->fromp()->dtypep() && rSelLsbConstp->toU32() == lConstp->width()) { const uint32_t rSelWidth = rSelp->widthp()->as()->toU32(); UASSERT_OBJ(lConstp->width() + rSelWidth == vtxp->width(), vtxp, @@ -967,7 +968,8 @@ class V3DfgPeephole final : public DfgVisitor { DfgConst* const rConstp = rhsp->as(); if (DfgSel* const lSelp = lhsp->cast()) { if (DfgConst* const lSelLsbConstp = lSelp->lsbp()->cast()) { - if (vtxp->width() == lSelp->fromp()->width() && lSelLsbConstp->toU32() == 0) { + if (vtxp->dtypep() == lSelp->fromp()->dtypep() + && lSelLsbConstp->toU32() == 0) { const uint32_t lSelWidth = lSelp->widthp()->as()->toU32(); UASSERT_OBJ(lSelWidth + rConstp->width() == vtxp->width(), vtxp, "Inconsistent"); @@ -1147,8 +1149,8 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgMul* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); - UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width"); if (associativeBinary(vtxp)) return; @@ -1156,8 +1158,8 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgMulS* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); - UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width"); if (associativeBinary(vtxp)) return; @@ -1185,7 +1187,7 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgReplicate* vtxp) override { - if (vtxp->width() == vtxp->srcp()->width()) { + if (vtxp->dtypep() == vtxp->srcp()->dtypep()) { APPLYING(REMOVE_REPLICATE_ONCE) { vtxp->replaceWith(vtxp->srcp()); return; @@ -1196,7 +1198,7 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgShiftL* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched width"); if (foldBinary(vtxp)) return; @@ -1204,7 +1206,7 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgShiftR* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched width"); if (foldBinary(vtxp)) return; @@ -1212,7 +1214,7 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgShiftRS* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched width"); if (foldBinary(vtxp)) return; @@ -1220,8 +1222,8 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgSub* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->lhsp()->width(), vtxp, "Mismatched LHS width"); - UASSERT_OBJ(vtxp->width() == vtxp->rhsp()->width(), vtxp, "Mismatched RHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->lhsp()->dtypep(), vtxp, "Mismatched LHS width"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->rhsp()->dtypep(), vtxp, "Mismatched RHS width"); if (foldBinary(vtxp)) return; @@ -1235,7 +1237,7 @@ class V3DfgPeephole final : public DfgVisitor { return; } } - if (vtxp->width() == 1 && rConstp->toU32() == 1) { + if (vtxp->dtypep() == m_bitDType && rConstp->toU32() == 1) { APPLYING(REPLACE_SUB_WITH_NOT) { DfgNot* const replacementp = new DfgNot{m_dfg, vtxp->fileline(), m_bitDType}; replacementp->srcp(lhsp); @@ -1354,7 +1356,7 @@ class V3DfgPeephole final : public DfgVisitor { if (DfgNot* const notp = fromp->cast()) { // Replace "Sel from Not" with "Not of Sel" if (!notp->hasMultipleSinks()) { - UASSERT_OBJ(notp->srcp()->width() == notp->width(), notp, "Mismatched widths"); + UASSERT_OBJ(notp->srcp()->dtypep() == notp->dtypep(), notp, "Mismatched widths"); APPLYING(PUSH_SEL_THROUGH_NOT) { // Make Sel select from source of Not vtxp->fromp(notp->srcp()); @@ -1432,15 +1434,15 @@ class V3DfgPeephole final : public DfgVisitor { } void visit(DfgCond* vtxp) override { - UASSERT_OBJ(vtxp->width() == vtxp->thenp()->width(), vtxp, "Width mismatch"); - UASSERT_OBJ(vtxp->width() == vtxp->elsep()->width(), vtxp, "Width mismatch"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->thenp()->dtypep(), vtxp, "Width mismatch"); + UASSERT_OBJ(vtxp->dtypep() == vtxp->elsep()->dtypep(), vtxp, "Width mismatch"); DfgVertex* const condp = vtxp->condp(); DfgVertex* const thenp = vtxp->thenp(); DfgVertex* const elsep = vtxp->elsep(); FileLine* const flp = vtxp->fileline(); - if (condp->width() != 1) return; + if (condp->dtypep() != m_bitDType) return; if (condp->isOnes()) { APPLYING(REMOVE_COND_WITH_TRUE_CONDITION) { @@ -1462,7 +1464,6 @@ class V3DfgPeephole final : public DfgVisitor { vtxp->condp(condNotp->srcp()); vtxp->thenp(elsep); vtxp->elsep(thenp); - visit(vtxp); return; } } @@ -1477,7 +1478,6 @@ class V3DfgPeephole final : public DfgVisitor { vtxp->condp(newCondp); vtxp->thenp(elsep); vtxp->elsep(thenp); - visit(vtxp); return; } } @@ -1543,7 +1543,7 @@ class V3DfgPeephole final : public DfgVisitor { } } - if (vtxp->width() == 1) { + if (vtxp->dtypep() == m_bitDType) { AstNodeDType* const dtypep = vtxp->dtypep(); if (thenp->isZero()) { // a ? 0 : b becomes ~a & b APPLYING(REPLACE_COND_WITH_THEN_BRANCH_ZERO) { @@ -1606,44 +1606,71 @@ class V3DfgPeephole final : public DfgVisitor { #undef APPLYING // Process one vertex. Return true if graph changed - bool processVertex(DfgVertex& vtx) { + void processVertex(DfgVertex* vtxp) { // Keep DfgVertexVar vertices in this pass. We will remove them later if they become // redundant. We want to keep the original variables for non-var vertices that drive // multiple sinks (otherwise we would need to introduce a temporary, but it is better for // debugging to keep the original variable name, if one is available), so we can't remove // redundant variables here. - const bool keep = vtx.is(); + const bool keep = vtxp->is(); // If it has no sinks (unused), we can remove it - if (!keep && !vtx.hasSinks()) { - vtx.unlinkDelete(m_dfg); - return true; + if (!keep && !vtxp->hasSinks()) { + vtxp->unlinkDelete(m_dfg); + m_changed = true; + return; } // Transform node - m_changed = false; - iterate(&vtx); + iterate(vtxp); // If it became unused, we can remove it - if (!keep && !vtx.hasSinks()) { - UASSERT_OBJ(m_changed, &vtx, "'m_changed' must be set if node became unused"); - vtx.unlinkDelete(m_dfg); - return true; + if (!keep && !vtxp->hasSinks()) { + UASSERT_OBJ(m_changed, vtxp, "'m_changed' must be set if node became unused"); + vtxp->unlinkDelete(m_dfg); } - - // Return the changed status - return m_changed; } V3DfgPeephole(DfgGraph& dfg, V3DfgPeepholeContext& ctx) : m_dfg{dfg} - , m_ctx{ctx} {} + , m_ctx{ctx} { + + while (true) { + // Do one pass over the graph in the forward direction. + m_changed = false; + for (DfgVertex *vtxp = m_dfg.verticesBegin(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp); + // Special case DfgConst as it's common and the is nothing we can do about them. + // No need to set 'm_changed' when deleting it as it influences nothing else. + if (vtxp->is()) { + if (!vtxp->hasSinks()) vtxp->unlinkDelete(m_dfg); + continue; + } + processVertex(vtxp); + } + if (!m_changed) break; + + // Do another pass in the opposite direction. Alternating directions reduces + // the pathological complexity with left/right leaning trees. + m_changed = false; + for (DfgVertex *vtxp = m_dfg.verticesRbegin(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesPrev(); + if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp); + // Special case DfgConst as it's common and the is nothing we can do about them. + // No need to set 'm_changed' when deleting it as it influences nothing else. + if (vtxp->is()) { + if (!vtxp->hasSinks()) vtxp->unlinkDelete(m_dfg); + continue; + } + processVertex(vtxp); + } + if (!m_changed) break; + } + } public: - static void apply(DfgGraph& dfg, V3DfgPeepholeContext& ctx) { - V3DfgPeephole visitor{dfg, ctx}; - dfg.runToFixedPoint([&](DfgVertex& vtx) { return visitor.processVertex(vtx); }); - } + static void apply(DfgGraph& dfg, V3DfgPeepholeContext& ctx) { V3DfgPeephole{dfg, ctx}; } }; void V3DfgPasses::peephole(DfgGraph& dfg, V3DfgPeepholeContext& ctx) { From 0570cb8d9fa35864e3a5c34b8233c70bdb814b4d Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 6 Oct 2022 19:19:34 +0100 Subject: [PATCH 089/177] DFG: Correctly set dtype when converting DfgCountOnes to Ast --- src/V3DfgDfgToAst.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index 2cab9eaec..be47278a9 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -54,6 +54,16 @@ Node* makeNode(const Vertex* vtxp, Ops... ops) { //====================================================================== // Vertices needing special conversion +template <> +AstCountOnes* makeNode( // + const DfgCountOnes* vtxp, AstNodeMath* op1) { + AstCountOnes* const nodep = new AstCountOnes{vtxp->fileline(), op1}; + // Set dtype same as V3Width + const int selwidth = V3Number::log2b(nodep->lhsp()->width()) + 1; + nodep->dtypeSetLogicSized(selwidth, VSigning::UNSIGNED); + return nodep; +} + template <> AstExtend* makeNode( // const DfgExtend* vtxp, AstNodeMath* op1) { From 29a080dd9b1e75ff1f997304e108949f5b733f4e Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 6 Oct 2022 18:34:18 +0100 Subject: [PATCH 090/177] DFG: Special case representation of AstSel AstSel is a ternary node, but the 'widthp' is always constant and is hence redundant, and 'lsbp' is very often constant. As AstSel is fairly common, we special case as a DfgSel for the constant 'lsbp', and as 'DfgMux` for the non-constant 'lsbp'. --- src/V3Dfg.cpp | 86 ++++----- src/V3DfgAstToDfg.cpp | 38 +++- src/V3DfgDfgToAst.cpp | 16 ++ src/V3DfgPeephole.cpp | 412 +++++++++++++++++++----------------------- src/V3DfgVertices.h | 41 +++++ 5 files changed, 309 insertions(+), 284 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index c5be6ba5f..0a12fc8c7 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -539,12 +539,6 @@ void DfgGraph::runToFixedPoint(std::function f) { static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; } -static bool isSimpleSel(const DfgSel* vtxp) { - const DfgConst* const lp = vtxp->lsbp()->cast(); - const DfgConst* const wp = vtxp->widthp()->cast(); - return lp && wp && !lp->hasMultipleSinks() && !wp->hasMultipleSinks(); -} - // Dump one DfgVertex in Graphviz format static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { @@ -598,11 +592,6 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { } if (const DfgConst* const constVtxp = vtx.cast()) { - const bool feedsSimpleSel = !constVtxp->findSink([](const DfgVertex& v) { // - return !v.is() || !isSimpleSel(v.as()); - }); - if (feedsSimpleSel) return; // Will draw it in the sel node as it is very common - const V3Number& num = constVtxp->constp()->num(); os << toDotId(vtx); @@ -620,20 +609,18 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { } if (const DfgSel* const selVtxp = vtx.cast()) { - if (isSimpleSel(selVtxp)) { - const uint32_t lsb = selVtxp->lsbp()->as()->toU32(); - const uint32_t msb = lsb + selVtxp->width() - 1; - os << toDotId(vtx); - os << " [label=\"SEL\n_[" << msb << ":" << lsb << "]\nW" << vtx.width() << " / F" - << vtx.fanout() << '"'; - if (vtx.hasMultipleSinks()) { - os << ", shape=doublecircle"; - } else { - os << ", shape=circle"; - } - os << "]" << endl; - return; + const uint32_t lsb = selVtxp->lsb(); + const uint32_t msb = lsb + selVtxp->width() - 1; + os << toDotId(vtx); + os << " [label=\"SEL\n_[" << msb << ":" << lsb << "]\nW" << vtx.width() << " / F" + << vtx.fanout() << '"'; + if (vtx.hasMultipleSinks()) { + os << ", shape=doublecircle"; + } else { + os << ", shape=circle"; } + os << "]" << endl; + return; } os << toDotId(vtx); @@ -656,16 +643,6 @@ static void dumpDotEdge(std::ostream& os, const DfgEdge& edge, const string& hea // Dump one DfgVertex and all of its source DfgEdges in Graphviz format static void dumpDotVertexAndSourceEdges(std::ostream& os, const DfgVertex& vtx) { dumpDotVertex(os, vtx); - - if (const DfgSel* const selVtxp = vtx.cast()) { - if (isSimpleSel(selVtxp)) { - UASSERT_OBJ(selVtxp->sourceEdge<0>()->sourcep() == selVtxp->fromp(), selVtxp, - "Operand ordering changed"); - dumpDotEdge(os, *selVtxp->sourceEdge<0>(), ""); - return; - } - } - vtx.forEachSourceEdge([&](const DfgEdge& edge, size_t idx) { // if (edge.sourcep()) { string headLabel; @@ -852,14 +829,14 @@ DfgVertex::~DfgVertex() { if (VN_IS(m_dtypep, UnpackArrayDType)) VL_DO_DANGLING(delete m_dtypep, m_dtypep); } -bool DfgVertex::selfEquals(const DfgVertex& that) const { - return this->m_type == that.m_type && this->dtypep() == that.dtypep(); -} +bool DfgVertex::selfEquals(const DfgVertex& that) const { return true; } -V3Hash DfgVertex::selfHash() const { return V3Hash{m_type} + width(); } +V3Hash DfgVertex::selfHash() const { return V3Hash{}; } bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const { if (this == &that) return true; + if (this->type() != that.type()) return false; + if (this->dtypep() != that.dtypep()) return false; if (!this->selfEquals(that)) return false; const auto key = (this < &that) ? EqualsCache::key_type{this, &that} // @@ -893,6 +870,8 @@ V3Hash DfgVertex::hash() { V3Hash& result = user(); if (!result.value()) { V3Hash hash; + hash += m_type; + hash += width(); hash += selfHash(); // Variables are defined by themselves, so there is no need to hash the sources. This // enables sound hashing of graphs circular only through variables, which we rely on. @@ -933,13 +912,25 @@ void DfgVertex::replaceWith(DfgVertex* newSorucep) { // Vertex classes //------------------------------------------------------------------------------ +// DfgConst ---------- + +bool DfgConst::selfEquals(const DfgVertex& that) const { + return constp()->sameTree(that.as()->constp()); +} + +V3Hash DfgConst::selfHash() const { return m_constp->num().toHash(); } + +// DfgSel ---------- + +bool DfgSel::selfEquals(const DfgVertex& that) const { return lsb() == that.as()->lsb(); } + +V3Hash DfgSel::selfHash() const { return V3Hash{lsb()}; } + // DfgVertexVar ---------- bool DfgVertexVar::selfEquals(const DfgVertex& that) const { - if (const DfgVertexVar* otherp = that.cast()) { - UASSERT_OBJ(varp() != otherp->varp(), this, - "There should only be one DfgVarPacked for a given AstVar"); - } + UASSERT_OBJ(varp() != that.as()->varp(), this, + "There should only be one DfgVarPacked for a given AstVar"); return false; } @@ -950,17 +941,6 @@ V3Hash DfgVertexVar::selfHash() const { return hash; } -// DfgConst ---------- - -bool DfgConst::selfEquals(const DfgVertex& that) const { - if (const DfgConst* otherp = that.cast()) { - return constp()->sameTree(otherp->constp()); - } - return false; -} - -V3Hash DfgConst::selfHash() const { return m_constp->num().toHash(); } - //------------------------------------------------------------------------------ // DfgVisitor //------------------------------------------------------------------------------ diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index 32344bdff..98d114a67 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -187,15 +187,12 @@ class AstToDfgVisitor final : public VNVisitor { if (AstConcat* const concatp = VN_CAST(nodep, Concat)) { AstNode* const lhsp = concatp->lhsp(); AstNode* const rhsp = concatp->rhsp(); - const uint32_t lWidth = lhsp->width(); - const uint32_t rWidth = rhsp->width(); { FileLine* const lFlp = lhsp->fileline(); DfgSel* const lVtxp = new DfgSel{*m_dfgp, lFlp, DfgVertex::dtypeFor(lhsp)}; lVtxp->fromp(vtxp); - lVtxp->lsbp(new DfgConst{*m_dfgp, new AstConst{lFlp, rWidth}}); - lVtxp->widthp(new DfgConst{*m_dfgp, new AstConst{lFlp, lWidth}}); + lVtxp->lsb(rhsp->width()); if (!convertAssignment(flp, lhsp, lVtxp)) return false; } @@ -203,8 +200,7 @@ class AstToDfgVisitor final : public VNVisitor { FileLine* const rFlp = rhsp->fileline(); DfgSel* const rVtxp = new DfgSel{*m_dfgp, rFlp, DfgVertex::dtypeFor(rhsp)}; rVtxp->fromp(vtxp); - rVtxp->lsbp(new DfgConst{*m_dfgp, new AstConst{rFlp, 0u}}); - rVtxp->widthp(new DfgConst{*m_dfgp, new AstConst{rFlp, rWidth}}); + rVtxp->lsb(0); return convertAssignment(flp, rhsp, rVtxp); } } @@ -467,6 +463,36 @@ class AstToDfgVisitor final : public VNVisitor { nodep->user1p(vtxp); } + void visit(AstSel* nodep) override { + UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex"); + if (unhandled(nodep)) return; + if (!VN_IS(nodep->widthp(), Const)) { // This should never be taken, but paranoia + m_foundUnhandled = true; + ++m_ctx.m_nonRepNode; + return; + } + iterate(nodep->fromp()); + if (m_foundUnhandled) return; + + FileLine* const flp = nodep->fileline(); + DfgVertex* vtxp = nullptr; + if (AstConst* const constp = VN_CAST(nodep->lsbp(), Const)) { + DfgSel* const selp = new DfgSel{*m_dfgp, flp, DfgVertex::dtypeFor(nodep)}; + selp->fromp(nodep->fromp()->user1u().to()); + selp->lsb(constp->toUInt()); + vtxp = selp; + } else { + iterate(nodep->lsbp()); + if (m_foundUnhandled) return; + DfgMux* const muxp = new DfgMux{*m_dfgp, flp, DfgVertex::dtypeFor(nodep)}; + muxp->fromp(nodep->fromp()->user1u().to()); + muxp->lsbp(nodep->lsbp()->user1u().to()); + vtxp = muxp; + } + m_uncommittedVertices.push_back(vtxp); + nodep->user1p(vtxp); + } + // The rest of the 'visit' methods are generated by 'astgen' #include "V3Dfg__gen_ast_to_dfg.h" diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index be47278a9..b41660bbf 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -332,6 +332,22 @@ class DfgToAstVisitor final : DfgVisitor { m_resultp = vtxp->constp()->cloneTree(false); } + void visit(DfgSel* vtxp) override { + FileLine* const flp = vtxp->fileline(); + AstNodeMath* const fromp = convertSource(vtxp->fromp()); + AstConst* const lsbp = new AstConst{flp, vtxp->lsb()}; + AstConst* const widthp = new AstConst{flp, vtxp->width()}; + m_resultp = new AstSel{flp, fromp, lsbp, widthp}; + } + + void visit(DfgMux* vtxp) override { + FileLine* const flp = vtxp->fileline(); + AstNodeMath* const fromp = convertSource(vtxp->fromp()); + AstNodeMath* const lsbp = convertSource(vtxp->lsbp()); + AstConst* const widthp = new AstConst{flp, vtxp->width()}; + m_resultp = new AstSel{flp, fromp, lsbp, widthp}; + } + // The rest of the 'visit' methods are generated by 'astgen' #include "V3Dfg__gen_dfg_to_ast.h" diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 05210b43f..40a45dbf7 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -682,6 +682,170 @@ class V3DfgPeephole final : public DfgVisitor { void visit(DfgRedXor* vtxp) override { optimizeReduction(vtxp); } + void visit(DfgSel* vtxp) override { + DfgVertex* const fromp = vtxp->fromp(); + + FileLine* const flp = vtxp->fileline(); + + const uint32_t lsb = vtxp->lsb(); + const uint32_t width = vtxp->width(); + const uint32_t msb = lsb + width - 1; + + if (DfgConst* const constp = fromp->cast()) { + APPLYING(FOLD_SEL) { + DfgConst* const replacementp = makeZero(flp, width); + replacementp->num().opSel(constp->num(), msb, lsb); + vtxp->replaceWith(replacementp); + return; + } + } + + // Full width select, replace with the source. + if (fromp->width() == width) { + UASSERT_OBJ(lsb == 0, fromp, "OOPS"); + APPLYING(REMOVE_FULL_WIDTH_SEL) { + vtxp->replaceWith(fromp); + return; + } + } + + // Sel from Concat + if (DfgConcat* const concatp = fromp->cast()) { + DfgVertex* const lhsp = concatp->lhsp(); + DfgVertex* const rhsp = concatp->rhsp(); + + if (msb < rhsp->width()) { + // If the select is entirely from rhs, then replace with sel from rhs + APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) { // + vtxp->fromp(rhsp); + } + } else if (lsb >= rhsp->width()) { + // If the select is entirely from the lhs, then replace with sel from lhs + APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) { + vtxp->fromp(lhsp); + vtxp->lsb(lsb - rhsp->width()); + } + } else if (lsb == 0 || msb == concatp->width() - 1 // + || lhsp->is() || rhsp->is() // + || !concatp->hasMultipleSinks()) { + // If the select straddles both sides, but at least one of the sides is wholly + // selected, or at least one of the sides is a Const, or this concat has no other + // use, then push the Sel past the Concat + APPLYING(PUSH_SEL_THROUGH_CONCAT) { + const uint32_t rSelWidth = rhsp->width() - lsb; + const uint32_t lSelWidth = width - rSelWidth; + + // The new Lhs vertex + DfgSel* const newLhsp = new DfgSel{m_dfg, flp, dtypeForWidth(lSelWidth)}; + newLhsp->fromp(lhsp); + newLhsp->lsb(0); + + // The new Rhs vertex + DfgSel* const newRhsp = new DfgSel{m_dfg, flp, dtypeForWidth(rSelWidth)}; + newRhsp->fromp(rhsp); + newRhsp->lsb(lsb); + + // The replacement Concat vertex + DfgConcat* const newConcat + = new DfgConcat{m_dfg, concatp->fileline(), vtxp->dtypep()}; + newConcat->lhsp(newLhsp); + newConcat->rhsp(newRhsp); + + // Replace this vertex + vtxp->replaceWith(newConcat); + return; + } + } + } + + if (DfgReplicate* const repp = fromp->cast()) { + // If the Sel is wholly into the source of the Replicate, push the Sel through the + // Replicate and apply it directly to the source of the Replicate. + const uint32_t srcWidth = repp->srcp()->width(); + if (width <= srcWidth) { + const uint32_t newLsb = lsb % srcWidth; + if (newLsb + width <= srcWidth) { + APPLYING(PUSH_SEL_THROUGH_REPLICATE) { + vtxp->fromp(repp->srcp()); + vtxp->lsb(newLsb); + } + } + } + } + + // Sel from Not + if (DfgNot* const notp = fromp->cast()) { + // Replace "Sel from Not" with "Not of Sel" + if (!notp->hasMultipleSinks()) { + UASSERT_OBJ(notp->srcp()->dtypep() == notp->dtypep(), notp, "Mismatched widths"); + APPLYING(PUSH_SEL_THROUGH_NOT) { + // Make Sel select from source of Not + vtxp->fromp(notp->srcp()); + // Add Not after Sel + DfgNot* const replacementp + = new DfgNot{m_dfg, notp->fileline(), vtxp->dtypep()}; + vtxp->replaceWith(replacementp); + replacementp->srcp(vtxp); + } + } + } + + // Sel from Sel + if (DfgSel* const selp = fromp->cast()) { + APPLYING(REPLACE_SEL_FROM_SEL) { + // Make this Sel select from the source of the source Sel + vtxp->fromp(selp->fromp()); + // Adjust LSB + vtxp->lsb(lsb + selp->lsb()); + } + } + + // Sel from Cond + if (DfgCond* const condp = fromp->cast()) { + // If at least one of the branches are a constant, push the select past the cond + if (condp->thenp()->is() || condp->elsep()->is()) { + APPLYING(PUSH_SEL_THROUGH_COND) { + // The new 'then' vertex + DfgSel* const newThenp = new DfgSel{m_dfg, flp, vtxp->dtypep()}; + newThenp->fromp(condp->thenp()); + newThenp->lsb(lsb); + + // The new 'else' vertex + DfgSel* const newElsep = new DfgSel{m_dfg, flp, vtxp->dtypep()}; + newElsep->fromp(condp->elsep()); + newElsep->lsb(lsb); + + // The replacement Cond vertex + DfgCond* const newCondp + = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()}; + newCondp->condp(condp->condp()); + newCondp->thenp(newThenp); + newCondp->elsep(newElsep); + + // Replace this vertex + vtxp->replaceWith(newCondp); + return; + } + } + } + + // Sel from ShiftL + if (DfgShiftL* const shiftLp = fromp->cast()) { + // If selecting bottom bits of left shift, push the Sel before the shift + if (lsb == 0) { + UASSERT_OBJ(shiftLp->lhsp()->width() >= width, vtxp, "input of shift narrow"); + APPLYING(PUSH_SEL_THROUGH_SHIFTL) { + vtxp->fromp(shiftLp->lhsp()); + DfgShiftL* const newShiftLp + = new DfgShiftL{m_dfg, shiftLp->fileline(), vtxp->dtypep()}; + vtxp->replaceWith(newShiftLp); + newShiftLp->lhsp(vtxp); + newShiftLp->rhsp(shiftLp->rhsp()); + } + } + } + } + //========================================================================= // DfgVertexBinary - bitwise //========================================================================= @@ -945,20 +1109,14 @@ class V3DfgPeephole final : public DfgVisitor { if (lhsp->isZero()) { DfgConst* const lConstp = lhsp->as(); if (DfgSel* const rSelp = rhsp->cast()) { - if (DfgConst* const rSelLsbConstp = rSelp->lsbp()->cast()) { - if (vtxp->dtypep() == rSelp->fromp()->dtypep() - && rSelLsbConstp->toU32() == lConstp->width()) { - const uint32_t rSelWidth = rSelp->widthp()->as()->toU32(); - UASSERT_OBJ(lConstp->width() + rSelWidth == vtxp->width(), vtxp, - "Inconsistent"); - APPLYING(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) { - DfgShiftR* const replacementp - = new DfgShiftR{m_dfg, flp, vtxp->dtypep()}; - replacementp->lhsp(rSelp->fromp()); - replacementp->rhsp(makeI32(flp, lConstp->width())); - vtxp->replaceWith(replacementp); - return; - } + if (vtxp->dtypep() == rSelp->fromp()->dtypep() + && rSelp->lsb() == lConstp->width()) { + APPLYING(REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) { + DfgShiftR* const replacementp = new DfgShiftR{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(rSelp->fromp()); + replacementp->rhsp(makeI32(flp, lConstp->width())); + vtxp->replaceWith(replacementp); + return; } } } @@ -967,20 +1125,13 @@ class V3DfgPeephole final : public DfgVisitor { if (rhsp->isZero()) { DfgConst* const rConstp = rhsp->as(); if (DfgSel* const lSelp = lhsp->cast()) { - if (DfgConst* const lSelLsbConstp = lSelp->lsbp()->cast()) { - if (vtxp->dtypep() == lSelp->fromp()->dtypep() - && lSelLsbConstp->toU32() == 0) { - const uint32_t lSelWidth = lSelp->widthp()->as()->toU32(); - UASSERT_OBJ(lSelWidth + rConstp->width() == vtxp->width(), vtxp, - "Inconsistent"); - APPLYING(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) { - DfgShiftL* const replacementp - = new DfgShiftL{m_dfg, flp, vtxp->dtypep()}; - replacementp->lhsp(lSelp->fromp()); - replacementp->rhsp(makeI32(flp, rConstp->width())); - vtxp->replaceWith(replacementp); - return; - } + if (vtxp->dtypep() == lSelp->fromp()->dtypep() && lSelp->lsb() == 0) { + APPLYING(REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) { + DfgShiftL* const replacementp = new DfgShiftL{m_dfg, flp, vtxp->dtypep()}; + replacementp->lhsp(lSelp->fromp()); + replacementp->rhsp(makeI32(flp, rConstp->width())); + vtxp->replaceWith(replacementp); + return; } } } @@ -1003,22 +1154,14 @@ class V3DfgPeephole final : public DfgVisitor { { const auto joinSels = [this](DfgSel* lSelp, DfgSel* rSelp, FileLine* flp) -> DfgSel* { - DfgConst* const lLsbp = lSelp->lsbp()->cast(); - DfgConst* const lWidthp = lSelp->widthp()->cast(); - DfgConst* const rLsbp = rSelp->lsbp()->cast(); - DfgConst* const rWidthp = rSelp->widthp()->cast(); - if (lLsbp && lWidthp && rLsbp && rWidthp) { - if (lSelp->fromp()->equals(*rSelp->fromp())) { - if (lLsbp->toU32() == rLsbp->toU32() + rWidthp->toU32()) { - // Two consecutive Sels, make a single Sel. - const uint32_t width = lWidthp->toU32() + rWidthp->toU32(); - AstNodeDType* const dtypep = dtypeForWidth(width); - DfgSel* const joinedSelp = new DfgSel{m_dfg, flp, dtypep}; - joinedSelp->fromp(rSelp->fromp()); - joinedSelp->lsbp(rSelp->lsbp()); - joinedSelp->widthp(makeI32(flp, width)); - return joinedSelp; - } + if (lSelp->fromp()->equals(*rSelp->fromp())) { + if (lSelp->lsb() == rSelp->lsb() + rSelp->width()) { + // Two consecutive Sels, make a single Sel. + const uint32_t width = lSelp->width() + rSelp->width(); + DfgSel* const joinedSelp = new DfgSel{m_dfg, flp, dtypeForWidth(width)}; + joinedSelp->fromp(rSelp->fromp()); + joinedSelp->lsb(rSelp->lsb()); + return joinedSelp; } } return nullptr; @@ -1252,187 +1395,6 @@ class V3DfgPeephole final : public DfgVisitor { // DfgVertexTernary //========================================================================= - void visit(DfgSel* vtxp) override { - DfgVertex* const fromp = vtxp->fromp(); - DfgConst* const lsbp = vtxp->lsbp()->cast(); - DfgConst* const widthp = vtxp->widthp()->cast(); - if (!lsbp || !widthp) return; - - FileLine* const flp = vtxp->fileline(); - - UASSERT_OBJ(lsbp->toI32() >= 0, vtxp, "Negative LSB in Sel"); - - const uint32_t lsb = lsbp->toU32(); - const uint32_t width = widthp->toU32(); - const uint32_t msb = lsb + width - 1; - - UASSERT_OBJ(width == vtxp->width(), vtxp, "Incorrect Sel width"); - - if (DfgConst* const constp = fromp->cast()) { - APPLYING(FOLD_SEL) { - DfgConst* const replacementp = makeZero(flp, width); - replacementp->num().opSel(constp->num(), msb, lsb); - vtxp->replaceWith(replacementp); - return; - } - } - - // Full width select, replace with the source. - if (fromp->width() == width) { - UASSERT_OBJ(lsb == 0, fromp, "OOPS"); - APPLYING(REMOVE_FULL_WIDTH_SEL) { - vtxp->replaceWith(fromp); - return; - } - } - - // Sel from Concat - if (DfgConcat* const concatp = fromp->cast()) { - DfgVertex* const lhsp = concatp->lhsp(); - DfgVertex* const rhsp = concatp->rhsp(); - - if (msb < rhsp->width()) { - // If the select is entirely from rhs, then replace with sel from rhs - APPLYING(REMOVE_SEL_FROM_RHS_OF_CONCAT) { // - vtxp->fromp(rhsp); - } - } else if (lsb >= rhsp->width()) { - // If the select is entirely from the lhs, then replace with sel from lhs - APPLYING(REMOVE_SEL_FROM_LHS_OF_CONCAT) { - vtxp->fromp(lhsp); - vtxp->lsbp(makeI32(flp, lsb - rhsp->width())); - } - } else if (lsb == 0 || msb == concatp->width() - 1 // - || lhsp->is() || rhsp->is() // - || !concatp->hasMultipleSinks()) { - // If the select straddles both sides, but at least one of the sides is wholly - // selected, or at least one of the sides is a Const, or this concat has no other - // use, then push the Sel past the Concat - APPLYING(PUSH_SEL_THROUGH_CONCAT) { - const uint32_t rSelWidth = rhsp->width() - lsb; - const uint32_t lSelWidth = width - rSelWidth; - - // The new Lhs vertex - DfgSel* const newLhsp = new DfgSel{m_dfg, flp, dtypeForWidth(lSelWidth)}; - newLhsp->fromp(lhsp); - newLhsp->lsbp(makeI32(lsbp->fileline(), 0)); - newLhsp->widthp(makeI32(widthp->fileline(), lSelWidth)); - - // The new Rhs vertex - DfgSel* const newRhsp = new DfgSel{m_dfg, flp, dtypeForWidth(rSelWidth)}; - newRhsp->fromp(rhsp); - newRhsp->lsbp(makeI32(lsbp->fileline(), lsb)); - newRhsp->widthp(makeI32(widthp->fileline(), rSelWidth)); - - // The replacement Concat vertex - DfgConcat* const newConcat - = new DfgConcat{m_dfg, concatp->fileline(), vtxp->dtypep()}; - newConcat->lhsp(newLhsp); - newConcat->rhsp(newRhsp); - - // Replace this vertex - vtxp->replaceWith(newConcat); - return; - } - } - } - - if (DfgReplicate* const repp = fromp->cast()) { - // If the Sel is wholly into the source of the Replicate, push the Sel through the - // Replicate and apply it directly to the source of the Replicate. - const uint32_t srcWidth = repp->srcp()->width(); - if (width <= srcWidth) { - const uint32_t newLsb = lsb % srcWidth; - if (newLsb + width <= srcWidth) { - APPLYING(PUSH_SEL_THROUGH_REPLICATE) { - vtxp->fromp(repp->srcp()); - vtxp->lsbp(makeI32(flp, newLsb)); - } - } - } - } - - // Sel from Not - if (DfgNot* const notp = fromp->cast()) { - // Replace "Sel from Not" with "Not of Sel" - if (!notp->hasMultipleSinks()) { - UASSERT_OBJ(notp->srcp()->dtypep() == notp->dtypep(), notp, "Mismatched widths"); - APPLYING(PUSH_SEL_THROUGH_NOT) { - // Make Sel select from source of Not - vtxp->fromp(notp->srcp()); - // Add Not after Sel - DfgNot* const replacementp - = new DfgNot{m_dfg, notp->fileline(), vtxp->dtypep()}; - vtxp->replaceWith(replacementp); - replacementp->srcp(vtxp); - } - } - } - - // Sel from Sel - if (DfgSel* const selp = fromp->cast()) { - UASSERT_OBJ(widthp->toU32() <= selp->width(), vtxp, "Out of bound Sel"); - if (DfgConst* const sourceLsbp = selp->lsbp()->cast()) { - UASSERT_OBJ(sourceLsbp->toI32() >= 0, selp, "negative"); - UASSERT_OBJ(selp->widthp()->as()->toU32() >= widthp->toU32(), selp, - "negative"); - APPLYING(REPLACE_SEL_FROM_SEL) { - // Make this Sel select from the source of the source Sel - vtxp->fromp(selp->fromp()); - // Adjust LSB - vtxp->lsbp(makeI32(flp, lsb + sourceLsbp->toU32())); - } - } - } - - // Sel from Cond - if (DfgCond* const condp = fromp->cast()) { - // If at least one of the branches are a constant, push the select past the cond - if (condp->thenp()->is() || condp->elsep()->is()) { - APPLYING(PUSH_SEL_THROUGH_COND) { - // The new 'then' vertex - DfgSel* const newThenp = new DfgSel{m_dfg, flp, vtxp->dtypep()}; - newThenp->fromp(condp->thenp()); - newThenp->lsbp(makeI32(lsbp->fileline(), lsb)); - newThenp->widthp(makeI32(widthp->fileline(), width)); - - // The new 'else' vertex - DfgSel* const newElsep = new DfgSel{m_dfg, flp, vtxp->dtypep()}; - newElsep->fromp(condp->elsep()); - newElsep->lsbp(makeI32(lsbp->fileline(), lsb)); - newElsep->widthp(makeI32(widthp->fileline(), width)); - - // The replacement Cond vertex - DfgCond* const newCondp - = new DfgCond{m_dfg, condp->fileline(), vtxp->dtypep()}; - newCondp->condp(condp->condp()); - newCondp->thenp(newThenp); - newCondp->elsep(newElsep); - - // Replace this vertex - vtxp->replaceWith(newCondp); - return; - } - } - } - - // Sel from ShiftL - if (DfgShiftL* const shiftLp = fromp->cast()) { - // If selecting bottom bits of left shift, push the Sel before the shift - if (lsb == 0) { - UASSERT_OBJ(shiftLp->lhsp()->width() >= width, vtxp, "input of shift narrow"); - APPLYING(PUSH_SEL_THROUGH_SHIFTL) { - vtxp->fromp(shiftLp->lhsp()); - DfgShiftL* const newShiftLp - = new DfgShiftL{m_dfg, shiftLp->fileline(), vtxp->dtypep()}; - vtxp->replaceWith(newShiftLp); - newShiftLp->lhsp(vtxp); - newShiftLp->rhsp(shiftLp->rhsp()); - } - } - } - } - void visit(DfgCond* vtxp) override { UASSERT_OBJ(vtxp->dtypep() == vtxp->thenp()->dtypep(), vtxp, "Width mismatch"); UASSERT_OBJ(vtxp->dtypep() == vtxp->elsep()->dtypep(), vtxp, "Width mismatch"); diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index b9cd763fe..5dccc1c27 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -109,6 +109,47 @@ public: } // LCOV_EXCL_STOP }; +// === DfgVertexBinary === +class DfgMux final : public DfgVertexBinary { + // AstSel is ternary, but the 'widthp' is always constant and is hence redundant, and + // 'lsbp' is very often constant. As AstSel is fairly common, we special case as a DfgSel for + // the constant 'lsbp', and as 'DfgMux` for the non-constant 'lsbp'. +public: + DfgMux(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep) + : DfgVertexBinary{dfg, dfgType(), flp, dtypep} {} + ASTGEN_MEMBERS_DfgMux; + + DfgVertex* fromp() const { return source<0>(); } + void fromp(DfgVertex* vtxp) { relinkSource<0>(vtxp); } + DfgVertex* lsbp() const { return source<1>(); } + void lsbp(DfgVertex* vtxp) { relinkSource<1>(vtxp); } + + const string srcName(size_t idx) const override { return idx ? "lsbp" : "fromp"; } +}; + +// === DfgVertexUnary === +class DfgSel final : public DfgVertexUnary { + // AstSel is ternary, but the 'widthp' is always constant and is hence redundant, and + // 'lsbp' is very often constant. As AstSel is fairly common, we special case as a DfgSel for + // the constant 'lsbp', and as 'DfgMux` for the non-constant 'lsbp'. + uint32_t m_lsb = 0; // The LSB index + + bool selfEquals(const DfgVertex& that) const override; + V3Hash selfHash() const override; + +public: + DfgSel(DfgGraph& dfg, FileLine* flp, AstNodeDType* dtypep) + : DfgVertexUnary{dfg, dfgType(), flp, dtypep} {} + ASTGEN_MEMBERS_DfgSel; + + DfgVertex* fromp() const { return source<0>(); } + void fromp(DfgVertex* vtxp) { relinkSource<0>(vtxp); } + uint32_t lsb() const { return m_lsb; } + void lsb(uint32_t value) { m_lsb = value; } + + const string srcName(size_t) const override { return "fromp"; } +}; + // === DfgVertexVar === class DfgVarArray final : public DfgVertexVar { friend class DfgVertex; From a972230b3aee32fe4f13a22c22e8172b5f13cc92 Mon Sep 17 00:00:00 2001 From: Iztok Jeras Date: Sat, 8 Oct 2022 08:50:15 +0200 Subject: [PATCH 091/177] Commentary: example_binary does not uses SystemC code (#3662) --- docs/guide/example_binary.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/example_binary.rst b/docs/guide/example_binary.rst index 974471c7a..0cb1514be 100644 --- a/docs/guide/example_binary.rst +++ b/docs/guide/example_binary.rst @@ -6,7 +6,7 @@ Example Create-Binary Execution =============================== -We'll compile this SystemC example into a Verilated simulation binary. For +We'll compile this SystemVerilog example into a Verilated simulation binary. For an example that discusses the next level of detail see :ref:`Example C++ Execution`. From 439d30a9533ecf65894815e8acc5aa3d28496c4d Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 7 Oct 2022 14:58:06 +0100 Subject: [PATCH 092/177] Minor cleanup in V3Number --- src/V3Number.cpp | 6 ++---- src/V3Number.h | 28 +++++++++++++++++----------- src/V3ParseImp.h | 4 ++-- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 8861cde38..4d3653f76 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -124,10 +124,8 @@ V3Number::V3Number(AstNode* nodep, const AstNodeDType* nodedtypep) { } } -void V3Number::V3NumberCreate(AstNode* nodep, const char* sourcep, FileLine* fl) { - init(nodep, 0); +void V3Number::create(const char* sourcep) { m_data.setLogic(); - m_fileline = fl; const char* value_startp = sourcep; for (const char* cp = sourcep; *cp; cp++) { if (*cp == '\'') { @@ -369,7 +367,7 @@ void V3Number::V3NumberCreate(AstNode* nodep, const char* sourcep, FileLine* fl) // m_value[0]); } -void V3Number::setNames(AstNode* nodep) { +void V3Number::nodep(AstNode* nodep) { m_nodep = nodep; if (!nodep) return; m_fileline = nodep->fileline(); diff --git a/src/V3Number.h b/src/V3Number.h index 82bfeacdf..7e4ec09c1 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -338,8 +338,8 @@ class V3Number final { // MEMBERS V3NumberData m_data; - AstNode* m_nodep = nullptr; // Parent node - FileLine* m_fileline = nullptr; + AstNode* m_nodep = nullptr; // Parent node - for error reporting only + FileLine* m_fileline = nullptr; // Source location - if no parent node is reasonable // METHODS V3Number& setSingleBits(char value); @@ -350,7 +350,7 @@ class V3Number final { void opCleanThis(bool warnOnTruncation = false); public: - void nodep(AstNode* nodep) { setNames(nodep); } + void nodep(AstNode* nodep); FileLine* fileline() const { return m_fileline; } V3Number& setZero(); V3Number& setQuad(uint64_t value); @@ -473,11 +473,8 @@ public: opCleanThis(); } // Create from a verilog 32'hxxxx number. - V3Number(AstNode* nodep, const char* sourcep) { V3NumberCreate(nodep, sourcep, nullptr); } - class FileLined {}; // Fileline based errors, for parsing only, otherwise pass nodep - V3Number(FileLined, FileLine* fl, const char* sourcep) { - V3NumberCreate(nullptr, sourcep, fl); - } + V3Number(AstNode* nodep, const char* sourcep) { create(nodep, sourcep); } + V3Number(FileLine* flp, const char* sourcep) { create(flp, sourcep); } class VerilogStringLiteral {}; // For creator type-overload selection V3Number(VerilogStringLiteral, AstNode* nodep, const string& str); class String {}; @@ -518,9 +515,19 @@ public: ~V3Number() {} private: - void V3NumberCreate(AstNode* nodep, const char* sourcep, FileLine* fl); + void create(AstNode* nodep, const char* sourcep) { + init(nodep, 0); + m_fileline = nullptr; + create(sourcep); + } + void create(FileLine* flp, const char* sourcep) { + init(nullptr, 0); + m_fileline = flp; + create(sourcep); + } + void create(const char* sourcep); void init(AstNode* nodep, int swidth = -1, bool sized = true) { - setNames(nodep); + this->nodep(nodep); if (swidth >= 0) { if (swidth == 0) { swidth = 1; @@ -535,7 +542,6 @@ private: m_data.m_sized = false; } } - void setNames(AstNode* nodep); static string displayPad(size_t fmtsize, char pad, bool left, const string& in); string displayed(FileLine* fl, const string& vformat) const; string displayed(const string& vformat) const { return displayed(m_fileline, vformat); } diff --git a/src/V3ParseImp.h b/src/V3ParseImp.h index 54c4ab080..9e3303923 100644 --- a/src/V3ParseImp.h +++ b/src/V3ParseImp.h @@ -232,8 +232,8 @@ public: m_stringps.push_back(strp); return strp; } - V3Number* newNumber(FileLine* fl, const char* text) { - V3Number* nump = new V3Number(V3Number::FileLined(), fl, text); + V3Number* newNumber(FileLine* flp, const char* text) { + V3Number* nump = new V3Number(flp, text); m_numberps.push_back(nump); return nump; } From 90447d54d17935ed6a26a89215b8967b6873318b Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 7 Oct 2022 15:44:14 +0100 Subject: [PATCH 093/177] Make DfgConst hold V3Number directly Remove intermediary AstConst. No functional change intended. --- src/V3Dfg.cpp | 8 ++++---- src/V3DfgAstToDfg.cpp | 2 +- src/V3DfgDfgToAst.cpp | 2 +- src/V3DfgPeephole.cpp | 14 ++++---------- src/V3DfgVertices.h | 17 +++++++++-------- src/V3Number.h | 6 ++++++ 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 0a12fc8c7..9aac9209d 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -592,7 +592,7 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) { } if (const DfgConst* const constVtxp = vtx.cast()) { - const V3Number& num = constVtxp->constp()->num(); + const V3Number& num = constVtxp->num(); os << toDotId(vtx); os << " [label=\""; @@ -915,10 +915,10 @@ void DfgVertex::replaceWith(DfgVertex* newSorucep) { // DfgConst ---------- bool DfgConst::selfEquals(const DfgVertex& that) const { - return constp()->sameTree(that.as()->constp()); + return num().isCaseEq(that.as()->num()); } -V3Hash DfgConst::selfHash() const { return m_constp->num().toHash(); } +V3Hash DfgConst::selfHash() const { return num().toHash(); } // DfgSel ---------- @@ -930,7 +930,7 @@ V3Hash DfgSel::selfHash() const { return V3Hash{lsb()}; } bool DfgVertexVar::selfEquals(const DfgVertex& that) const { UASSERT_OBJ(varp() != that.as()->varp(), this, - "There should only be one DfgVarPacked for a given AstVar"); + "There should only be one DfgVertexVar for a given AstVar"); return false; } diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index 98d114a67..93dc09186 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -458,7 +458,7 @@ class AstToDfgVisitor final : public VNVisitor { void visit(AstConst* nodep) override { UASSERT_OBJ(!nodep->user1p(), nodep, "Already has Dfg vertex"); if (unhandled(nodep)) return; - DfgVertex* const vtxp = new DfgConst{*m_dfgp, nodep->cloneTree(false)}; + DfgVertex* const vtxp = new DfgConst{*m_dfgp, nodep->fileline(), nodep->num()}; m_uncommittedVertices.push_back(vtxp); nodep->user1p(vtxp); } diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index b41660bbf..e30a31ae8 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -329,7 +329,7 @@ class DfgToAstVisitor final : DfgVisitor { } void visit(DfgConst* vtxp) override { // - m_resultp = vtxp->constp()->cloneTree(false); + m_resultp = new AstConst{vtxp->fileline(), vtxp->num()}; } void visit(DfgSel* vtxp) override { diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 40a45dbf7..254bcde4b 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -154,17 +154,11 @@ class V3DfgPeephole final : public DfgVisitor { // Shorthand static AstNodeDType* dtypeForWidth(uint32_t width) { return DfgVertex::dtypeForWidth(width); } - // Create a new DfgConst vertex with the given width and value - DfgConst* makeConst(FileLine* flp, uint32_t width, uint32_t value) { - const int widthInt = static_cast(width); - return new DfgConst{m_dfg, new AstConst{flp, AstConst::WidthedValue{}, widthInt, value}}; - } + // Create a 32-bit DfgConst vertex + DfgConst* makeI32(FileLine* flp, uint32_t val) { return new DfgConst{m_dfg, flp, 32, val}; } - // Create a new 32-bit DfgConst vertex - DfgConst* makeI32(FileLine* flp, uint32_t value) { return makeConst(flp, 32, value); } - - // Create a new DfgConst vertex with the given width and value zero - DfgConst* makeZero(FileLine* flp, uint32_t width) { return makeConst(flp, width, 0); } + // Create a DfgConst vertex with the given width and value zero + DfgConst* makeZero(FileLine* flp, uint32_t width) { return new DfgConst{m_dfg, flp, width}; } // Constant fold unary vertex, return true if folded template diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index 5dccc1c27..c67a7e6fd 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -79,21 +79,22 @@ class DfgConst final : public DfgVertex { friend class DfgVertex; friend class DfgVisitor; - AstConst* const m_constp; // The AstConst associated with this vertex (owned by this vertex) + V3Number m_num; // Constant value bool selfEquals(const DfgVertex& that) const override; V3Hash selfHash() const override; public: - DfgConst(DfgGraph& dfg, AstConst* constp) - : DfgVertex{dfg, dfgType(), constp->fileline(), dtypeFor(constp)} - , m_constp{constp} {} + DfgConst(DfgGraph& dfg, FileLine* flp, const V3Number& num) + : DfgVertex{dfg, dfgType(), flp, dtypeForWidth(num.width())} + , m_num{num} {} + DfgConst(DfgGraph& dfg, FileLine* flp, uint32_t width, uint32_t value = 0) + : DfgVertex{dfg, dfgType(), flp, dtypeForWidth(width)} + , m_num{flp, static_cast(width), value} {} ASTGEN_MEMBERS_DfgConst; - ~DfgConst() override { VL_DO_DANGLING(m_constp->deleteTree(), m_constp); } - - AstConst* constp() const { return m_constp; } - V3Number& num() const { return m_constp->num(); } + V3Number& num() { return m_num; } + const V3Number& num() const { return m_num; } uint32_t toU32() const { return num().toUInt(); } int32_t toI32() const { return num().toSInt(); } diff --git a/src/V3Number.h b/src/V3Number.h index 7e4ec09c1..a419e9a0e 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -472,6 +472,12 @@ public: m_data.num()[0].m_value = value; opCleanThis(); } + V3Number(FileLine* flp, int width, uint32_t value) { + init(nullptr, width, true); + m_fileline = flp; + m_data.num()[0].m_value = value; + opCleanThis(); + } // Create from a verilog 32'hxxxx number. V3Number(AstNode* nodep, const char* sourcep) { create(nodep, sourcep); } V3Number(FileLine* flp, const char* sourcep) { create(flp, sourcep); } From 461f3c10041fe1f99a2f6880fe5d51a0ba29dee3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 7 Oct 2022 16:13:54 +0100 Subject: [PATCH 094/177] DFG: Remove topological sort Cyclic components are now extracted separately, so there is no functional reason to have to do a topological sort (previously we used it to detect cyclic graphs). Removing it to gain some speed. --- src/V3Dfg.cpp | 71 ------------------------------------------ src/V3Dfg.h | 6 ---- src/V3DfgOptimizer.cpp | 3 -- 3 files changed, 80 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 9aac9209d..195bbbfaa 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -46,77 +46,6 @@ void DfgGraph::addGraph(DfgGraph& other) { }); } -bool DfgGraph::sortTopologically(bool reverse) { - // Vertices in reverse topological order - std::vector order; - - // Markings for algorithm - enum class Mark : uint8_t { Scheduled, OnPath, Finished }; - std::unordered_map marks; - - // Stack of nodes in depth first search. The second element of the pair is true if the vertex - // is on the current DFS path, and false if it's only scheduled for visitation. - std::vector> stack; - - // Schedule vertex for visitation - const auto scheudle = [&](DfgVertex& vtx) { - // Nothing to do if already finished - if (marks.emplace(&vtx, Mark::Scheduled).first->second == Mark::Finished) return; - // Otherwise scheule for visitation - stack.emplace_back(&vtx, false); - }; - - // For each vertex (direct loop, so we can return early) - for (DfgVertex* vtxp = m_vertices.begin(); vtxp; vtxp = vtxp->m_verticesEnt.nextp()) { - // Initiate DFS from this vertex - scheudle(*vtxp); - while (!stack.empty()) { - // Pick up stack top - const auto pair = stack.back(); - stack.pop_back(); - DfgVertex* const currp = pair.first; - const bool onPath = pair.second; - Mark& mark = marks.at(currp); - - if (onPath) { // Popped node on path - // Mark it as done - UASSERT_OBJ(mark == Mark::OnPath, currp, "DFS got lost"); - mark = Mark::Finished; - // Add to order - order.push_back(currp); - } else { // Otherwise node was scheduled for visitation, so visit it - // If already finished, then nothing to do - if (mark == Mark::Finished) continue; - // If already on path, then not a DAG - if (mark == Mark::OnPath) return false; - // Push to path and mark as such - mark = Mark::OnPath; - stack.emplace_back(currp, true); - // Schedule children - currp->forEachSink(scheudle); - } - } - } - - // Move given vertex to end of vertex list - const auto reinsert = [this](DfgVertex& vtx) { - // Remove from current location - removeVertex(vtx); - // 'addVertex' appends to the end of the vertex list, so can do this in one loop - addVertex(vtx); - }; - - // Remember 'order' is in reverse topological order - if (!reverse) { - for (DfgVertex* vtxp : vlstd::reverse_view(order)) reinsert(*vtxp); - } else { - for (DfgVertex* vtxp : order) reinsert(*vtxp); - } - - // Done - return true; -} - std::vector> DfgGraph::splitIntoComponents(std::string label) { size_t componentNumber = 0; std::unordered_map vertex2component; diff --git a/src/V3Dfg.h b/src/V3Dfg.h index d56ac6f85..822b192d1 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -179,12 +179,6 @@ public: // Add contents of other graph to this graph. Leaves other graph empty. void addGraph(DfgGraph& other); - // Topologically sort the list of vertices in this graph (such that 'forEachVertex' will - // iterate in topological order), or reverse topologically if the passed boolean argument is - // true. Returns true on success (the graph is acyclic and a topological order exists), false - // if the graph is cyclic. If the graph is cyclic, the vertex ordering is not modified. - bool sortTopologically(bool reverse = false); - // Split this graph into individual components (unique sub-graphs with no edges between them). // Leaves 'this' graph empty. std::vector> splitIntoComponents(std::string label); diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index a7cbde164..c2449d678 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -290,9 +290,6 @@ void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) { // For each acyclic component for (auto& component : acyclicComponents) { if (dumpDfg() >= 7) component->dumpDotFilePrefixed(ctx.prefix() + "source"); - // Reverse topologically sort the component - const bool acyclic = component->sortTopologically(/* reverse: */ true); - UASSERT_OBJ(acyclic, nodep, "Supposedly acyclic graph is cyclic"); // Optimize the component V3DfgPasses::optimize(*component, ctx); // Add back under the main DFG (we will convert everything back in one go) From c033a0d7c8517d7cbeb77a14549e8792882e7360 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 7 Oct 2022 16:13:01 +0100 Subject: [PATCH 095/177] Optimize DfgGraph vertex storage Vertices representing variables (DfgVertexVar) and constants (DfgConst) are very common (40-50% of all vertices created in some large designs), and we also need to, or can treat them specially in algorithms. Keep these as separate lists in DfgGraph for direct access to them. This improve verilation speed. --- src/V3Dfg.cpp | 156 ++++++++++++++++++++++-------------------- src/V3Dfg.h | 96 ++++++++++++++++---------- src/V3DfgDfgToAst.cpp | 46 +++++++------ src/V3DfgPasses.cpp | 120 +++++++++++++++++++++++--------- src/V3DfgPasses.h | 2 + src/V3DfgPeephole.cpp | 42 ++---------- src/V3DfgPeephole.h | 1 - src/V3DfgVertices.h | 15 +++- 8 files changed, 270 insertions(+), 208 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 195bbbfaa..4fd2f7a93 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -212,28 +212,35 @@ class ExtractCyclicComponents final { // Methods for merging void visitMergeSCCs(const DfgVertex& vtx, size_t targetComponent) { - // We stop at variable boundaries, which is where we will split the graphs - if (vtx.is()) return; - // Mark visited/move on if already visited if (!m_merged.insert(&vtx).second) return; // Assign vertex to the target component m_state.at(&vtx).component = targetComponent; - // Visit all neighbours - vtx.forEachSource([=](const DfgVertex& other) { visitMergeSCCs(other, targetComponent); }); - vtx.forEachSink([=](const DfgVertex& other) { visitMergeSCCs(other, targetComponent); }); + // Visit all neighbours. We stop at variable boundaries, + // which is where we will split the graphs + vtx.forEachSource([=](const DfgVertex& other) { + if (other.is()) return; + visitMergeSCCs(other, targetComponent); + }); + vtx.forEachSink([=](const DfgVertex& other) { + if (other.is()) return; + visitMergeSCCs(other, targetComponent); + }); } void mergeSCCs() { // Ensure that component boundaries are always at variables, by merging SCCs m_merged.reserve(m_dfg.size()); - m_dfg.forEachVertex([this](DfgVertex& vtx) { - // Start DFS from each vertex that is in a non-trivial SCC, and merge everything that - // is reachable from it into this component. - if (const size_t target = m_state.at(&vtx).component) visitMergeSCCs(vtx, target); - }); + // Merging stops at variable boundaries, so we don't need to iterate variables. Constants + // are reachable from their sinks, or ar unused, so we don't need to iterate them either. + for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + // Start DFS from each vertex that is in a non-trivial SCC, and merge everything + // that is reachable from it into this component. + if (const size_t target = m_state.at(vtxp).component) visitMergeSCCs(*vtxp, target); + } } //========================================================================== @@ -301,7 +308,7 @@ class ExtractCyclicComponents final { } // Fix edges that cross components - void fixEdges(DfgVertex& vtx) { + void fixEdges(DfgVertexVar& vtx) { if (DfgVarPacked* const vvtxp = vtx.cast()) { fixSources( *vvtxp, [&](DfgVarPacked& clone, DfgVertex& driver, size_t driverIdx) { @@ -321,59 +328,55 @@ class ExtractCyclicComponents final { fixSinks(*vvtxp); return; } - - if (VL_UNLIKELY(m_doExpensiveChecks)) { - // Non-variable vertex. Just check that edges do not cross components - const size_t component = m_state.at(&vtx).component; - vtx.forEachSourceEdge([&](DfgEdge& edge, size_t) { - DfgVertex& source = *edge.sourcep(); - // OK to cross at variables - if (source.is()) return; - UASSERT_OBJ(component == m_state.at(&source).component, &vtx, - "Component crossing edge without variable involvement"); - }); - } } static void packSources(DfgGraph& dfg) { // Remove undriven variable sources - dfg.forEachVertex([&](DfgVertex& vtx) { - if (DfgVarPacked* const varp = vtx.cast()) { + for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + if (DfgVarPacked* const varp = vtxp->cast()) { varp->packSources(); if (!varp->hasSinks() && varp->arity() == 0) { VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); } return; } - if (DfgVarArray* const varp = vtx.cast()) { + if (DfgVarArray* const varp = vtxp->cast()) { varp->packSources(); if (!varp->hasSinks() && varp->arity() == 0) { VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); } return; } - }); + } } - static void checkEdges(DfgGraph& dfg) { - // Check that each edge connects to a vertex that is within the same graph. - // Also check variable vertex sources are all connected. + void checkGraph(DfgGraph& dfg) const { + // Build set of vertices std::unordered_set vertices{dfg.size()}; dfg.forEachVertex([&](const DfgVertex& vtx) { vertices.insert(&vtx); }); + + // Check that: + // - Edges only cross components at variable boundaries + // - Each edge connects to a vertex that is within the same graph + // - Variable vertex sources are all connected. dfg.forEachVertex([&](const DfgVertex& vtx) { + const size_t component = m_state.at(&vtx).component; vtx.forEachSource([&](const DfgVertex& src) { + if (!src.is()) { // OK to cross at variables + UASSERT_OBJ(component == m_state.at(&src).component, &vtx, + "Edge crossing components without variable involvement"); + } UASSERT_OBJ(vertices.count(&src), &vtx, "Source vertex not in graph"); }); vtx.forEachSink([&](const DfgVertex& snk) { + if (!snk.is()) { // OK to cross at variables + UASSERT_OBJ(component == m_state.at(&snk).component, &vtx, + "Edge crossing components without variable involvement"); + } UASSERT_OBJ(vertices.count(&snk), &snk, "Sink vertex not in graph"); }); - if (const DfgVarPacked* const vtxp = vtx.cast()) { - vtxp->forEachSourceEdge([](const DfgEdge& edge, size_t) { - UASSERT_OBJ(edge.sourcep(), edge.sinkp(), "Missing source on variable vertex"); - }); - return; - } - if (const DfgVarArray* const vtxp = vtx.cast()) { + if (const DfgVertexVar* const vtxp = vtx.cast()) { vtxp->forEachSourceEdge([](const DfgEdge& edge, size_t) { UASSERT_OBJ(edge.sourcep(), edge.sinkp(), "Missing source on variable vertex"); }); @@ -393,26 +396,45 @@ class ExtractCyclicComponents final { m_components[i].reset(new DfgGraph{*m_dfg.modulep(), m_prefix + cvtToStr(i)}); } - // Fix up edges crossing components, and move vertices into their correct component. Note - // that fixing up the edges can create clones. Clones are added to the correct component, - // which also means that they might be added to the original DFG. Clones do not need - // fixing up, but also are not necessarily in the m_state map (in fact they are only there - // in debug mode), so we only iterate up to the original vertices. Because any new vertex - // is added at the end of the vertex list, we can just do this by iterating a fixed number - // of vertices. - size_t vertexCount = m_dfg.size(); - m_dfg.forEachVertex([&](DfgVertex& vtx) { - if (!vertexCount) return; - --vertexCount; + // Fix up edges crossing components (we can only do this at variable boundaries, and the + // earlier merging of components ensured crossing in fact only happen at variable + // boundaries). Note that fixing up the edges can create clones of variables. Clones are + // added to the correct component, which also means that they might be added to the + // original DFG. Clones do not need fixing up, but also are not necessarily in the m_state + // map (in fact they are only there in debug mode), so we need to check this. + // Also move vertices into their correct component while we are at it. + for (DfgVertexVar *vtxp = m_dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + // It is possible the last vertex (with a nullptr for 'nextp') gets cloned, and hence + // it's 'nextp' would become none nullptr as the clone is added. However, we don't need + // to iterate clones anyway, so it's ok to get the 'nextp' early in the loop. + nextp = vtxp->verticesNext(); + // Clones need not be fixed up + if (!m_state.count(vtxp)) return; // Fix up the edges crossing components - fixEdges(vtx); - // Move the vertex to the component graph (leave component 0, which is the originally - // acyclic sub-graph, in the original graph) - if (const size_t component = m_state.at(&vtx).component) { - m_dfg.removeVertex(vtx); - m_components[component - 1]->addVertex(vtx); + fixEdges(*vtxp); + // Move the vertex to the component graph (leave component 0, which is the + // originally acyclic sub-graph, in the original graph) + if (const size_t component = m_state.at(vtxp).component) { + m_dfg.removeVertex(*vtxp); + m_components[component - 1]->addVertex(*vtxp); } - }); + } + + // Move other vertices to their component graphs + for (DfgConst *vtxp = m_dfg.constVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + if (const size_t component = m_state.at(vtxp).component) { + m_dfg.removeVertex(*vtxp); + m_components[component - 1]->addVertex(*vtxp); + } + } + for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + if (const size_t component = m_state.at(vtxp).component) { + m_dfg.removeVertex(*vtxp); + m_components[component - 1]->addVertex(*vtxp); + } + } // Pack sources of variables to remove the now undriven inputs // (cloning might have unlinked some of the inputs), @@ -421,8 +443,8 @@ class ExtractCyclicComponents final { if (VL_UNLIKELY(m_doExpensiveChecks)) { // Check results for consistency - checkEdges(m_dfg); - for (const auto& dfgp : m_components) checkEdges(*dfgp); + checkGraph(m_dfg); + for (const auto& dfgp : m_components) checkGraph(*dfgp); } } @@ -448,24 +470,6 @@ std::vector> DfgGraph::extractCyclicComponents(std::st return ExtractCyclicComponents::apply(*this, label); } -void DfgGraph::runToFixedPoint(std::function f) { - bool changed; - const auto apply = [&](DfgVertex& vtx) -> void { - if (f(vtx)) changed = true; - }; - while (true) { - // Do one pass over the graph. - changed = false; - forEachVertex(apply); - if (!changed) break; - // Do another pass in the opposite direction. Alternating directions reduces - // the pathological complexity with left/right leaning trees. - changed = false; - forEachVertexInReverse(apply); - if (!changed) break; - } -} - static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; } // Dump one DfgVertex in Graphviz format diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 822b192d1..5f5928c6c 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -120,7 +120,15 @@ class DfgGraph final { }; // MEMBERS - V3List m_vertices; // The vertices in the graph + + // Variables and constants make up a significant proportion of vertices (40-50% was observed + // in large designs), and they can often be treated specially in algorithms, which in turn + // enables significant verilation performance gains, so we keep these in separate lists for + // direct access. + V3List m_varVertices; // The variable vertices in the graph + V3List m_constVertices; // The constant vertices in the graph + V3List m_opVertices; // The operation vertices in the graph + size_t m_size = 0; // Number of vertices in the graph uint32_t m_userCurrent = 0; // Vertex user data generation number currently in use uint32_t m_userCnt = 0; // Vertex user data generation counter @@ -156,9 +164,13 @@ public: return UserDataInUse{this}; } - // Access to vertex list for faster iteration in important contexts - DfgVertex* verticesBegin() const { return m_vertices.begin(); } - DfgVertex* verticesRbegin() const { return m_vertices.rbegin(); } + // Access to vertex lists for faster iteration in important contexts + inline DfgVertexVar* varVerticesBeginp() const; + inline DfgVertexVar* varVerticesRbeginp() const; + inline DfgConst* constVerticesBeginp() const; + inline DfgConst* constVerticesRbeginp() const; + inline DfgVertex* opVerticesBeginp() const; + inline DfgVertex* opVerticesRbeginp() const; // Calls given function 'f' for each vertex in the graph. It is safe to manipulate any vertices // in the graph, or to delete/unlink the vertex passed to 'f' during iteration. It is however @@ -168,14 +180,6 @@ public: // 'const' variant of 'forEachVertex'. No mutation allowed. inline void forEachVertex(std::function f) const; - // Same as 'forEachVertex' but iterates in reverse order. - inline void forEachVertexInReverse(std::function f); - - // Returns first vertex of type 'Vertex' that satisfies the given predicate 'p', - // or nullptr if no such vertex exists in the graph. - template - inline Vertex* findVertex(std::function p) const; - // Add contents of other graph to this graph. Leaves other graph empty. void addGraph(DfgGraph& other); @@ -193,10 +197,6 @@ public: // it was originally connected. std::vector> extractCyclicComponents(std::string label); - // Apply the given function to all vertices in the graph. The function return value - // indicates that a change has been made to the graph. Repeat until no changes reported. - void runToFixedPoint(std::function f); - // Dump graph in Graphviz format into the given stream 'os'. 'label' is added to the name of // the graph which is included in the output. void dumpDot(std::ostream& os, const string& label = "") const; @@ -537,46 +537,53 @@ public: void DfgGraph::addVertex(DfgVertex& vtx) { ++m_size; - vtx.m_verticesEnt.pushBack(m_vertices, &vtx); + if (vtx.is()) { + vtx.m_verticesEnt.pushBack(m_constVertices, &vtx); + } else if (vtx.is()) { + vtx.m_verticesEnt.pushBack(m_varVertices, &vtx); + } else { + vtx.m_verticesEnt.pushBack(m_opVertices, &vtx); + } vtx.m_graphp = this; } void DfgGraph::removeVertex(DfgVertex& vtx) { --m_size; - vtx.m_verticesEnt.unlink(m_vertices, &vtx); + if (vtx.is()) { + vtx.m_verticesEnt.unlink(m_constVertices, &vtx); + } else if (vtx.is()) { + vtx.m_verticesEnt.unlink(m_varVertices, &vtx); + } else { + vtx.m_verticesEnt.unlink(m_opVertices, &vtx); + } vtx.m_graphp = nullptr; } void DfgGraph::forEachVertex(std::function f) { - for (DfgVertex *vtxp = m_vertices.begin(), *nextp; vtxp; vtxp = nextp) { - nextp = vtxp->m_verticesEnt.nextp(); + for (DfgVertex *vtxp = m_varVertices.begin(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + f(*vtxp); + } + for (DfgVertex *vtxp = m_constVertices.begin(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + f(*vtxp); + } + for (DfgVertex *vtxp = m_opVertices.begin(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); f(*vtxp); } } void DfgGraph::forEachVertex(std::function f) const { - for (const DfgVertex* vtxp = m_vertices.begin(); vtxp; vtxp = vtxp->m_verticesEnt.nextp()) { + for (const DfgVertex* vtxp = m_varVertices.begin(); vtxp; vtxp = vtxp->verticesNext()) { f(*vtxp); } -} - -void DfgGraph::forEachVertexInReverse(std::function f) { - for (DfgVertex *vtxp = m_vertices.rbegin(), *nextp; vtxp; vtxp = nextp) { - nextp = vtxp->m_verticesEnt.prevp(); + for (const DfgVertex* vtxp = m_constVertices.begin(); vtxp; vtxp = vtxp->verticesNext()) { f(*vtxp); } -} - -template -Vertex* DfgGraph::findVertex(std::function p) const { - static_assert(std::is_base_of::value, - "'Vertex' must be subclass of 'DfgVertex'"); - for (DfgVertex* vtxp = m_vertices.begin(); vtxp; vtxp = vtxp->m_verticesEnt.nextp()) { - if (Vertex* const vvtxp = vtxp->cast()) { - if (p(*vvtxp)) return vvtxp; - } + for (const DfgVertex* vtxp = m_opVertices.begin(); vtxp; vtxp = vtxp->verticesNext()) { + f(*vtxp); } - return nullptr; } void DfgVertex::forEachSource(std::function f) const { @@ -831,6 +838,21 @@ public: // The rest of the DfgVertex subclasses are generated by 'astgen' from AstNodeMath nodes #include "V3Dfg__gen_auto_classes.h" +DfgVertexVar* DfgGraph::varVerticesBeginp() const { + return static_cast(m_varVertices.begin()); +} +DfgVertexVar* DfgGraph::varVerticesRbeginp() const { + return static_cast(m_varVertices.rbegin()); +} +DfgConst* DfgGraph::constVerticesBeginp() const { + return static_cast(m_constVertices.begin()); +} +DfgConst* DfgGraph::constVerticesRbeginp() const { + return static_cast(m_constVertices.rbegin()); +} +DfgVertex* DfgGraph::opVerticesBeginp() const { return m_opVertices.begin(); } +DfgVertex* DfgGraph::opVerticesRbeginp() const { return m_opVertices.rbegin(); } + bool DfgVertex::isZero() const { if (const DfgConst* const constp = cast()) return constp->isZero(); return false; diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index e30a31ae8..12cdd2dd6 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -355,6 +355,7 @@ class DfgToAstVisitor final : DfgVisitor { explicit DfgToAstVisitor(DfgGraph& dfg, V3DfgOptimizationContext& ctx) : m_modp{dfg.modulep()} , m_ctx{ctx} { + // Convert the graph back to combinational assignments // Used by DfgVertex::hash const auto userDataInUse = dfg.userDataInUse(); @@ -362,14 +363,15 @@ class DfgToAstVisitor final : DfgVisitor { // We can eliminate some variables completely std::vector redundantVarps; - // Convert vertices back to assignments - dfg.forEachVertex([&](DfgVertex& vtx) { + // First render variable assignments + for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + + // If there is no driver (this vertex is an input to the graph), then nothing to do. + if (!vtxp->isDrivenByDfg()) continue; + // Render packed variable assignments - if (const DfgVarPacked* const dfgVarp = vtx.cast()) { - // DfgVarPacked instances (these might be driving the given AstVar variable) - // If there is no driver (i.e.: this DfgVarPacked is an input to the Dfg), then - // nothing to do - if (!dfgVarp->isDrivenByDfg()) return; + if (const DfgVarPacked* const dfgVarp = vtxp->cast()) { // The driver of this DfgVarPacked might drive multiple variables. Only emit one // assignment from the driver to an arbitrarily chosen canonical variable, and // assign the other variables from that canonical variable @@ -386,36 +388,40 @@ class DfgToAstVisitor final : DfgVisitor { redundantVarps.push_back(dfgVarp->varp()); ++m_ctx.m_replacedVars; } - return; + // Done + continue; } // Render array variable assignments - if (const DfgVarArray* dfgVarp = vtx.cast()) { - // If there is no driver, then there is nothing to do - if (!dfgVarp->isDrivenByDfg()) return; + if (const DfgVarArray* dfgVarp = vtxp->cast()) { // We don't canonicalize arrays, so just render the drivers convertArrayDiver(dfgVarp); - // - return; + // Done + continue; } + } - // If the vertex is known to be inlined, then nothing else to do - if (inlineVertex(vtx)) return; + // Constants are always inlined, so we only need to iterate proper operations + for (DfgVertex *vtxp = dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + + // If the vertex is known to be inlined, then there is nothing to do + if (inlineVertex(*vtxp)) continue; // Check if this uses a temporary, vs one of the vars rendered above - AstVar* const resultVarp = getResultVar(&vtx); + AstVar* const resultVarp = getResultVar(vtxp); if (resultVarp->user1()) { // We introduced a temporary for this DfgVertex ++m_ctx.m_intermediateVars; - FileLine* const flp = vtx.fileline(); + FileLine* const flp = vtxp->fileline(); // Just render the logic - AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(&vtx); - // The lhs is a temporary + AstNodeMath* const rhsp = convertDfgVertexToAstNodeMath(vtxp); + // The lhs is the temporary AstNodeMath* const lhsp = new AstVarRef{flp, resultVarp, VAccess::WRITE}; // Add assignment of the value to the variable addResultEquation(flp, lhsp, rhsp); } - }); + } // Remap all references to point to the canonical variables, if one exists VNDeleter deleter; diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 571bdb489..e4d75371d 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -84,16 +84,37 @@ void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) { // Used by DfgVertex::hash const auto userDataInUse = dfg.userDataInUse(); - // In reverse, as the graph is sometimes in reverse topological order already - for (DfgVertex *vtxp = dfg.verticesRbegin(), *nextp; vtxp; vtxp = nextp) { - nextp = vtxp->verticesPrev(); + // Pre-hash variables for speed, these are all unique, so just set their hash to a unique value + uint32_t varHash = 0; + for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + vtxp->user() = V3Hash{++varHash}; + } + + // Similarly pre-hash constants for speed. While we don't combine constants, we do want + // expressions using the same constants to be combined, so we do need to hash equal constants + // to equal values. + for (DfgConst *vtxp = dfg.constVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + // Get rid of unused constants while we are at it + if (!vtxp->hasSinks()) { + vtxp->unlinkDelete(dfg); + continue; + } + vtxp->user() = vtxp->num().toHash(); + } + + // Combine operation vertices + for (DfgVertex *vtxp = dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + // Get rid of unused operations while we are at it + if (!vtxp->hasSinks()) { + vtxp->unlinkDelete(dfg); + continue; + } + const V3Hash hash = vtxp->hash(); if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp); - - // Don't merge constants - if (vtxp->is()) continue; - - // For everything else... - std::vector& vec = verticesWithEqualHashes[vtxp->hash()]; + std::vector& vec = verticesWithEqualHashes[hash]; bool replaced = false; for (DfgVertex* const candidatep : vec) { if (candidatep->equals(*vtxp, equalsCache)) { @@ -109,23 +130,37 @@ void V3DfgPasses::cse(DfgGraph& dfg, V3DfgCseContext& ctx) { } } +void V3DfgPasses::inlineVars(DfgGraph& dfg) { + for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + if (DfgVarPacked* const varp = vtxp->cast()) { + if (varp->hasSinks() && varp->isDrivenFullyByDfg()) { + DfgVertex* const driverp = varp->source(0); + varp->forEachSinkEdge([=](DfgEdge& edge) { edge.relinkSource(driverp); }); + } + } + } +} + void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { - dfg.forEachVertex([&](DfgVertex& vtx) { - // We can eliminate certain redundant DfgVarPacked vertices - DfgVarPacked* const varp = vtx.cast(); - if (!varp) return; + for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + + // We can only eliminate DfgVarPacked vertices at the moment + DfgVarPacked* const varp = vtxp->cast(); + if (!varp) continue; // Can't remove if it has consumers - if (varp->hasSinks()) return; + if (varp->hasSinks()) continue; // Can't remove if read in the module and driven here (i.e.: it's an output of the DFG) - if (varp->hasModRefs() && varp->isDrivenByDfg()) return; + if (varp->hasModRefs() && varp->isDrivenByDfg()) continue; // Can't remove if only partially driven by the DFG - if (varp->isDrivenByDfg() && !varp->isDrivenFullyByDfg()) return; + if (varp->isDrivenByDfg() && !varp->isDrivenFullyByDfg()) continue; // Can't remove if referenced externally, or other special reasons - if (varp->keep()) return; + if (varp->keep()) continue; // If the driver of this variable has multiple non-variable sinks, then we would need // a temporary when rendering the graph. Instead of introducing a temporary, keep the @@ -144,7 +179,7 @@ void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { return firstSinkVarp && nonVarSinks >= 2; }); // Keep this DfgVarPacked if needed - if (keepFirst && firstSinkVarp == varp) return; + if (keepFirst && firstSinkVarp == varp) continue; } // OK, we can delete this DfgVarPacked @@ -155,22 +190,41 @@ void V3DfgPasses::removeVars(DfgGraph& dfg, DfgRemoveVarsContext& ctx) { if (!varp->hasRefs()) varp->varp()->unlinkFrBack()->deleteTree(); // Unlink and delete vertex - vtx.unlinkDelete(dfg); - }); + varp->unlinkDelete(dfg); + } } void V3DfgPasses::removeUnused(DfgGraph& dfg) { - const auto processVertex = [&](DfgVertex& vtx) { - // Keep variables - if (vtx.is()) return false; - // Keep if it has sinks - if (vtx.hasSinks()) return false; - // Unlink and delete vertex - vtx.unlinkDelete(dfg); - return true; - }; + // Iteratively remove operation vertices + while (true) { + // Do one pass over the graph. + bool changed = false; + for (DfgVertex *vtxp = dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + if (!vtxp->hasSinks()) { + changed = true; + vtxp->unlinkDelete(dfg); + } + } + if (!changed) break; + // Do another pass in the opposite direction. Alternating directions reduces + // the pathological complexity with left/right leaning trees. + changed = false; + for (DfgVertex *vtxp = dfg.opVerticesRbeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesPrev(); + if (!vtxp->hasSinks()) { + changed = true; + vtxp->unlinkDelete(dfg); + } + } + if (!changed) break; + } - dfg.runToFixedPoint(processVertex); + // Finally remove unused constants + for (DfgConst *vtxp = dfg.constVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + if (!vtxp->hasSinks()) vtxp->unlinkDelete(dfg); + } } void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { @@ -193,12 +247,12 @@ void V3DfgPasses::optimize(DfgGraph& dfg, V3DfgOptimizationContext& ctx) { if (dumpDfg() >= 8) dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "input"); apply(3, "input ", [&]() {}); apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext0); }); + apply(4, "inlineVars ", [&]() { inlineVars(dfg); }); if (v3Global.opt.fDfgPeephole()) { apply(4, "peephole ", [&]() { peephole(dfg, ctx.m_peepholeContext); }); - // Without peephole no variables will be redundant, and we just did CSE, so skip these - apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext1); }); - apply(4, "removeVars ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); }); } + apply(4, "cse ", [&]() { cse(dfg, ctx.m_cseContext1); }); + apply(4, "removeVars ", [&]() { removeVars(dfg, ctx.m_removeVarsContext); }); apply(3, "optimized ", [&]() { removeUnused(dfg); }); if (dumpDfg() >= 8) dfg.dumpDotAllVarConesPrefixed(ctx.prefix() + "optimized"); } diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index 2cfa69a24..93c0d94fa 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -102,6 +102,8 @@ AstModule* dfgToAst(DfgGraph&, V3DfgOptimizationContext&); // Common subexpression elimination void cse(DfgGraph&, V3DfgCseContext&); +// Inline fully driven variables +void inlineVars(DfgGraph&); // Peephole optimizations void peephole(DfgGraph&, V3DfgPeepholeContext&); // Remove redundant variables diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 254bcde4b..4b9e8f9b6 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -1544,34 +1544,12 @@ class V3DfgPeephole final : public DfgVisitor { } } - //========================================================================= - // DfgVertexVar - //========================================================================= - - void visit(DfgVarPacked* vtxp) override { - // Inline variables fully driven by the logic represented by the DFG - if (vtxp->hasSinks() && vtxp->isDrivenFullyByDfg()) { - APPLYING(INLINE_VAR) { - // Make consumers of the DfgVar consume the driver directly - DfgVertex* const driverp = vtxp->source(0); - vtxp->forEachSinkEdge([=](DfgEdge& edge) { edge.relinkSource(driverp); }); - } - } - } - #undef APPLYING // Process one vertex. Return true if graph changed void processVertex(DfgVertex* vtxp) { - // Keep DfgVertexVar vertices in this pass. We will remove them later if they become - // redundant. We want to keep the original variables for non-var vertices that drive - // multiple sinks (otherwise we would need to introduce a temporary, but it is better for - // debugging to keep the original variable name, if one is available), so we can't remove - // redundant variables here. - const bool keep = vtxp->is(); - // If it has no sinks (unused), we can remove it - if (!keep && !vtxp->hasSinks()) { + if (!vtxp->hasSinks()) { vtxp->unlinkDelete(m_dfg); m_changed = true; return; @@ -1581,7 +1559,7 @@ class V3DfgPeephole final : public DfgVisitor { iterate(vtxp); // If it became unused, we can remove it - if (!keep && !vtxp->hasSinks()) { + if (!vtxp->hasSinks()) { UASSERT_OBJ(m_changed, vtxp, "'m_changed' must be set if node became unused"); vtxp->unlinkDelete(m_dfg); } @@ -1594,15 +1572,9 @@ class V3DfgPeephole final : public DfgVisitor { while (true) { // Do one pass over the graph in the forward direction. m_changed = false; - for (DfgVertex *vtxp = m_dfg.verticesBegin(), *nextp; vtxp; vtxp = nextp) { + for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { nextp = vtxp->verticesNext(); if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp); - // Special case DfgConst as it's common and the is nothing we can do about them. - // No need to set 'm_changed' when deleting it as it influences nothing else. - if (vtxp->is()) { - if (!vtxp->hasSinks()) vtxp->unlinkDelete(m_dfg); - continue; - } processVertex(vtxp); } if (!m_changed) break; @@ -1610,15 +1582,9 @@ class V3DfgPeephole final : public DfgVisitor { // Do another pass in the opposite direction. Alternating directions reduces // the pathological complexity with left/right leaning trees. m_changed = false; - for (DfgVertex *vtxp = m_dfg.verticesRbegin(), *nextp; vtxp; vtxp = nextp) { + for (DfgVertex *vtxp = m_dfg.opVerticesRbeginp(), *nextp; vtxp; vtxp = nextp) { nextp = vtxp->verticesPrev(); if (VL_LIKELY(nextp)) VL_PREFETCH_RW(nextp); - // Special case DfgConst as it's common and the is nothing we can do about them. - // No need to set 'm_changed' when deleting it as it influences nothing else. - if (vtxp->is()) { - if (!vtxp->hasSinks()) vtxp->unlinkDelete(m_dfg); - continue; - } processVertex(vtxp); } if (!m_changed) break; diff --git a/src/V3DfgPeephole.h b/src/V3DfgPeephole.h index 2f64da014..6323fbe13 100644 --- a/src/V3DfgPeephole.h +++ b/src/V3DfgPeephole.h @@ -33,7 +33,6 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_SEL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_UNARY) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_VAR) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_REDUCTION) \ diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index c67a7e6fd..8b003ace0 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -52,6 +52,15 @@ public: , m_varp{varp} {} ASTGEN_MEMBERS_DfgVertexVar; + DfgVertexVar* verticesNext() const { + return static_cast(DfgVertex::verticesNext()); + } + DfgVertexVar* verticesPrev() const { + return static_cast(DfgVertex::verticesPrev()); + } + + bool isDrivenByDfg() const { return arity() > 0; } + AstVar* varp() const { return m_varp; } bool hasModRefs() const { return m_hasModRefs; } void setHasModRefs() { m_hasModRefs = true; } @@ -93,6 +102,9 @@ public: , m_num{flp, static_cast(width), value} {} ASTGEN_MEMBERS_DfgConst; + DfgConst* verticesNext() const { return static_cast(DfgVertex::verticesNext()); } + DfgConst* verticesPrev() const { return static_cast(DfgVertex::verticesPrev()); } + V3Number& num() { return m_num; } const V3Number& num() const { return m_num; } @@ -167,8 +179,6 @@ public: } ASTGEN_MEMBERS_DfgVarArray; - bool isDrivenByDfg() const { return arity() > 0; } - void addDriver(FileLine* flp, uint32_t index, DfgVertex* vtxp) { m_driverData.emplace_back(flp, index); DfgVertexVariadic::addSource()->relinkSource(vtxp); @@ -224,7 +234,6 @@ public: : DfgVertexVar{dfg, dfgType(), varp, 1u} {} ASTGEN_MEMBERS_DfgVarPacked; - bool isDrivenByDfg() const { return arity() > 0; } bool isDrivenFullyByDfg() const { return arity() == 1 && source(0)->dtypep() == dtypep(); } void addDriver(FileLine* flp, uint32_t lsb, DfgVertex* vtxp) { From ff49f797e5ec201343bddfd5c317e7fec437cd8b Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 8 Oct 2022 12:05:30 +0100 Subject: [PATCH 096/177] Speed up DfgGraph::addGraph Append whole lists in one go, rather than going item by item. --- src/V3Dfg.cpp | 20 ++++++++++++++++---- src/V3Dfg.h | 2 ++ src/V3List.h | 17 +++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 4fd2f7a93..39983cd5c 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -40,10 +40,22 @@ DfgGraph::~DfgGraph() { } void DfgGraph::addGraph(DfgGraph& other) { - other.forEachVertex([&](DfgVertex& vtx) { - other.removeVertex(vtx); - this->addVertex(vtx); - }); + m_size += other.m_size; + other.m_size = 0; + + const auto moveVertexList = [this](V3List& src, V3List& dst) { + if (DfgVertex* vtxp = src.begin()) { + vtxp->m_verticesEnt.moveAppend(src, dst, vtxp); + do { + vtxp->m_graphp = this; + vtxp = vtxp->verticesNext(); + } while (vtxp); + } + }; + + moveVertexList(other.m_varVertices, m_varVertices); + moveVertexList(other.m_constVertices, m_constVertices); + moveVertexList(other.m_opVertices, m_opVertices); } std::vector> DfgGraph::splitIntoComponents(std::string label) { diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 5f5928c6c..3720e09e4 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -536,6 +536,7 @@ public: //------------------------------------------------------------------------------ void DfgGraph::addVertex(DfgVertex& vtx) { + // Note: changes here need to be replicated in DfgGraph::addGraph ++m_size; if (vtx.is()) { vtx.m_verticesEnt.pushBack(m_constVertices, &vtx); @@ -548,6 +549,7 @@ void DfgGraph::addVertex(DfgVertex& vtx) { } void DfgGraph::removeVertex(DfgVertex& vtx) { + // Note: changes here need to be replicated in DfgGraph::addGraph --m_size; if (vtx.is()) { vtx.m_verticesEnt.unlink(m_constVertices, &vtx); diff --git a/src/V3List.h b/src/V3List.h index 83ef4de17..e7e524c6e 100644 --- a/src/V3List.h +++ b/src/V3List.h @@ -119,6 +119,23 @@ public: } m_prevp = m_nextp = nullptr; } + // Remove all nodes from 'oldListr', append them to 'newListr'. 'this' must be a member of the + // object at 'selfp', and 'selfp' must be the head of the list in 'oldListr'. + void moveAppend(V3List& oldListr, V3List& newListr, T selfp) { + UASSERT(selfp == oldListr.m_headp, "Must be head of list to use 'moveAppend'"); + const size_t offset = (size_t)(uint8_t*)(this) - (size_t)(uint8_t*)(selfp); + const T headp = selfp; + const T tailp = oldListr.m_tailp; + oldListr.reset(); + if (newListr.empty()) { + newListr.m_headp = headp; + newListr.m_tailp = tailp; + } else { + baseToListEnt(newListr.m_tailp, offset)->m_nextp = headp; + m_prevp = newListr.m_tailp; + newListr.m_tailp = tailp; + } + } }; //============================================================================ From 18c26b90afaaf87bad19586f10f338ee793a2b7e Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 9 Oct 2022 14:16:44 -0400 Subject: [PATCH 097/177] Fix --trace with --main/--binary (#3664) --- src/V3EmitCMain.cpp | 2 +- test_regress/t/t_trace_binary.out | 16 ++++++++++ test_regress/t/t_trace_binary.pl | 35 ++++++++++++++++++++++ test_regress/t/t_trace_binary.v | 20 +++++++++++++ test_regress/t/t_trace_binary_flag_off.out | 2 ++ test_regress/t/t_trace_binary_flag_off.pl | 35 ++++++++++++++++++++++ 6 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 test_regress/t/t_trace_binary.out create mode 100755 test_regress/t/t_trace_binary.pl create mode 100644 test_regress/t/t_trace_binary.v create mode 100644 test_regress/t/t_trace_binary_flag_off.out create mode 100755 test_regress/t/t_trace_binary_flag_off.pl diff --git a/src/V3EmitCMain.cpp b/src/V3EmitCMain.cpp index 87fd518ad..cfe83de6b 100644 --- a/src/V3EmitCMain.cpp +++ b/src/V3EmitCMain.cpp @@ -64,8 +64,8 @@ private: puts("int main(int argc, char** argv, char**) {\n"); puts("// Setup context, defaults, and parse command line\n"); puts("Verilated::debug(0);\n"); - if (v3Global.opt.trace()) puts("Verilated::traceEverOn(true);\n"); puts("const std::unique_ptr contextp{new VerilatedContext};\n"); + if (v3Global.opt.trace()) puts("contextp->traceEverOn(true);\n"); puts("contextp->commandArgs(argc, argv);\n"); puts("\n"); diff --git a/test_regress/t/t_trace_binary.out b/test_regress/t/t_trace_binary.out new file mode 100644 index 000000000..32d4f8ec5 --- /dev/null +++ b/test_regress/t/t_trace_binary.out @@ -0,0 +1,16 @@ +$version Generated by VerilatedVcd $end +$date Sun Oct 9 14:08:37 2022 $end +$timescale 1ps $end + + $scope module TOP $end + $scope module t $end + $var wire 32 # sig [31:0] $end + $upscope $end + $upscope $end +$enddefinitions $end + + +#0 +b00000000000000000000000000001010 # +#20 +b00000000000000000000000000010100 # diff --git a/test_regress/t/t_trace_binary.pl b/test_regress/t/t_trace_binary.pl new file mode 100755 index 000000000..03fd8a3c5 --- /dev/null +++ b/test_regress/t/t_trace_binary.pl @@ -0,0 +1,35 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags => [# Custom as don't want -cc + "-Mdir $Self->{obj_dir}", + "--debug-check", ], + verilator_flags2 => ['--binary --trace'], + verilator_make_cmake => 0, + verilator_make_gmake => 0, + make_main => 0, + ); + + execute( + check_finished => 1, + ); + + vcd_identical("$Self->{obj_dir}/simx.vcd", $Self->{golden_filename}); +} + +ok(1); +1; diff --git a/test_regress/t/t_trace_binary.v b/test_regress/t/t_trace_binary.v new file mode 100644 index 000000000..445c2b375 --- /dev/null +++ b/test_regress/t/t_trace_binary.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2020 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`define STRINGIFY(x) `"x`" + +module t(/*AUTOARG*/); + int sig; + initial begin + sig = 10; + $dumpfile({`STRINGIFY(`TEST_OBJ_DIR),"/simx.vcd"}); + $dumpvars(); + #20; + sig = 20; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_trace_binary_flag_off.out b/test_regress/t/t_trace_binary_flag_off.out new file mode 100644 index 000000000..8c40ee2bf --- /dev/null +++ b/test_regress/t/t_trace_binary_flag_off.out @@ -0,0 +1,2 @@ +-Info: t/t_trace_binary.v:14: $dumpvar ignored, as Verilated without --trace +*-* All Finished *-* diff --git a/test_regress/t/t_trace_binary_flag_off.pl b/test_regress/t/t_trace_binary_flag_off.pl new file mode 100755 index 000000000..eaa6a3518 --- /dev/null +++ b/test_regress/t/t_trace_binary_flag_off.pl @@ -0,0 +1,35 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vlt => 1); + +top_filename("t/t_trace_binary.v"); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags => [# Custom as don't want -cc + "-Mdir $Self->{obj_dir}", + "--debug-check", ], + verilator_flags2 => ['--binary'], + verilator_make_cmake => 0, + verilator_make_gmake => 0, + make_main => 0, + ); + + execute( + expect_filename => $Self->{golden_filename}, + ); +} + +ok(1); +1; From 6f9c585452dd57d39966ded97a547c62d0f38b4d Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 9 Oct 2022 14:18:14 -0400 Subject: [PATCH 098/177] Spelling (#3664) --- include/verilated.cpp | 2 +- test_regress/t/t_trace_dumporder_bad.out | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/verilated.cpp b/include/verilated.cpp index 38ecdf221..46192052f 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -2310,7 +2310,7 @@ std::string VerilatedContext::dumpfile() const VL_MT_SAFE_EXCLUDES(m_timeDumpMut std::string VerilatedContext::dumpfileCheck() const VL_MT_SAFE_EXCLUDES(m_timeDumpMutex) { std::string out = dumpfile(); if (VL_UNLIKELY(out.empty())) { - VL_PRINTF_MT("%%Warning: $dumpvar ignored as not proceeded by $dumpfile\n"); + VL_PRINTF_MT("%%Warning: $dumpvar ignored as not preceded by $dumpfile\n"); return ""; } return out; diff --git a/test_regress/t/t_trace_dumporder_bad.out b/test_regress/t/t_trace_dumporder_bad.out index f73b7d67a..84fe64421 100644 --- a/test_regress/t/t_trace_dumporder_bad.out +++ b/test_regress/t/t_trace_dumporder_bad.out @@ -1,2 +1,2 @@ -%Warning: $dumpvar ignored as not proceeded by $dumpfile +%Warning: $dumpvar ignored as not preceded by $dumpfile *-* All Finished *-* From ba052beccdddfbf5b286a6c9cae51a0033116451 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Mon, 10 Oct 2022 13:58:05 +0200 Subject: [PATCH 099/177] Make reference to increment temporary an rvalue (#3659) Signed-off-by: Krzysztof Bieganski --- src/V3LinkInc.cpp | 3 ++- test_regress/t/t_increment_bad.out | 3 +++ test_regress/t/t_increment_bad.v | 2 ++ test_regress/t/t_sampled_expr_unsup.out | 8 ++++---- test_regress/t/t_sampled_expr_unsup.v | 26 ++++++------------------- 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/V3LinkInc.cpp b/src/V3LinkInc.cpp index 452105dd6..7b16f8722 100644 --- a/src/V3LinkInc.cpp +++ b/src/V3LinkInc.cpp @@ -189,6 +189,7 @@ private: void visit(AstLogEq* nodep) override { unsupported_visit(nodep); } void visit(AstLogIf* nodep) override { unsupported_visit(nodep); } void visit(AstNodeCond* nodep) override { unsupported_visit(nodep); } + void visit(AstPropClocked* nodep) override { unsupported_visit(nodep); } void prepost_visit(AstNodeTriop* nodep) { // Check if we are underneath a statement if (!m_insStmtp) { @@ -273,7 +274,7 @@ private: } // Replace the node with the temporary - nodep->replaceWith(new AstVarRef(varrefp->fileline(), varp, VAccess::WRITE)); + nodep->replaceWith(new AstVarRef{varrefp->fileline(), varp, VAccess::READ}); VL_DO_DANGLING(nodep->deleteTree(), nodep); } void visit(AstPreAdd* nodep) override { prepost_visit(nodep); } diff --git a/test_regress/t/t_increment_bad.out b/test_regress/t/t_increment_bad.out index ef339225a..f10697bd0 100644 --- a/test_regress/t/t_increment_bad.out +++ b/test_regress/t/t_increment_bad.out @@ -20,4 +20,7 @@ %Error-UNSUPPORTED: t/t_increment_bad.v:23:24: Unsupported: Incrementation in this context. 23 | pos = array[0][0]++; | ^~ +%Error-UNSUPPORTED: t/t_increment_bad.v:26:37: Unsupported: Incrementation in this context. + 26 | assert property (@(posedge clk) a++ >= 0); + | ^~ %Error: Exiting due to diff --git a/test_regress/t/t_increment_bad.v b/test_regress/t/t_increment_bad.v index 7ce03d704..86755af51 100644 --- a/test_regress/t/t_increment_bad.v +++ b/test_regress/t/t_increment_bad.v @@ -22,4 +22,6 @@ module t (/*AUTOARG*/ pos = array[0][0]++; end + + assert property (@(posedge clk) a++ >= 0); endmodule diff --git a/test_regress/t/t_sampled_expr_unsup.out b/test_regress/t/t_sampled_expr_unsup.out index 170ef48f8..8a8a1634d 100644 --- a/test_regress/t/t_sampled_expr_unsup.out +++ b/test_regress/t/t_sampled_expr_unsup.out @@ -1,6 +1,6 @@ -%Error-UNSUPPORTED: t/t_sampled_expr_unsup.v:34:36: Unsupported: Write to variable in sampled expression - : ... In instance t.t1 - 34 | assert property (@(posedge clk) a++ >= 0); - | ^ +%Error-UNSUPPORTED: t/t_sampled_expr_unsup.v:20:38: Unsupported: Write to variable in sampled expression + : ... In instance t + 20 | assert property (@(posedge clk) f(a) >= 0); + | ^ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_sampled_expr_unsup.v b/test_regress/t/t_sampled_expr_unsup.v index 1e8eb9de2..b46d74bf1 100644 --- a/test_regress/t/t_sampled_expr_unsup.v +++ b/test_regress/t/t_sampled_expr_unsup.v @@ -10,26 +10,12 @@ module t (/*AUTOARG*/ ); input clk; - integer cyc; + int a = 0; - Test1 t1(clk); + function int f(output int a); + a = 1; + return a; + endfunction - always @(posedge clk) begin - cyc <= cyc + 1; - - if (cyc >= 10) begin - $write("*-* All Finished *-*\n"); - $finish; - end - end -endmodule - -module Test1( - clk - ); - - input clk; - reg [3:0] a = 0; - - assert property (@(posedge clk) a++ >= 0); + assert property (@(posedge clk) f(a) >= 0); endmodule From 2a110c91cfa29f87a36b2aa500ed1456e275c86b Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 8 Oct 2022 14:34:07 +0100 Subject: [PATCH 100/177] Speed up DfgGraph decomposition algorithms --- src/Makefile_obj.in | 1 + src/V3Dfg.cpp | 428 +---------------------------- src/V3Dfg.h | 33 +++ src/V3DfgDecomposition.cpp | 544 +++++++++++++++++++++++++++++++++++++ 4 files changed, 579 insertions(+), 427 deletions(-) create mode 100644 src/V3DfgDecomposition.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 503fb540f..bcde753fc 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -182,6 +182,7 @@ RAW_OBJS = \ V3Descope.o \ V3Dfg.o \ V3DfgAstToDfg.o \ + V3DfgDecomposition.o \ V3DfgDfgToAst.o \ V3DfgOptimizer.o \ V3DfgPasses.o \ diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 39983cd5c..b3341ff77 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -21,9 +21,6 @@ #include "V3File.h" -#include -#include -#include VL_DEFINE_DEBUG_FUNCTIONS; @@ -47,6 +44,7 @@ void DfgGraph::addGraph(DfgGraph& other) { if (DfgVertex* vtxp = src.begin()) { vtxp->m_verticesEnt.moveAppend(src, dst, vtxp); do { + vtxp->m_userCnt = 0; vtxp->m_graphp = this; vtxp = vtxp->verticesNext(); } while (vtxp); @@ -58,430 +56,6 @@ void DfgGraph::addGraph(DfgGraph& other) { moveVertexList(other.m_opVertices, m_opVertices); } -std::vector> DfgGraph::splitIntoComponents(std::string label) { - size_t componentNumber = 0; - std::unordered_map vertex2component; - - forEachVertex([&](const DfgVertex& vtx) { - // If already assigned this vertex to a component, then continue - if (vertex2component.count(&vtx)) return; - - // Work queue for depth first traversal starting from this vertex - std::vector queue{&vtx}; - - // Depth first traversal - while (!queue.empty()) { - // Pop next work item - const DfgVertex& item = *queue.back(); - queue.pop_back(); - - // Mark vertex as belonging to current component (if it's not marked yet) - const bool isFirstEncounter = vertex2component.emplace(&item, componentNumber).second; - - // If we have already visited this vertex during the traversal, then move on. - if (!isFirstEncounter) continue; - - // Enqueue all sources and sinks of this vertex. - item.forEachSource([&](const DfgVertex& src) { queue.push_back(&src); }); - item.forEachSink([&](const DfgVertex& dst) { queue.push_back(&dst); }); - } - - // Done with this component - ++componentNumber; - }); - - // Create the component graphs - std::vector> results{componentNumber}; - - const std::string prefix{name() + (label.empty() ? "" : "-") + label + "-component-"}; - - for (size_t i = 0; i < componentNumber; ++i) { - results[i].reset(new DfgGraph{*m_modulep, prefix + cvtToStr(i)}); - } - - // Move all vertices under the corresponding component graphs - forEachVertex([&](DfgVertex& vtx) { - this->removeVertex(vtx); - results[vertex2component[&vtx]]->addVertex(vtx); - }); - - UASSERT(size() == 0, "'this' DfgGraph should have been emptied"); - - return results; -} - -class ExtractCyclicComponents final { - static constexpr size_t UNASSIGNED = std::numeric_limits::max(); - - // TYPES - struct VertexState { - size_t index; // Used by Pearce's algorithm for detecting SCCs - size_t component = UNASSIGNED; // Result component number (0 stays in input graph) - VertexState(size_t index) - : index{index} {} - }; - - // STATE - - //========================================================================== - // Shared state - - DfgGraph& m_dfg; // The input graph - const std::string m_prefix; // Component name prefix - std::unordered_map m_state; // Vertex state - size_t m_nonTrivialSCCs = 0; // Number of non-trivial SCCs in the graph - const bool m_doExpensiveChecks = v3Global.opt.debugCheck(); - - //========================================================================== - // State for Pearce's algorithm for detecting SCCs - - size_t m_index = 0; // Visitation index counter - std::vector m_stack; // The stack used by the algorithm - - //========================================================================== - // State for merging - - std::unordered_set m_merged; // Marks visited vertices - - //========================================================================== - // State for extraction - - // The extracted cyclic components - std::vector> m_components; - // Map from 'variable vertex' -> 'component index' -> 'clone in that component' - std::unordered_map> m_clones; - - // METHODS - - //========================================================================== - // Methods for Pearce's algorithm to detect strongly connected components - - void visitColorSCCs(DfgVertex& vtx) { - const auto pair = m_state.emplace(std::piecewise_construct, // - std::forward_as_tuple(&vtx), // - std::forward_as_tuple(m_index)); - - // If already visited, then nothing to do - if (!pair.second) return; - - // Visiting node - const size_t rootIndex = m_index++; - - vtx.forEachSink([&](DfgVertex& child) { - // Visit child - visitColorSCCs(child); - auto& childSatate = m_state.at(&child); - // If the child is not in an SCC - if (childSatate.component == UNASSIGNED) { - auto& vtxState = m_state.at(&vtx); - if (vtxState.index > childSatate.index) vtxState.index = childSatate.index; - } - }); - - auto& vtxState = m_state.at(&vtx); - if (vtxState.index == rootIndex) { - // This is the 'root' of an SCC - - // A trivial SCC contains only a single vertex - const bool isTrivial = m_stack.empty() || m_state.at(m_stack.back()).index < rootIndex; - // We also need a separate component for vertices that drive themselves (which can - // happen for input like 'assign a = a'), as we want to extract them (they are cyclic). - const bool drivesSelf = vtx.findSink([&vtx](const DfgVertex& sink) { // - return &vtx == &sink; - }); - - if (!isTrivial || drivesSelf) { - // Allocate new component - ++m_nonTrivialSCCs; - vtxState.component = m_nonTrivialSCCs; - while (!m_stack.empty()) { - DfgVertex* const topp = m_stack.back(); - auto& topState = m_state.at(topp); - // Only higher nodes belong to the same SCC - if (topState.index < rootIndex) break; - m_stack.pop_back(); - topState.component = m_nonTrivialSCCs; - } - } else { - // Trivial SCC (and does not drive itself), so acyclic. Keep it in original graph. - vtxState.component = 0; - } - } else { - // Not the root of an SCC - m_stack.push_back(&vtx); - } - } - - void colorSCCs() { - // Implements Pearce's algorithm to color the strongly connected components. For reference - // see "An Improved Algorithm for Finding the Strongly Connected Components of a Directed - // Graph", David J.Pearce, 2005 - m_state.reserve(m_dfg.size()); - m_dfg.forEachVertex([&](DfgVertex& vtx) { visitColorSCCs(vtx); }); - } - - //========================================================================== - // Methods for merging - - void visitMergeSCCs(const DfgVertex& vtx, size_t targetComponent) { - // Mark visited/move on if already visited - if (!m_merged.insert(&vtx).second) return; - - // Assign vertex to the target component - m_state.at(&vtx).component = targetComponent; - - // Visit all neighbours. We stop at variable boundaries, - // which is where we will split the graphs - vtx.forEachSource([=](const DfgVertex& other) { - if (other.is()) return; - visitMergeSCCs(other, targetComponent); - }); - vtx.forEachSink([=](const DfgVertex& other) { - if (other.is()) return; - visitMergeSCCs(other, targetComponent); - }); - } - - void mergeSCCs() { - // Ensure that component boundaries are always at variables, by merging SCCs - m_merged.reserve(m_dfg.size()); - // Merging stops at variable boundaries, so we don't need to iterate variables. Constants - // are reachable from their sinks, or ar unused, so we don't need to iterate them either. - for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { - nextp = vtxp->verticesNext(); - // Start DFS from each vertex that is in a non-trivial SCC, and merge everything - // that is reachable from it into this component. - if (const size_t target = m_state.at(vtxp).component) visitMergeSCCs(*vtxp, target); - } - } - - //========================================================================== - // Methods for extraction - - // Retrieve clone of vertex in the given component - DfgVertexVar& getClone(DfgVertexVar& vtx, size_t component) { - UASSERT_OBJ(m_state.at(&vtx).component != component, &vtx, "Vertex is in that component"); - DfgVertexVar*& clonep = m_clones[&vtx][component]; - if (!clonep) { - DfgGraph& dfg = component == 0 ? m_dfg : *m_components[component - 1]; - if (DfgVarPacked* const pVtxp = vtx.cast()) { - clonep = new DfgVarPacked{dfg, pVtxp->varp()}; - } else if (DfgVarArray* const aVtxp = vtx.cast()) { - clonep = new DfgVarArray{dfg, aVtxp->varp()}; - } - UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexVar' sub-type"); - if (VL_UNLIKELY(m_doExpensiveChecks)) { - // Assign component number of clone for later checks - m_state - .emplace(std::piecewise_construct, std::forward_as_tuple(clonep), - std::forward_as_tuple(0)) - .first->second.component - = component; - } - // We need to mark both the original and the clone as having additional references - vtx.setHasModRefs(); - clonep->setHasModRefs(); - } - return *clonep; - } - - // Fix up non-variable sources of a DfgVertexVar that are in a different component, - // using the provided 'relink' callback - template - void fixSources(T_Vertex& vtx, std::function relink) { - static_assert(std::is_base_of::value, - "'Vertex' must be a 'DfgVertexVar'"); - const size_t component = m_state.at(&vtx).component; - vtx.forEachSourceEdge([&](DfgEdge& edge, size_t idx) { - DfgVertex& source = *edge.sourcep(); - // DfgVertexVar sources are fixed up by `fixSinks` on those sources - if (source.is()) return; - const size_t sourceComponent = m_state.at(&source).component; - // Same component is OK - if (sourceComponent == component) return; - // Unlink the source edge (source is reconnected by 'relink' - edge.unlinkSource(); - // Apply the fixup - DfgVertexVar& clone = getClone(vtx, sourceComponent); - relink(*(clone.as()), source, idx); - }); - } - - // Fix up sinks of given variable vertex that are in a different component - void fixSinks(DfgVertexVar& vtx) { - const size_t component = m_state.at(&vtx).component; - vtx.forEachSinkEdge([&](DfgEdge& edge) { - const size_t sinkComponent = m_state.at(edge.sinkp()).component; - // Same component is OK - if (sinkComponent == component) return; - // Relink the sink to read the clone - edge.relinkSource(&getClone(vtx, sinkComponent)); - }); - } - - // Fix edges that cross components - void fixEdges(DfgVertexVar& vtx) { - if (DfgVarPacked* const vvtxp = vtx.cast()) { - fixSources( - *vvtxp, [&](DfgVarPacked& clone, DfgVertex& driver, size_t driverIdx) { - clone.addDriver(vvtxp->driverFileLine(driverIdx), // - vvtxp->driverLsb(driverIdx), &driver); - }); - fixSinks(*vvtxp); - return; - } - - if (DfgVarArray* const vvtxp = vtx.cast()) { - fixSources( // - *vvtxp, [&](DfgVarArray& clone, DfgVertex& driver, size_t driverIdx) { - clone.addDriver(vvtxp->driverFileLine(driverIdx), // - vvtxp->driverIndex(driverIdx), &driver); - }); - fixSinks(*vvtxp); - return; - } - } - - static void packSources(DfgGraph& dfg) { - // Remove undriven variable sources - for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { - nextp = vtxp->verticesNext(); - if (DfgVarPacked* const varp = vtxp->cast()) { - varp->packSources(); - if (!varp->hasSinks() && varp->arity() == 0) { - VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); - } - return; - } - if (DfgVarArray* const varp = vtxp->cast()) { - varp->packSources(); - if (!varp->hasSinks() && varp->arity() == 0) { - VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); - } - return; - } - } - } - - void checkGraph(DfgGraph& dfg) const { - // Build set of vertices - std::unordered_set vertices{dfg.size()}; - dfg.forEachVertex([&](const DfgVertex& vtx) { vertices.insert(&vtx); }); - - // Check that: - // - Edges only cross components at variable boundaries - // - Each edge connects to a vertex that is within the same graph - // - Variable vertex sources are all connected. - dfg.forEachVertex([&](const DfgVertex& vtx) { - const size_t component = m_state.at(&vtx).component; - vtx.forEachSource([&](const DfgVertex& src) { - if (!src.is()) { // OK to cross at variables - UASSERT_OBJ(component == m_state.at(&src).component, &vtx, - "Edge crossing components without variable involvement"); - } - UASSERT_OBJ(vertices.count(&src), &vtx, "Source vertex not in graph"); - }); - vtx.forEachSink([&](const DfgVertex& snk) { - if (!snk.is()) { // OK to cross at variables - UASSERT_OBJ(component == m_state.at(&snk).component, &vtx, - "Edge crossing components without variable involvement"); - } - UASSERT_OBJ(vertices.count(&snk), &snk, "Sink vertex not in graph"); - }); - if (const DfgVertexVar* const vtxp = vtx.cast()) { - vtxp->forEachSourceEdge([](const DfgEdge& edge, size_t) { - UASSERT_OBJ(edge.sourcep(), edge.sinkp(), "Missing source on variable vertex"); - }); - return; - } - }); - } - - void extractComponents() { - // If the graph was acyclic (which should be the common case), there will be no non-trivial - // SCCs, so we are done. - if (!m_nonTrivialSCCs) return; - - // Allocate result graphs - m_components.resize(m_nonTrivialSCCs); - for (size_t i = 0; i < m_nonTrivialSCCs; ++i) { - m_components[i].reset(new DfgGraph{*m_dfg.modulep(), m_prefix + cvtToStr(i)}); - } - - // Fix up edges crossing components (we can only do this at variable boundaries, and the - // earlier merging of components ensured crossing in fact only happen at variable - // boundaries). Note that fixing up the edges can create clones of variables. Clones are - // added to the correct component, which also means that they might be added to the - // original DFG. Clones do not need fixing up, but also are not necessarily in the m_state - // map (in fact they are only there in debug mode), so we need to check this. - // Also move vertices into their correct component while we are at it. - for (DfgVertexVar *vtxp = m_dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { - // It is possible the last vertex (with a nullptr for 'nextp') gets cloned, and hence - // it's 'nextp' would become none nullptr as the clone is added. However, we don't need - // to iterate clones anyway, so it's ok to get the 'nextp' early in the loop. - nextp = vtxp->verticesNext(); - // Clones need not be fixed up - if (!m_state.count(vtxp)) return; - // Fix up the edges crossing components - fixEdges(*vtxp); - // Move the vertex to the component graph (leave component 0, which is the - // originally acyclic sub-graph, in the original graph) - if (const size_t component = m_state.at(vtxp).component) { - m_dfg.removeVertex(*vtxp); - m_components[component - 1]->addVertex(*vtxp); - } - } - - // Move other vertices to their component graphs - for (DfgConst *vtxp = m_dfg.constVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { - nextp = vtxp->verticesNext(); - if (const size_t component = m_state.at(vtxp).component) { - m_dfg.removeVertex(*vtxp); - m_components[component - 1]->addVertex(*vtxp); - } - } - for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { - nextp = vtxp->verticesNext(); - if (const size_t component = m_state.at(vtxp).component) { - m_dfg.removeVertex(*vtxp); - m_components[component - 1]->addVertex(*vtxp); - } - } - - // Pack sources of variables to remove the now undriven inputs - // (cloning might have unlinked some of the inputs), - packSources(m_dfg); - for (const auto& dfgp : m_components) packSources(*dfgp); - - if (VL_UNLIKELY(m_doExpensiveChecks)) { - // Check results for consistency - checkGraph(m_dfg); - for (const auto& dfgp : m_components) checkGraph(*dfgp); - } - } - - // CONSTRUCTOR - entry point - explicit ExtractCyclicComponents(DfgGraph& dfg, std::string label) - : m_dfg{dfg} - , m_prefix{dfg.name() + (label.empty() ? "" : "-") + label + "-component-"} { - // Find all the non-trivial SCCs (and trivial cycles) in the graph - colorSCCs(); - // Ensure that component boundaries are always at variables, by merging SCCs - mergeSCCs(); - // Extract the components - extractComponents(); - } - -public: - static std::vector> apply(DfgGraph& dfg, const std::string& label) { - return std::move(ExtractCyclicComponents{dfg, label}.m_components); - } -}; - -std::vector> DfgGraph::extractCyclicComponents(std::string label) { - return ExtractCyclicComponents::apply(*this, label); -} - static const string toDotId(const DfgVertex& vtx) { return '"' + cvtToHex(&vtx) + '"'; } // Dump one DfgVertex in Graphviz format diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 3720e09e4..a81f0cea5 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -184,6 +184,7 @@ public: void addGraph(DfgGraph& other); // Split this graph into individual components (unique sub-graphs with no edges between them). + // Also removes any vertices that are not weakly connected to any variable. // Leaves 'this' graph empty. std::vector> splitIntoComponents(std::string label); @@ -340,6 +341,7 @@ public: // The type of this vertex VDfgType type() const { return m_type; } + // Retrieve user data, constructing it fresh on first try. template T& user() { static_assert(sizeof(T) <= sizeof(UserDataStorage), @@ -357,6 +359,22 @@ public: return *storagep; } + // Retrieve user data, must be current. + template + T& getUser() { + static_assert(sizeof(T) <= sizeof(UserDataStorage), + "Size of user data type 'T' is too large for allocated storage"); + static_assert(alignof(T) <= alignof(UserDataStorage), + "Alignment of user data type 'T' is larger than allocated storage"); + T* const storagep = reinterpret_cast(&m_userDataStorage); +#if VL_DEBUG + const uint32_t userCurrent = m_graphp->m_userCurrent; + UASSERT_OBJ(userCurrent, this, "DfgVertex user data used without reserving"); + UASSERT_OBJ(m_userCnt == userCurrent, this, "DfgVertex user data is stale"); +#endif + return *storagep; + } + // Width of result uint32_t width() const { // This is a hot enough function that this is an expensive check, so in debug build only. @@ -412,6 +430,10 @@ public: DfgVertex* verticesNext() const { return m_verticesEnt.nextp(); } DfgVertex* verticesPrev() const { return m_verticesEnt.prevp(); } + // Calls given function 'f' for each source vertex of this vertex + // Unconnected source edges are not iterated. + inline void forEachSource(std::function f); + // Calls given function 'f' for each source vertex of this vertex // Unconnected source edges are not iterated. inline void forEachSource(std::function f) const; @@ -545,6 +567,7 @@ void DfgGraph::addVertex(DfgVertex& vtx) { } else { vtx.m_verticesEnt.pushBack(m_opVertices, &vtx); } + vtx.m_userCnt = 0; vtx.m_graphp = this; } @@ -558,6 +581,7 @@ void DfgGraph::removeVertex(DfgVertex& vtx) { } else { vtx.m_verticesEnt.unlink(m_opVertices, &vtx); } + vtx.m_userCnt = 0; vtx.m_graphp = nullptr; } @@ -588,6 +612,15 @@ void DfgGraph::forEachVertex(std::function f) const { } } +void DfgVertex::forEachSource(std::function f) { + const auto pair = sourceEdges(); + const DfgEdge* const edgesp = pair.first; + const size_t arity = pair.second; + for (size_t i = 0; i < arity; ++i) { + if (DfgVertex* const sourcep = edgesp[i].m_sourcep) f(*sourcep); + } +} + void DfgVertex::forEachSource(std::function f) const { const auto pair = sourceEdges(); const DfgEdge* const edgesp = pair.first; diff --git a/src/V3DfgDecomposition.cpp b/src/V3DfgDecomposition.cpp new file mode 100644 index 000000000..86b6cb46f --- /dev/null +++ b/src/V3DfgDecomposition.cpp @@ -0,0 +1,544 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: DfgGraph decomposition algorithms +// +// 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 +// +//************************************************************************* +// +// Algorithms that take a DfgGraph and decompose it into multiple DfgGraphs. +// +//************************************************************************* + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3Dfg.h" +#include "V3File.h" + +#include +#include +#include + +VL_DEFINE_DEBUG_FUNCTIONS; + +class SplitIntoComponents final { + + // STATE + DfgGraph& m_dfg; // The input graph + const std::string m_prefix; // Component name prefix + std::vector> m_components; // The extracted components + // Component counter - starting from 1 as 0 is the default value used as a marker + size_t m_componentCounter = 1; + + void colorComponents() { + // Work queue for depth first traversal starting from this vertex + std::vector queue; + queue.reserve(m_dfg.size()); + + // any sort of interesting logic must involve a variable, so we only need to iterate them + for (DfgVertexVar *vtxp = m_dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + // If already assigned this vertex to a component, then continue + if (vtxp->user()) continue; + + // Start depth first traversal at this vertex + queue.push_back(vtxp); + + // Depth first traversal + do { + // Pop next work item + DfgVertex& item = *queue.back(); + queue.pop_back(); + + // Move on if already visited + if (item.user()) continue; + + // Assign to current component + item.user() = m_componentCounter; + + // Enqueue all sources and sinks of this vertex. + item.forEachSource([&](DfgVertex& src) { queue.push_back(&src); }); + item.forEachSink([&](DfgVertex& dst) { queue.push_back(&dst); }); + } while (!queue.empty()); + + // Done with this component + ++m_componentCounter; + } + } + + void moveVertices(DfgVertex* headp) { + for (DfgVertex *vtxp = headp, *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + DfgVertex& vtx = *vtxp; + if (const size_t component = vtx.user()) { + m_dfg.removeVertex(vtx); + m_components[component - 1]->addVertex(vtx); + } else { + // This vertex is not connected to a variable and is hence unused, remove here + vtx.unlinkDelete(m_dfg); + } + } + } + + SplitIntoComponents(DfgGraph& dfg, std::string label) + : m_dfg{dfg} + , m_prefix{dfg.name() + (label.empty() ? "" : "-") + label + "-component-"} { + // Component number is stored as DfgVertex::user() + const auto userDataInUse = m_dfg.userDataInUse(); + // Color each component of the graph + colorComponents(); + // Allocate the component graphs + m_components.resize(m_componentCounter - 1); + for (size_t i = 1; i < m_componentCounter; ++i) { + m_components[i - 1].reset(new DfgGraph{*m_dfg.modulep(), m_prefix + cvtToStr(i - 1)}); + } + // Move the vertices to the component graphs + moveVertices(m_dfg.varVerticesBeginp()); + moveVertices(m_dfg.constVerticesBeginp()); + moveVertices(m_dfg.opVerticesBeginp()); + // + UASSERT(m_dfg.size() == 0, "'this' DfgGraph should have been emptied"); + } + +public: + static std::vector> apply(DfgGraph& dfg, const std::string& label) { + return std::move(SplitIntoComponents{dfg, label}.m_components); + } +}; + +std::vector> DfgGraph::splitIntoComponents(std::string label) { + return SplitIntoComponents::apply(*this, label); +} + +class ExtractCyclicComponents final { + static constexpr size_t UNASSIGNED = std::numeric_limits::max(); + + // TYPES + struct VertexState { + size_t index = UNASSIGNED; // Used by Pearce's algorithm for detecting SCCs + size_t component = UNASSIGNED; // Result component number (0 stays in input graph) + bool merged = false; // Visited in the merging pass + VertexState(){}; + }; + + // STATE + + //========================================================================== + // Shared state + + DfgGraph& m_dfg; // The input graph + std::deque m_stateStorage; // Container for VertexState instances + const std::string m_prefix; // Component name prefix + size_t m_nonTrivialSCCs = 0; // Number of non-trivial SCCs in the graph + const bool m_doExpensiveChecks = v3Global.opt.debugCheck(); + + //========================================================================== + // State for Pearce's algorithm for detecting SCCs + + size_t m_index = 0; // Visitation index counter + std::vector m_stack; // The stack used by the algorithm + + //========================================================================== + // State for extraction + + // The extracted cyclic components + std::vector> m_components; + // Map from 'variable vertex' -> 'component index' -> 'clone in that component' + std::unordered_map> m_clones; + + // METHODS + + //========================================================================== + // Shared methods + + VertexState& state(DfgVertex& vtx) const { return *vtx.getUser(); } + + VertexState& allocState(DfgVertex& vtx) { + VertexState*& statep = vtx.user(); + UASSERT_OBJ(!statep, &vtx, "Vertex state already allocated " << cvtToHex(statep)); + m_stateStorage.emplace_back(); + statep = &m_stateStorage.back(); + return *statep; + } + + VertexState& getOrAllocState(DfgVertex& vtx) { + VertexState*& statep = vtx.user(); + if (!statep) { + m_stateStorage.emplace_back(); + statep = &m_stateStorage.back(); + } + return *statep; + } + + //========================================================================== + // Methods for Pearce's algorithm to detect strongly connected components + + void visitColorSCCs(DfgVertex& vtx, VertexState& vtxState) { + UDEBUGONLY(UASSERT_OBJ(vtxState.index == UNASSIGNED, &vtx, "Already visited vertex");); + + // Visiting vertex + const size_t rootIndex = vtxState.index = ++m_index; + + // Visit children + vtx.forEachSink([&](DfgVertex& child) { + VertexState& childSatate = getOrAllocState(child); + // If the child has not yet been visited, then continue traversal + if (childSatate.index == UNASSIGNED) visitColorSCCs(child, childSatate); + // If the child is not in an SCC + if (childSatate.component == UNASSIGNED) { + if (vtxState.index > childSatate.index) vtxState.index = childSatate.index; + } + }); + + if (vtxState.index == rootIndex) { + // This is the 'root' of an SCC + + // A trivial SCC contains only a single vertex + const bool isTrivial = m_stack.empty() || state(*m_stack.back()).index < rootIndex; + // We also need a separate component for vertices that drive themselves (which can + // happen for input like 'assign a = a'), as we want to extract them (they are cyclic). + const bool drivesSelf = vtx.findSink([&vtx](const DfgVertex& sink) { // + return &vtx == &sink; + }); + + if (!isTrivial || drivesSelf) { + // Allocate new component + ++m_nonTrivialSCCs; + vtxState.component = m_nonTrivialSCCs; + while (!m_stack.empty()) { + VertexState& topState = state(*m_stack.back()); + // Only higher nodes belong to the same SCC + if (topState.index < rootIndex) break; + m_stack.pop_back(); + topState.component = m_nonTrivialSCCs; + } + } else { + // Trivial SCC (and does not drive itself), so acyclic. Keep it in original graph. + vtxState.component = 0; + } + } else { + // Not the root of an SCC + m_stack.push_back(&vtx); + } + } + + void colorSCCs() { + // Implements Pearce's algorithm to color the strongly connected components. For reference + // see "An Improved Algorithm for Finding the Strongly Connected Components of a Directed + // Graph", David J.Pearce, 2005. + + // We can leverage some properties of the input graph to gain a bit of speed. Firstly, we + // know constant nodes have no in edges, so they cannot be part of a non-trivial SCC. Mark + // them as such without starting a whole traversal. + for (DfgConst *vtxp = m_dfg.constVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + VertexState& vtxState = allocState(*vtxp); + vtxState.index = 0; + vtxState.component = 0; + } + + // Next, we know that all SCCs must include a variable (as the input graph was converted + // from an AST, we can only have a cycle by going through a variable), so we only start + // traversals through them, and only if we know they have both in and out edges. + for (DfgVertexVar *vtxp = m_dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + if (vtxp->arity() > 0 && vtxp->hasSinks()) { + VertexState& vtxState = getOrAllocState(*vtxp); + // If not yet visited, start a traversal + if (vtxState.index == UNASSIGNED) visitColorSCCs(*vtxp, vtxState); + } else { + VertexState& vtxState = getOrAllocState(*vtxp); + UDEBUGONLY(UASSERT_OBJ(vtxState.index == UNASSIGNED || vtxState.component == 0, + vtxp, "Non circular variable must be in a trivial SCC");); + vtxState.index = 0; + vtxState.component = 0; + } + } + + // Finally, everything we did not visit through the traversal of a variable cannot be in an + // SCC, (otherwise we would have found it from a variable). + for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + VertexState& vtxState = getOrAllocState(*vtxp); + if (vtxState.index == UNASSIGNED) { + vtxState.index = 0; + vtxState.component = 0; + } + } + } + + //========================================================================== + // Methods for merging + + void visitMergeSCCs(DfgVertex& vtx, size_t targetComponent) { + VertexState& vtxState = state(vtx); + + // Move on if already visited + if (vtxState.merged) return; + + // Visiting vertex + vtxState.merged = true; + + // Assign vertex to the target component + vtxState.component = targetComponent; + + // Visit all neighbours. We stop at variable boundaries, + // which is where we will split the graphs + vtx.forEachSource([=](DfgVertex& other) { + if (other.is()) return; + visitMergeSCCs(other, targetComponent); + }); + vtx.forEachSink([=](DfgVertex& other) { + if (other.is()) return; + visitMergeSCCs(other, targetComponent); + }); + } + + void mergeSCCs() { + // Ensure that component boundaries are always at variables, by merging SCCs. Merging stops + // at variable boundaries, so we don't need to iterate variables. Constants are reachable + // from their sinks, or ar unused, so we don't need to iterate them either. + for (DfgVertex *vtxp = m_dfg.opVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + DfgVertex& vtx = *vtxp; + // Start DFS from each vertex that is in a non-trivial SCC, and merge everything + // that is reachable from it into this component. + if (const size_t target = state(vtx).component) visitMergeSCCs(vtx, target); + } + } + + //========================================================================== + // Methods for extraction + + // Retrieve clone of vertex in the given component + DfgVertexVar& getClone(DfgVertexVar& vtx, size_t component) { + UASSERT_OBJ(state(vtx).component != component, &vtx, "Vertex is in that component"); + DfgVertexVar*& clonep = m_clones[&vtx][component]; + if (!clonep) { + if (DfgVarPacked* const pVtxp = vtx.cast()) { + clonep = new DfgVarPacked{m_dfg, pVtxp->varp()}; + } else if (DfgVarArray* const aVtxp = vtx.cast()) { + clonep = new DfgVarArray{m_dfg, aVtxp->varp()}; + } + UASSERT_OBJ(clonep, &vtx, "Unhandled 'DfgVertexVar' sub-type"); + VertexState& cloneStatep = allocState(*clonep); + cloneStatep.component = component; + // We need to mark both the original and the clone as having additional references + vtx.setHasModRefs(); + clonep->setHasModRefs(); + } + return *clonep; + } + + // Fix up non-variable sources of a DfgVertexVar that are in a different component, + // using the provided 'relink' callback + template + void fixSources(T_Vertex& vtx, std::function relink) { + static_assert(std::is_base_of::value, + "'Vertex' must be a 'DfgVertexVar'"); + const size_t component = state(vtx).component; + vtx.forEachSourceEdge([&](DfgEdge& edge, size_t idx) { + DfgVertex& source = *edge.sourcep(); + // DfgVertexVar sources are fixed up by `fixSinks` on those sources + if (source.is()) return; + const size_t sourceComponent = state(source).component; + // Same component is OK + if (sourceComponent == component) return; + // Unlink the source edge (source is reconnected by 'relink' + edge.unlinkSource(); + // Apply the fixup + DfgVertexVar& clone = getClone(vtx, sourceComponent); + relink(*(clone.as()), source, idx); + }); + } + + // Fix up sinks of given variable vertex that are in a different component + void fixSinks(DfgVertexVar& vtx) { + const size_t component = state(vtx).component; + vtx.forEachSinkEdge([&](DfgEdge& edge) { + const size_t sinkComponent = state(*edge.sinkp()).component; + // Same component is OK + if (sinkComponent == component) return; + // Relink the sink to read the clone + edge.relinkSource(&getClone(vtx, sinkComponent)); + }); + } + + // Fix edges that cross components + void fixEdges(DfgVertexVar& vtx) { + if (DfgVarPacked* const vvtxp = vtx.cast()) { + fixSources( + *vvtxp, [&](DfgVarPacked& clone, DfgVertex& driver, size_t driverIdx) { + clone.addDriver(vvtxp->driverFileLine(driverIdx), // + vvtxp->driverLsb(driverIdx), &driver); + }); + fixSinks(*vvtxp); + return; + } + + if (DfgVarArray* const vvtxp = vtx.cast()) { + fixSources( // + *vvtxp, [&](DfgVarArray& clone, DfgVertex& driver, size_t driverIdx) { + clone.addDriver(vvtxp->driverFileLine(driverIdx), // + vvtxp->driverIndex(driverIdx), &driver); + }); + fixSinks(*vvtxp); + return; + } + } + + static void packSources(DfgGraph& dfg) { + // Remove undriven variable sources + for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + if (DfgVarPacked* const varp = vtxp->cast()) { + varp->packSources(); + if (!varp->hasSinks() && varp->arity() == 0) { + VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); + } + return; + } + if (DfgVarArray* const varp = vtxp->cast()) { + varp->packSources(); + if (!varp->hasSinks() && varp->arity() == 0) { + VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); + } + return; + } + } + } + + void moveVertices(DfgVertex* headp) { + for (DfgVertex *vtxp = headp, *nextp; vtxp; vtxp = nextp) { + nextp = vtxp->verticesNext(); + DfgVertex& vtx = *vtxp; + if (const size_t component = state(vtx).component) { + m_dfg.removeVertex(vtx); + m_components[component - 1]->addVertex(vtx); + } + } + } + + void checkEdges(DfgGraph& dfg) const { + // Check that: + // - Edges only cross components at variable boundaries + // - Variable vertex sources are all connected. + dfg.forEachVertex([&](DfgVertex& vtx) { + const size_t component = state(vtx).component; + vtx.forEachSource([&](DfgVertex& src) { + if (src.is()) return; // OK to cross at variables + UASSERT_OBJ(component == state(src).component, &vtx, + "Edge crossing components without variable involvement"); + }); + vtx.forEachSink([&](DfgVertex& snk) { + if (snk.is()) return; // OK to cross at variables + UASSERT_OBJ(component == state(snk).component, &vtx, + "Edge crossing components without variable involvement"); + }); + if (const DfgVertexVar* const vtxp = vtx.cast()) { + vtxp->forEachSourceEdge([](const DfgEdge& edge, size_t) { + UASSERT_OBJ(edge.sourcep(), edge.sinkp(), "Missing source on variable vertex"); + }); + } + }); + } + + void checkGraph(DfgGraph& dfg) const { + // Build set of vertices + std::unordered_set vertices{dfg.size()}; + dfg.forEachVertex([&](const DfgVertex& vtx) { vertices.insert(&vtx); }); + + // Check that each edge connects to a vertex that is within the same graph + dfg.forEachVertex([&](DfgVertex& vtx) { + vtx.forEachSource([&](DfgVertex& src) { + UASSERT_OBJ(vertices.count(&src), &vtx, "Source vertex not in graph"); + }); + vtx.forEachSink([&](DfgVertex& snk) { + UASSERT_OBJ(vertices.count(&snk), &snk, "Sink vertex not in graph"); + }); + }); + } + + void extractComponents() { + // Allocate result graphs + m_components.resize(m_nonTrivialSCCs); + for (size_t i = 0; i < m_nonTrivialSCCs; ++i) { + m_components[i].reset(new DfgGraph{*m_dfg.modulep(), m_prefix + cvtToStr(i)}); + } + + // Fix up edges crossing components (we can only do this at variable boundaries, and the + // earlier merging of components ensured crossing in fact only happen at variable + // boundaries). Note that fixing up the edges can create clones of variables. Clones do + // not need fixing up, so we do not need to iterate them. + DfgVertex* const lastp = m_dfg.varVerticesRbeginp(); + for (DfgVertexVar *vtxp = m_dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { + // It is possible the last vertex (with a nullptr for 'nextp') gets cloned, and hence + // it's 'nextp' would become none nullptr as the clone is added. However, we don't need + // to iterate clones anyway, so it's ok to get the 'nextp' early in the loop. + nextp = vtxp->verticesNext(); + DfgVertexVar& vtx = *vtxp; + // Fix up the edges crossing components + fixEdges(vtx); + // Don't iterate clones added during this loop + if (vtxp == lastp) break; + } + + // Pack sources of variables to remove the now undriven inputs + // (cloning might have unlinked some of the inputs), + packSources(m_dfg); + for (const auto& dfgp : m_components) packSources(*dfgp); + + // Check results for consistency + if (VL_UNLIKELY(m_doExpensiveChecks)) { + checkEdges(m_dfg); + for (const auto& dfgp : m_components) checkEdges(*dfgp); + } + + // Move other vertices to their component graphs + // After this, vertex states are invalid as we moved the vertices + moveVertices(m_dfg.varVerticesBeginp()); + moveVertices(m_dfg.constVerticesBeginp()); + moveVertices(m_dfg.opVerticesBeginp()); + + // Check results for consistency + if (VL_UNLIKELY(m_doExpensiveChecks)) { + checkGraph(m_dfg); + for (const auto& dfgp : m_components) checkGraph(*dfgp); + } + } + + // CONSTRUCTOR - entry point + explicit ExtractCyclicComponents(DfgGraph& dfg, std::string label) + : m_dfg{dfg} + , m_prefix{dfg.name() + (label.empty() ? "" : "-") + label + "-component-"} { + // VertexState is stored as user data + const auto userDataInUse = dfg.userDataInUse(); + // Find all the non-trivial SCCs (and trivial cycles) in the graph + colorSCCs(); + // If the graph was acyclic (which should be the common case), + // there will be no non-trivial SCCs, so we are done. + if (!m_nonTrivialSCCs) return; + // Ensure that component boundaries are always at variables, by merging SCCs + mergeSCCs(); + // Extract the components + extractComponents(); + } + +public: + static std::vector> apply(DfgGraph& dfg, const std::string& label) { + return std::move(ExtractCyclicComponents{dfg, label}.m_components); + } +}; + +std::vector> DfgGraph::extractCyclicComponents(std::string label) { + return ExtractCyclicComponents::apply(*this, label); +} From 8dacbdec3aa797420976683d1f592e5b3c54bcdb Mon Sep 17 00:00:00 2001 From: github action Date: Tue, 11 Oct 2022 09:04:38 +0000 Subject: [PATCH 101/177] Apply 'make format' --- src/V3Dfg.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index b3341ff77..d4cfaff40 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -21,7 +21,6 @@ #include "V3File.h" - VL_DEFINE_DEBUG_FUNCTIONS; //------------------------------------------------------------------------------ From b2070a9407ee5cc7516b3c2552632cee1cf3e7cc Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 11 Oct 2022 10:06:05 +0100 Subject: [PATCH 102/177] Commentary: Mention DFG in changes --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 45bbdb2b2..bdb6af7f9 100644 --- a/Changes +++ b/Changes @@ -19,6 +19,8 @@ Verilator 5.001 devel clocks are now simulated correctly (#3278, #3384). [Geza Lore, Shunyao CAD] * Support timing controls (delays, event controls in any location, wait statements) and forks. See docs for details. [Krzysztof Bieganski, Antmicro Ltd] +* Introduce a new combinational logic optimizer (DFG), that can yield + significant performance improvements on some designs. [Geza Lore, Shunyao CAD] * Add --binary option as alias of --main --exe --build --timing (#3625). From 68927d4fd32fbaf3807ee40cbba7bdef8258bf37 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Thu, 13 Oct 2022 14:33:15 +0200 Subject: [PATCH 103/177] Make class ref typing stricter (#3671) Prevents the possibility of assigning an integer to a class reference, both at the SystemVerilog and the emitted C++ levels. Signed-off-by: Krzysztof Bieganski --- include/verilated_types.h | 21 ++++++++++++++++++--- src/V3EmitCConstInit.h | 4 +++- src/V3EmitCFunc.cpp | 4 +++- src/V3Number.cpp | 13 ++++++++++--- src/V3Width.cpp | 11 +++++++++++ test_regress/t/t_class_assign_bad.out | 17 +++++++++++++++++ test_regress/t/t_class_assign_bad.pl | 19 +++++++++++++++++++ test_regress/t/t_class_assign_bad.v | 21 +++++++++++++++++++++ 8 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 test_regress/t/t_class_assign_bad.out create mode 100755 test_regress/t/t_class_assign_bad.pl create mode 100644 test_regress/t/t_class_assign_bad.v diff --git a/include/verilated_types.h b/include/verilated_types.h index 11132d570..dba797b74 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -1070,6 +1070,13 @@ public: virtual ~VlClass() {} }; +//=================================================================== +// Represents the null pointer. Used for setting VlClassRef to null instead of +// via nullptr_t, to prevent the implicit conversion of 0 to nullptr. +struct VlNull { + operator bool() const { return false; } +}; + //=================================================================== // Verilog class reference container // There are no multithreaded locks on this; the base variable must @@ -1098,14 +1105,16 @@ private: public: // CONSTRUCTORS VlClassRef() = default; + // Init with nullptr + VlClassRef(VlNull){}; template VlClassRef(VlDeleter& deleter, T_Args&&... args) : m_objp{new T_Class{std::forward(args)...}} { m_objp->m_deleter = &deleter; refCountInc(); } - // cppcheck-suppress noExplicitConstructor - VlClassRef(T_Class* objp) + // Explicit to avoid implicit conversion from 0 + explicit VlClassRef(T_Class* objp) : m_objp{objp} { refCountInc(); } @@ -1145,10 +1154,16 @@ public: m_objp = vlstd::exchange(moved.m_objp, nullptr); return *this; } + // Assign with nullptr + VlClassRef& operator=(VlNull) { + refCountDec(); + m_objp = nullptr; + return *this; + } // Dynamic caster template VlClassRef dynamicCast() const { - return dynamic_cast(m_objp); + return VlClassRef{dynamic_cast(m_objp)}; } // Dereference operators T_Class& operator*() const { return *m_objp; } diff --git a/src/V3EmitCConstInit.h b/src/V3EmitCConstInit.h index fbcac4c16..9b4c2b4e9 100644 --- a/src/V3EmitCConstInit.h +++ b/src/V3EmitCConstInit.h @@ -101,7 +101,9 @@ protected: const V3Number& num = nodep->num(); UASSERT_OBJ(!num.isFourState(), nodep, "4-state value in constant pool"); const AstNodeDType* const dtypep = nodep->dtypep(); - if (num.isString()) { + if (num.isNull()) { + puts("VlNull{}"); + } else if (num.isString()) { // Note: putsQuoted does not track indentation, so we use this instead puts("\""); puts(num.toString()); diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 00c2baab2..e17d72150 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -483,7 +483,9 @@ void EmitCFunc::emitCvtWideArray(AstNode* nodep, AstNode* fromp) { void EmitCFunc::emitConstant(AstConst* nodep, AstVarRef* assigntop, const string& assignString) { // Put out constant set to the specified variable, or given variable in a string - if (nodep->num().isFourState()) { + if (nodep->num().isNull()) { + puts("VlNull{}"); + } else if (nodep->num().isFourState()) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: 4-state numbers in this context"); } else if (nodep->num().isString()) { putbs("std::string{"); diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 4d3653f76..5916944a5 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -506,7 +506,6 @@ string V3Number::ascii(bool prefixed, bool cleanVerilog) const { out << "'"; if (bitIs0(0)) { out << '0'; - if (isNull()) out << "[null]"; } else if (bitIs1(0)) { out << '1'; } else if (bitIsZ(0)) { @@ -542,7 +541,13 @@ string V3Number::ascii(bool prefixed, bool cleanVerilog) const { // Always deal with 4 bits at once. Note no 4-state, it's above. out << displayed("%0h"); } - if (isNull() && VL_UNCOVERABLE(!isEqZero())) out << "-%E-null-not-zero"; + if (isNull()) { + if (VL_UNCOVERABLE(!isEqZero())) { + out << "-%E-null-not-zero"; + } else { + out << " [null]"; + } + } return out.str(); } @@ -2169,7 +2174,9 @@ V3Number& V3Number::opAssignNonXZ(const V3Number& lhs, bool ignoreXZ) { // to itself; V3Simulate does this when hits "foo=foo;" // So no: NUM_ASSERT_OP_ARGS1(lhs); if (this != &lhs) { - if (isString()) { + if (VL_UNLIKELY(lhs.isNull())) { + m_data.m_isNull = true; + } else if (isString()) { if (VL_UNLIKELY(!lhs.isString())) { // Non-compatible types, erase value. m_data.str() = ""; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index e7901340a..6717b91e6 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -5086,6 +5086,7 @@ private: // (get an ASSIGN with EXTEND on the lhs instead of rhs) } if (!portp->basicp() || portp->basicp()->isOpaque()) { + checkClassAssign(nodep, "Function Argument", pinp, portp->dtypep()); userIterate(pinp, WidthVP(portp->dtypep(), FINAL).p()); } else { iterateCheckAssign(nodep, "Function Argument", pinp, FINAL, portp->dtypep()); @@ -5803,6 +5804,15 @@ private: return false; // No change } + void checkClassAssign(AstNode* nodep, const char* side, AstNode* rhsp, + AstNodeDType* lhsDTypep) { + if (VN_IS(lhsDTypep, ClassRefDType) && !VN_IS(rhsp->dtypep(), ClassRefDType)) { + if (auto* const constp = VN_CAST(rhsp, Const)) { + if (constp->num().isNull()) return; + } + nodep->v3error(side << " expects a " << lhsDTypep->prettyTypeName()); + } + } static bool similarDTypeRecurse(AstNodeDType* node1p, AstNodeDType* node2p) { return node1p->skipRefp()->similarDType(node2p->skipRefp()); } @@ -5885,6 +5895,7 @@ private: // if (debug()) nodep->dumpTree(cout, "-checkass: "); UASSERT_OBJ(stage == FINAL, nodep, "Bad width call"); // We iterate and size the RHS based on the result of RHS evaluation + checkClassAssign(nodep, side, rhsp, lhsDTypep); const bool lhsStream = (VN_IS(nodep, NodeAssign) && VN_IS(VN_AS(nodep, NodeAssign)->lhsp(), NodeStream)); rhsp = iterateCheck(nodep, side, rhsp, ASSIGN, FINAL, lhsDTypep, diff --git a/test_regress/t/t_class_assign_bad.out b/test_regress/t/t_class_assign_bad.out new file mode 100644 index 000000000..f93dffe76 --- /dev/null +++ b/test_regress/t/t_class_assign_bad.out @@ -0,0 +1,17 @@ +%Error: t/t_class_assign_bad.v:16:9: Assign RHS expects a CLASSREFDTYPE 'Cls' + : ... In instance t + 16 | c = 0; + | ^ +%Error: t/t_class_assign_bad.v:17:9: Assign RHS expects a CLASSREFDTYPE 'Cls' + : ... In instance t + 17 | c = 1; + | ^ +%Error: t/t_class_assign_bad.v:18:7: Function Argument expects a CLASSREFDTYPE 'Cls' + : ... In instance t + 18 | t(0); + | ^ +%Error: t/t_class_assign_bad.v:19:7: Function Argument expects a CLASSREFDTYPE 'Cls' + : ... In instance t + 19 | t(1); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_class_assign_bad.pl b/test_regress/t/t_class_assign_bad.pl new file mode 100755 index 000000000..b59a5c675 --- /dev/null +++ b/test_regress/t/t_class_assign_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +compile( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_class_assign_bad.v b/test_regress/t/t_class_assign_bad.v new file mode 100644 index 000000000..97b7b9f46 --- /dev/null +++ b/test_regress/t/t_class_assign_bad.v @@ -0,0 +1,21 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +class Cls; +endclass : Cls + +module t (/*AUTOARG*/); + Cls c; + + task t(Cls c); endtask + + initial begin + c = 0; + c = 1; + t(0); + t(1); + end +endmodule From caed0865165857cb5d5c4560a35da2865116556e Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Thu, 13 Oct 2022 21:04:43 +0200 Subject: [PATCH 104/177] Move Postponed logic after the eval loop (#3673) Signed-off-by: Krzysztof Bieganski --- src/V3Active.cpp | 23 ++++++++++++++-------- src/V3Assert.cpp | 2 +- src/V3AstNodeOther.h | 2 +- src/V3Order.cpp | 21 +------------------- src/V3Sched.cpp | 25 +++++++++++++++++++++--- src/V3Sched.h | 1 + test_regress/t/t_past_strobe.out | 1 - test_regress/t/t_past_strobe.v | 2 +- test_regress/t/t_timing_strobe.out | 4 ++++ test_regress/t/t_timing_strobe.pl | 29 ++++++++++++++++++++++++++++ test_regress/t/t_timing_strobe.v | 31 ++++++++++++++++++++++++++++++ 11 files changed, 106 insertions(+), 35 deletions(-) create mode 100644 test_regress/t/t_timing_strobe.out create mode 100755 test_regress/t/t_timing_strobe.pl create mode 100644 test_regress/t/t_timing_strobe.v diff --git a/src/V3Active.cpp b/src/V3Active.cpp index f753bfcd7..0d0b0e3b4 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -243,16 +243,21 @@ public: // METHODS AstScope* scopep() { return m_scopep; } - // Return an AstActive sensitive to the given special sensitivity class + // Make a new AstActive sensitive to the given special sensitivity class and return it + template + AstActive* makeSpecialActive(FileLine* const fl) { + AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, SenItemKind{}}}; + auto* const activep = new AstActive{fl, "", senTreep}; + activep->sensesStorep(activep->sensesp()); + addActive(activep); + return activep; + } + + // Return an AstActive sensitive to the given special sensitivity class (possibly pre-created) template AstActive* getSpecialActive(FileLine* fl) { AstActive*& cachep = getSpecialActive(); - if (!cachep) { - AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, SenItemKind{}}}; - cachep = new AstActive{fl, "", senTreep}; - cachep->sensesStorep(cachep->sensesp()); - addActive(cachep); - } + if (!cachep) cachep = makeSpecialActive(fl); return cachep; } @@ -535,7 +540,9 @@ private: // Might be empty with later optimizations, so this assertion can be removed, // but for now it is guaranteed to be not empty. UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not be empty"); - visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS); + // Make a new active for it, needs to be the only item under the active for V3Sched + AstActive* const activep = m_namer.makeSpecialActive(nodep->fileline()); + activep->addStmtsp(nodep->unlinkFrBack()); } void visit(AstAlwaysPublic* nodep) override { visitAlways(nodep, nodep->sensesp(), VAlwaysKwd::ALWAYS); diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 97ced7b6b..a499aaf7b 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -426,7 +426,7 @@ private: newMonitorNumVarRefp(nodep, VAccess::READ)}}, stmtsp}; ifp->branchPred(VBranchPred::BP_UNLIKELY); - AstNode* const newp = new AstAlwaysPostponed{fl, ifp}; + AstNode* const newp = new AstAlways{fl, VAlwaysKwd::ALWAYS, nullptr, ifp}; m_modp->addStmtsp(newp); } else if (nodep->displayType() == VDisplayType::DT_STROBE) { nodep->displayType(VDisplayType::DT_DISPLAY); diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 3ef73b94a..9a9ea6d4c 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2649,7 +2649,7 @@ public: ASTGEN_MEMBERS_AstAlwaysPost; }; class AstAlwaysPostponed final : public AstNodeProcedure { - // Like always but postponement scheduling region + // Like always but Postponed scheduling region public: AstAlwaysPostponed(FileLine* fl, AstNode* stmtsp) diff --git a/src/V3Order.cpp b/src/V3Order.cpp index ea2313c48..80c147a40 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -181,7 +181,6 @@ class OrderBuildVisitor final : public VNVisitor { bool m_inClocked = false; // Underneath clocked AstActive bool m_inPre = false; // Underneath AstAssignPre bool m_inPost = false; // Underneath AstAssignPost/AstAlwaysPost - bool m_inPostponed = false; // Underneath AstAlwaysPostponed std::function m_readTriggersCombLogic; // METHODS @@ -265,19 +264,7 @@ class OrderBuildVisitor final : public VNVisitor { const bool prevCon = varscp->user2() & VU_CON; // Compute whether the variable is produced (written) here - bool gen = false; - if (!prevGen && nodep->access().isWriteOrRW()) { - gen = true; - if (m_inPostponed) { - // IEEE 1800-2017 (4.2.9) forbids any value updates in the postponed region, but - // Verilator generated trigger signals for $strobe are cleared after the - // display is executed. This is both safe to ignore (because their single read - // is in the same AstAlwaysPostponed, just prior to the clear), and is - // necessary to ignore to avoid a circular logic (UNOPTFLAT) warning. - UASSERT_OBJ(prevCon, nodep, "Should have been consumed in same process"); - gen = false; - } - } + bool gen = !prevGen && nodep->access().isWriteOrRW(); // Compute whether the value is consumed (read) here bool con = false; @@ -398,12 +385,6 @@ class OrderBuildVisitor final : public VNVisitor { iterateLogic(nodep); m_inPost = false; } - void visit(AstAlwaysPostponed* nodep) override { - UASSERT_OBJ(!m_inPostponed, nodep, "Should not nest"); - m_inPostponed = true; - iterateLogic(nodep); - m_inPostponed = false; - } void visit(AstFinal* nodep) override { // LCOV_EXCL_START nodep->v3fatalSrc("AstFinal should not need ordering"); } // LCOV_EXCL_STOP diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index ebf0dfea9..b34133c77 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -184,7 +184,11 @@ LogicClasses gatherLogicClasses(AstNetlist* netlistp) { } else if (senTreep->hasCombo()) { UASSERT_OBJ(!senTreep->sensesp()->nextp(), activep, "combinational logic with additional sensitivities"); - result.m_comb.emplace_back(scopep, activep); + if (VN_IS(activep->stmtsp(), AlwaysPostponed)) { + result.m_postponed.emplace_back(scopep, activep); + } else { + result.m_comb.emplace_back(scopep, activep); + } } else { UASSERT_OBJ(senTreep->hasClocked(), activep, "What else could it be?"); result.m_clocked.emplace_back(scopep, activep); @@ -267,6 +271,14 @@ AstCFunc* createInitial(AstNetlist* netlistp, const LogicClasses& logicClasses) return funcp; // Not splitting yet as it is not final } +AstCFunc* createPostponed(AstNetlist* netlistp, const LogicClasses& logicClasses) { + if (logicClasses.m_postponed.empty()) return nullptr; + AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_postponed", /* slow: */ true); + orderSequentially(funcp, logicClasses.m_postponed); + splitCheck(funcp); + return funcp; +} + void createFinal(AstNetlist* netlistp, const LogicClasses& logicClasses) { AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_final", /* slow: */ true); orderSequentially(funcp, logicClasses.m_final); @@ -738,6 +750,7 @@ void createEval(AstNetlist* netlistp, // AstVarScope* nbaTrigsp, // AstCFunc* actFuncp, // AstCFunc* nbaFuncp, // + AstCFunc* postponedFuncp, // TimingKit& timingKit // ) { FileLine* const flp = netlistp->fileline(); @@ -836,6 +849,9 @@ void createEval(AstNetlist* netlistp, // // Add the NBA eval loop funcp->addStmtsp(nbaEvalLoopp); + + // Add the Postponed eval call + if (postponedFuncp) funcp->addStmtsp(new AstCCall{flp, postponedFuncp}); } } // namespace @@ -1025,9 +1041,12 @@ void schedule(AstNetlist* netlistp) { netlistp->evalNbap(nbaFuncp); // Remember for V3LifePost if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-nba"); - // Step 11: Bolt it all together to create the '_eval' function + // Step 11: Create the 'postponed' region evaluation function + auto* const postponedFuncp = createPostponed(netlistp, logicClasses); + + // Step 12: Bolt it all together to create the '_eval' function createEval(netlistp, icoLoopp, actTrig, preTrigVscp, nbaTrigVscp, actFuncp, nbaFuncp, - timingKit); + postponedFuncp, timingKit); transformForks(netlistp); diff --git a/src/V3Sched.h b/src/V3Sched.h index 876b0a510..9846ada1d 100644 --- a/src/V3Sched.h +++ b/src/V3Sched.h @@ -81,6 +81,7 @@ struct LogicClasses final { 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) + LogicByScope m_postponed; // Postponed logic ($strobe) LogicClasses() = default; VL_UNCOPYABLE(LogicClasses); diff --git a/test_regress/t/t_past_strobe.out b/test_regress/t/t_past_strobe.out index 9df6ce6ca..1f96befeb 100644 --- a/test_regress/t/t_past_strobe.out +++ b/test_regress/t/t_past_strobe.out @@ -8,4 +8,3 @@ 8 == 8, 7 == 7 9 == 9, 8 == 8 *-* All Finished *-* -10 == 10, 9 == 9 diff --git a/test_regress/t/t_past_strobe.v b/test_regress/t/t_past_strobe.v index b7e58466a..4ccbce987 100644 --- a/test_regress/t/t_past_strobe.v +++ b/test_regress/t/t_past_strobe.v @@ -38,7 +38,7 @@ module Test1( input [3:0] a, b; always @(posedge clk) begin - $strobe("%0d == %0d, %0d == %0d", a, b, $past(a), $past(b)); + if (a < 9) $strobe("%0d == %0d, %0d == %0d", a, b, $past(a), $past(b)); end endmodule diff --git a/test_regress/t/t_timing_strobe.out b/test_regress/t/t_timing_strobe.out new file mode 100644 index 000000000..01a73482a --- /dev/null +++ b/test_regress/t/t_timing_strobe.out @@ -0,0 +1,4 @@ +v = 1 +v = 2 +v = 3 +*-* All Finished *-* diff --git a/test_regress/t/t_timing_strobe.pl b/test_regress/t/t_timing_strobe.pl new file mode 100755 index 000000000..4ab3c9652 --- /dev/null +++ b/test_regress/t/t_timing_strobe.pl @@ -0,0 +1,29 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_timing_strobe.v b/test_regress/t/t_timing_strobe.v new file mode 100644 index 000000000..8c1057fb4 --- /dev/null +++ b/test_regress/t/t_timing_strobe.v @@ -0,0 +1,31 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + event e1; + event e2; + int v = 0; + + initial begin + #1 $strobe("v = %0d", v); ->e1; + @e2 $strobe("v = %0d", v); ->e1; + @e2 $strobe("v = %0d", v); ->e1; + @e2 $write("*-* All Finished *-*\n"); + $finish; + end + + initial begin + @e1 v = 1; #1 ->e2; + @e1 v = 2; #1 ->e2; + @e1 v = 3; #1 ->e2; + end + + initial #5 $stop; // timeout +endmodule From 8a347248f5f192ac4a99b794a8269eccd39eab56 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Fri, 14 Oct 2022 09:35:26 +0200 Subject: [PATCH 105/177] Use `AstDelay` nodes for intra-assignment delays (#3672) Also fix messy implementation of net delays. Signed-off-by: Krzysztof Bieganski --- docs/gen/ex_STMTDLY_msg.rst | 2 +- src/V3Active.cpp | 4 +-- src/V3AstNodeOther.h | 11 ++++-- src/V3AstNodes.cpp | 3 +- src/V3Timing.cpp | 42 ++++++++++++++--------- src/verilog.y | 29 ++++++++++------ test_regress/t/t_delay_stmtdly_bad.out | 20 +++++------ test_regress/t/t_lint_stmtdly_bad.out | 4 +-- test_regress/t/t_net_delay.out | 12 +++---- test_regress/t/t_notiming.out | 12 +++---- test_regress/t/t_notiming_off.out | 16 ++++----- test_regress/t/t_timing_func_bad.out | 12 +++---- test_regress/t/t_timing_unset1.out | 16 ++++----- test_regress/t/t_timing_unset2.out | 16 ++++----- test_regress/t/t_timing_zerodly_unsup.out | 4 +-- test_regress/t/t_wait.out | 20 +++++------ 16 files changed, 119 insertions(+), 104 deletions(-) diff --git a/docs/gen/ex_STMTDLY_msg.rst b/docs/gen/ex_STMTDLY_msg.rst index 3b7c7bc69..15ceefa58 100644 --- a/docs/gen/ex_STMTDLY_msg.rst +++ b/docs/gen/ex_STMTDLY_msg.rst @@ -1,4 +1,4 @@ .. comment: generated by t_lint_stmtdly_bad .. code-block:: - %Warning-STMTDLY: example.v:1:8 Ignoring delay on this statement due to --no-timing + %Warning-STMTDLY: example.v:1:7 Ignoring delay on this statement due to --no-timing diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 0d0b0e3b4..8c804b92a 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -525,9 +525,7 @@ private: } void visit(AstAssignAlias* nodep) override { moveUnderSpecial(nodep); } void visit(AstCoverToggle* nodep) override { moveUnderSpecial(nodep); } - void visit(AstAssignW* nodep) override { - visitAlways(nodep, nullptr, VAlwaysKwd::ALWAYS_COMB); - } + void visit(AstAssignW* nodep) override { moveUnderSpecial(nodep); } void visit(AstAlways* nodep) override { if (!nodep->stmtsp()) { // Empty always. Remove it now. VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 9a9ea6d4c..618a33533 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1971,7 +1971,7 @@ class AstVar final : public AstNode { // A variable (in/out/wire/reg/param) inside a module // // @astgen op1 := childDTypep : Optional[AstNodeDType] - // @astgen op2 := delayp : Optional[AstNode] // Net delay + // @astgen op2 := delayp : Optional[AstDelay] // Net delay // Initial value that never changes (static const), or constructor argument for // MTASKSTATE variables // @astgen op3 := valuep : Optional[AstNode] @@ -3023,10 +3023,9 @@ class AstDelay final : public AstNodeStmt { // @astgen op1 := lhsp : AstNode // Delay value // @astgen op2 := stmtsp : List[AstNode] // Statements under delay public: - AstDelay(FileLine* fl, AstNode* lhsp, AstNode* stmtsp) + AstDelay(FileLine* fl, AstNode* lhsp) : ASTGEN_SUPER_Delay(fl) { this->lhsp(lhsp); - this->addStmtsp(stmtsp); } ASTGEN_MEMBERS_AstDelay; bool isTimingControl() const override { return true; } @@ -3827,6 +3826,12 @@ public: AstNode* const controlp = timingControlp() ? timingControlp()->cloneTree(false) : nullptr; return new AstAssignW{fileline(), lhsp, rhsp, controlp}; } + bool isTimingControl() const override { + return timingControlp() + || lhsp()->exists([](const AstNodeVarRef* const refp) { + return refp->access().isWriteOrRW() && refp->varp()->delayp(); + }); + } bool brokeLhsMustBeLvalue() const override { return true; } AstAlways* convertToAlways(); }; diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 5ea8c44c6..9758a9e58 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2243,12 +2243,13 @@ void AstCUse::dump(std::ostream& str) const { } AstAlways* AstAssignW::convertToAlways() { + const bool hasTimingControl = isTimingControl(); AstNode* const lhs1p = lhsp()->unlinkFrBack(); AstNode* const rhs1p = rhsp()->unlinkFrBack(); AstNode* const controlp = timingControlp() ? timingControlp()->unlinkFrBack() : nullptr; FileLine* const flp = fileline(); AstNode* bodysp = new AstAssign{flp, lhs1p, rhs1p, controlp}; - if (controlp) { + if (hasTimingControl) { // If there's a timing control, put the assignment in a fork..join_none. This process won't // get marked as suspendable and thus will be scheduled normally auto* forkp = new AstFork{flp, "", bodysp}; diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 6f60cd266..a7f278122 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -135,14 +135,17 @@ private: return nodep->user3u().to(); } // Find net delay on the LHS of an assignment - AstNode* getLhsNetDelay(AstNodeAssign* nodep) const { + AstDelay* getLhsNetDelay(AstNodeAssign* nodep) const { bool foundWrite = false; - AstNode* delayp = nullptr; + AstDelay* delayp = nullptr; nodep->lhsp()->foreach([&](const AstNodeVarRef* const refp) { if (!refp->access().isWriteOrRW()) return; UASSERT_OBJ(!foundWrite, nodep, "Should only be one variable written to on the LHS"); foundWrite = true; - if (refp->varp()->delayp()) delayp = refp->varp()->delayp()->cloneTree(false); + if (refp->varp()->delayp()) { + delayp = refp->varp()->delayp(); + delayp->unlinkFrBack(); + } }); return delayp; } @@ -150,20 +153,25 @@ private: // assignment under it AstNodeStmt* factorOutTimingControl(AstNodeAssign* nodep) const { AstNodeStmt* stmtp = nodep; - AstNode* delayp = getLhsNetDelay(nodep); + AstDelay* delayp = getLhsNetDelay(nodep); FileLine* const flp = nodep->fileline(); AstNode* const controlp = nodep->timingControlp(); if (controlp) { controlp->unlinkFrBack(); - if (!VN_IS(controlp, SenTree)) { - delayp = delayp ? new AstAdd{flp, delayp, controlp} : controlp; + if (auto* const assignDelayp = VN_CAST(controlp, Delay)) { + if (delayp) { + delayp->lhsp(new AstAdd{flp, delayp->lhsp()->unlinkFrBack(), + assignDelayp->lhsp()->unlinkFrBack()}); + VL_DO_DANGLING(assignDelayp->deleteTree(), nodep); + } else { + delayp = assignDelayp; + } } } if (delayp) { - auto* const delayStmtp = new AstDelay{flp, delayp, nullptr}; - stmtp->replaceWith(delayStmtp); - delayStmtp->addStmtsp(stmtp); - stmtp = delayStmtp; + stmtp->replaceWith(delayp); + delayp->addStmtsp(stmtp); + stmtp = delayp; } if (auto* const sensesp = VN_CAST(controlp, SenTree)) { auto* const eventControlp = new AstEventControl{flp, sensesp, nullptr}; @@ -493,7 +501,6 @@ private: VL_DO_DANGLING(nodep->deleteTree(), nodep); } void visit(AstNodeAssign* nodep) override { - iterateChildren(nodep); // Only process once to avoid infinite loops (due to the net delay) if (nodep->user1SetOnce()) return; AstNode* const controlp = factorOutTimingControl(nodep); @@ -536,14 +543,14 @@ private: replaceWithIntermediate(nodep->rhsp(), m_intraValueNames.get(nodep)); } void visit(AstAssignW* nodep) override { - iterateChildren(nodep); - auto* const netDelayp = getLhsNetDelay(nodep); + AstDelay* const netDelayp = getLhsNetDelay(nodep); if (!netDelayp && !nodep->timingControlp()) return; // This assignment will be converted to an always. In some cases this may generate an - // UNOPTFLAT, e.g.: assign #1 clk = ~clk. We create a temp var for the LHS of this assign, - // to disable the UNOPTFLAT warning for it. - // TODO: Find a way to do this without introducing this var. Perhaps make V3SchedAcyclic - // recognize awaits and prevent it from treating this kind of logic as cyclic + // UNOPTFLAT, e.g.: assign #1 clk = ~clk. We create a temp var for the LHS of this + // assign, to disable the UNOPTFLAT warning for it. + // TODO: Find a way to do this without introducing this var. Perhaps make + // V3SchedAcyclic recognize awaits and prevent it from treating this kind of logic as + // cyclic AstNode* const lhsp = nodep->lhsp()->unlinkFrBack(); std::string varname; if (auto* const refp = VN_CAST(lhsp, VarRef)) { @@ -631,6 +638,7 @@ private: //-------------------- void visit(AstNodeMath*) override {} // Accelerate + void visit(AstVar*) override {} void visit(AstNode* nodep) override { iterateChildren(nodep); } public: diff --git a/src/verilog.y b/src/verilog.y index 86ecb7604..47ea44310 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -85,7 +85,7 @@ public: AstCase* m_caseAttrp = nullptr; // Current case statement for attribute adding AstNodeDType* m_varDTypep = nullptr; // Pointer to data type for next signal declaration AstNodeDType* m_memDTypep = nullptr; // Pointer to data type for next member declaration - AstNode* m_netDelayp = nullptr; // Pointer to delay for next signal declaration + AstDelay* m_netDelayp = nullptr; // Pointer to delay for next signal declaration AstStrengthSpec* m_netStrengthp = nullptr; // Pointer to strength for next net declaration AstNodeModule* m_modp = nullptr; // Last module for timeunits bool m_pinAnsi = false; // In ANSI port list @@ -190,7 +190,7 @@ public: if (m_varDTypep) VL_DO_CLEAR(m_varDTypep->deleteTree(), m_varDTypep = nullptr); m_varDTypep = dtypep; } - void setNetDelay(AstNode* netDelayp) { m_netDelayp = netDelayp; } + void setNetDelay(AstDelay* netDelayp) { m_netDelayp = netDelayp; } void setNetStrength(AstStrengthSpec* netStrengthp) { m_netStrengthp = netStrengthp; } void pinPush() { m_pinStack.push(m_pinNum); @@ -1082,6 +1082,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"}) // Blank lines for type insertion // Blank lines for type insertion // Blank lines for type insertion +// Blank lines for type insertion %start source_text @@ -2736,16 +2737,20 @@ delay_or_event_controlE: // IEEE: delay_or_event_control plus empty //UNSUP | yREPEAT '(' expr ')' event_control { } ; -delay_controlE: +delay_controlE: /* empty */ { $$ = nullptr; } | delay_control { $$ = $1; } ; -delay_control: //== IEEE: delay_control - '#' delay_value { $$ = $2; } - | '#' '(' minTypMax ')' { $$ = $3; } - | '#' '(' minTypMax ',' minTypMax ')' { $$ = $3; RISEFALLDLYUNSUP($3); DEL($5); } - | '#' '(' minTypMax ',' minTypMax ',' minTypMax ')' { $$ = $3; RISEFALLDLYUNSUP($3); DEL($5); DEL($7); } +delay_control: //== IEEE: delay_control + '#' delay_value + { $$ = new AstDelay{$1, $2}; } + | '#' '(' minTypMax ')' + { $$ = new AstDelay{$1, $3}; } + | '#' '(' minTypMax ',' minTypMax ')' + { $$ = new AstDelay{$1, $3}; RISEFALLDLYUNSUP($3); DEL($5); } + | '#' '(' minTypMax ',' minTypMax ',' minTypMax ')' + { $$ = new AstDelay{$1, $3}; RISEFALLDLYUNSUP($3); DEL($5); DEL($7); } ; delay_value: // ==IEEE:delay_value @@ -2777,7 +2782,6 @@ netSig: // IEEE: net_decl_assignment - one element from { $$ = VARDONEA($1, *$1, nullptr, $2); auto* const assignp = new AstAssignW{$3, new AstVarRef{$1, *$1, VAccess::WRITE}, $4}; if (GRAMMARP->m_netStrengthp) assignp->strengthSpecp(GRAMMARP->m_netStrengthp->cloneTree(false)); - if ($$->delayp()) assignp->timingControlp($$->delayp()->unlinkFrBack()); // IEEE 1800-2017 10.3.3 AstNode::addNext($$, assignp); } | netId variable_dimensionList sigAttrListE { $$ = VARDONEA($1,*$1, $2, $3); } @@ -3320,8 +3324,11 @@ statement_item: // IEEE: statement_item | par_block { $$ = $1; } // // IEEE: procedural_timing_control_statement + procedural_timing_control | delay_control stmtBlock { AstNode* nextp = nullptr; - if ($2 && $2->nextp()) nextp = $2->nextp()->unlinkFrBackWithNext(); - $$ = new AstDelay{$1->fileline(), $1, $2}; + if ($2) { + if ($2->nextp()) nextp = $2->nextp()->unlinkFrBackWithNext(); + $1->addStmtsp($2); + } + $$ = $1; addNextNull($$, nextp); } | event_control stmtBlock { AstNode* nextp = nullptr; if ($2 && $2->nextp()) nextp = $2->nextp()->unlinkFrBackWithNext(); diff --git a/test_regress/t/t_delay_stmtdly_bad.out b/test_regress/t/t_delay_stmtdly_bad.out index 90327f0d8..8d13754c8 100644 --- a/test_regress/t/t_delay_stmtdly_bad.out +++ b/test_regress/t/t_delay_stmtdly_bad.out @@ -1,25 +1,25 @@ -%Warning-ASSIGNDLY: t/t_delay.v:24:13: Ignoring timing control on this assignment/primitive due to --no-timing +%Warning-ASSIGNDLY: t/t_delay.v:24:11: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t 24 | assign #(1.2000000000000000) dly1 = dly0 + 32'h1; - | ^~~~~~~~~~~~~~~~~~ + | ^ ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-ASSIGNDLY: t/t_delay.v:29:19: Ignoring timing control on this assignment/primitive due to --no-timing +%Warning-ASSIGNDLY: t/t_delay.v:29:18: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t 29 | dly0 <= #0 32'h11; - | ^ -%Warning-ASSIGNDLY: t/t_delay.v:32:19: Ignoring timing control on this assignment/primitive due to --no-timing + | ^ +%Warning-ASSIGNDLY: t/t_delay.v:32:18: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t 32 | dly0 <= #0.12 dly0 + 32'h12; - | ^~~~ -%Warning-ASSIGNDLY: t/t_delay.v:40:26: Ignoring timing control on this assignment/primitive due to --no-timing + | ^ +%Warning-ASSIGNDLY: t/t_delay.v:40:18: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t 40 | dly0 <= #(dly_s.dly) 32'h55; - | ^~~ -%Warning-STMTDLY: t/t_delay.v:45:11: Ignoring delay on this statement due to --no-timing + | ^ +%Warning-STMTDLY: t/t_delay.v:45:10: Ignoring delay on this statement due to --no-timing : ... In instance t 45 | #100 $finish; - | ^~~ + | ^ %Warning-UNUSED: t/t_delay.v:22:12: Signal is not used: 'dly_s' : ... In instance t 22 | dly_s_t dly_s; diff --git a/test_regress/t/t_lint_stmtdly_bad.out b/test_regress/t/t_lint_stmtdly_bad.out index b47faf8ef..c91e37476 100644 --- a/test_regress/t/t_lint_stmtdly_bad.out +++ b/test_regress/t/t_lint_stmtdly_bad.out @@ -1,7 +1,7 @@ -%Warning-STMTDLY: t/t_lint_stmtdly_bad.v:10:8: Ignoring delay on this statement due to --no-timing +%Warning-STMTDLY: t/t_lint_stmtdly_bad.v:10:7: Ignoring delay on this statement due to --no-timing : ... In instance t 10 | #100 $finish; - | ^~~ + | ^ ... For warning description see https://verilator.org/warn/STMTDLY?v=latest ... Use "/* verilator lint_off STMTDLY */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_net_delay.out b/test_regress/t/t_net_delay.out index 1b79c6bac..b11072c2e 100644 --- a/test_regress/t/t_net_delay.out +++ b/test_regress/t/t_net_delay.out @@ -1,11 +1,7 @@ -%Warning-ASSIGNDLY: t/t_net_delay.v:13:15: Ignoring timing control on this assignment/primitive due to --no-timing - : ... In instance t - 13 | wire[3:0] #4 val1 = cyc; - | ^ - ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest - ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-ASSIGNDLY: t/t_net_delay.v:17:12: Ignoring timing control on this assignment/primitive due to --no-timing +%Warning-ASSIGNDLY: t/t_net_delay.v:17:11: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t 17 | assign #4 val2 = cyc; - | ^ + | ^ + ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest + ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_notiming.out b/test_regress/t/t_notiming.out index cb2b6017c..b625df546 100644 --- a/test_regress/t/t_notiming.out +++ b/test_regress/t/t_notiming.out @@ -1,7 +1,7 @@ -%Warning-STMTDLY: t/t_notiming.v:12:9: Ignoring delay on this statement due to --no-timing +%Warning-STMTDLY: t/t_notiming.v:12:8: Ignoring delay on this statement due to --no-timing : ... In instance t 12 | #1 - | ^ + | ^ ... For warning description see https://verilator.org/warn/STMTDLY?v=latest ... Use "/* verilator lint_off STMTDLY */" and lint_on around source to disable this message. %Error-NOTIMING: t/t_notiming.v:13:8: Fork statements require --timing @@ -22,12 +22,12 @@ : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure 19 | @e | ^ -%Warning-STMTDLY: t/t_notiming.v:26:13: Ignoring delay on this statement due to --no-timing +%Warning-STMTDLY: t/t_notiming.v:26:12: Ignoring delay on this statement due to --no-timing : ... In instance t 26 | initial #1 ->e; - | ^ -%Warning-STMTDLY: t/t_notiming.v:27:13: Ignoring delay on this statement due to --no-timing + | ^ +%Warning-STMTDLY: t/t_notiming.v:27:12: Ignoring delay on this statement due to --no-timing : ... In instance t 27 | initial #2 $stop; - | ^ + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_notiming_off.out b/test_regress/t/t_notiming_off.out index fc269af87..72c86fd65 100644 --- a/test_regress/t/t_notiming_off.out +++ b/test_regress/t/t_notiming_off.out @@ -4,26 +4,26 @@ 25 | @e1; | ^ ... For error description see https://verilator.org/warn/NOTIMING?v=latest -%Warning-STMTDLY: t/t_timing_off.v:33:13: Ignoring delay on this statement due to --no-timing +%Warning-STMTDLY: t/t_timing_off.v:33:12: Ignoring delay on this statement due to --no-timing : ... In instance t 33 | initial #2 ->e1; - | ^ + | ^ ... Use "/* verilator lint_off STMTDLY */" and lint_on around source to disable this message. -%Warning-STMTDLY: t/t_timing_off.v:37:13: Ignoring delay on this statement due to --no-timing +%Warning-STMTDLY: t/t_timing_off.v:37:12: Ignoring delay on this statement due to --no-timing : ... In instance t 37 | initial #3 $stop; - | ^ -%Warning-STMTDLY: t/t_timing_off.v:38:13: Ignoring delay on this statement due to --no-timing + | ^ +%Warning-STMTDLY: t/t_timing_off.v:38:12: Ignoring delay on this statement due to --no-timing : ... In instance t 38 | initial #1 @(e1, e2) #1 $stop; - | ^ + | ^ %Error-NOTIMING: t/t_timing_off.v:38:15: Event control statement in this location requires --timing : ... In instance t : ... With --no-timing, suggest have one event control statement per procedure, at the top of the procedure 38 | initial #1 @(e1, e2) #1 $stop; | ^ -%Warning-STMTDLY: t/t_timing_off.v:38:26: Ignoring delay on this statement due to --no-timing +%Warning-STMTDLY: t/t_timing_off.v:38:25: Ignoring delay on this statement due to --no-timing : ... In instance t 38 | initial #1 @(e1, e2) #1 $stop; - | ^ + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_timing_func_bad.out b/test_regress/t/t_timing_func_bad.out index 8bf343ea8..c3fb3c88e 100644 --- a/test_regress/t/t_timing_func_bad.out +++ b/test_regress/t/t_timing_func_bad.out @@ -1,11 +1,11 @@ -%Error: t/t_timing_func_bad.v:10:8: Delays are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) +%Error: t/t_timing_func_bad.v:10:7: Delays are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) : ... In instance t 10 | #1 $stop; - | ^ -%Error: t/t_timing_func_bad.v:15:13: Timing controls are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) + | ^ +%Error: t/t_timing_func_bad.v:15:12: Timing controls are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) : ... In instance t 15 | f2 = #5 0; $stop; - | ^ + | ^ %Error: t/t_timing_func_bad.v:20:7: Event controls are not legal in functions. Suggest use a task (IEEE 1800-2017 13.4.4) : ... In instance t 20 | @e $stop; @@ -18,8 +18,8 @@ : ... In instance t 31 | wait(i == 0) $stop; | ^~~~ -%Error: t/t_timing_func_bad.v:42:8: Delays are not legal in final blocks (IEEE 1800-2017 9.2.3) +%Error: t/t_timing_func_bad.v:42:7: Delays are not legal in final blocks (IEEE 1800-2017 9.2.3) : ... In instance t 42 | #1; - | ^ + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_timing_unset1.out b/test_regress/t/t_timing_unset1.out index 4f3b14171..c6bcbb64a 100644 --- a/test_regress/t/t_timing_unset1.out +++ b/test_regress/t/t_timing_unset1.out @@ -1,7 +1,7 @@ -%Error-NEEDTIMINGOPT: t/t_notiming.v:12:9: Use --timing or --no-timing to specify how delays should be handled +%Error-NEEDTIMINGOPT: t/t_notiming.v:12:8: Use --timing or --no-timing to specify how delays should be handled : ... In instance t 12 | #1 - | ^ + | ^ ... For error description see https://verilator.org/warn/NEEDTIMINGOPT?v=latest %Error-NEEDTIMINGOPT: t/t_notiming.v:13:8: Use --timing or --no-timing to specify how forks should be handled : ... In instance t @@ -15,20 +15,20 @@ : ... In instance t 15 | wait(x == 4) | ^~~~ -%Error-NEEDTIMINGOPT: t/t_notiming.v:16:13: Use --timing or --no-timing to specify how timing controls should be handled +%Error-NEEDTIMINGOPT: t/t_notiming.v:16:12: Use --timing or --no-timing to specify how timing controls should be handled : ... In instance t 16 | x = #1 8; - | ^ + | ^ %Error-NEEDTIMINGOPT: t/t_notiming.v:19:8: Use --timing or --no-timing to specify how event controls should be handled : ... In instance t 19 | @e | ^ -%Error-NEEDTIMINGOPT: t/t_notiming.v:26:13: Use --timing or --no-timing to specify how delays should be handled +%Error-NEEDTIMINGOPT: t/t_notiming.v:26:12: Use --timing or --no-timing to specify how delays should be handled : ... In instance t 26 | initial #1 ->e; - | ^ -%Error-NEEDTIMINGOPT: t/t_notiming.v:27:13: Use --timing or --no-timing to specify how delays should be handled + | ^ +%Error-NEEDTIMINGOPT: t/t_notiming.v:27:12: Use --timing or --no-timing to specify how delays should be handled : ... In instance t 27 | initial #2 $stop; - | ^ + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_timing_unset2.out b/test_regress/t/t_timing_unset2.out index 7d8dd8304..0d340a8f2 100644 --- a/test_regress/t/t_timing_unset2.out +++ b/test_regress/t/t_timing_unset2.out @@ -3,24 +3,24 @@ 25 | @e1; | ^ ... For error description see https://verilator.org/warn/NEEDTIMINGOPT?v=latest -%Error-NEEDTIMINGOPT: t/t_timing_off.v:33:13: Use --timing or --no-timing to specify how delays should be handled +%Error-NEEDTIMINGOPT: t/t_timing_off.v:33:12: Use --timing or --no-timing to specify how delays should be handled : ... In instance t 33 | initial #2 ->e1; - | ^ -%Error-NEEDTIMINGOPT: t/t_timing_off.v:37:13: Use --timing or --no-timing to specify how delays should be handled + | ^ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:37:12: Use --timing or --no-timing to specify how delays should be handled : ... In instance t 37 | initial #3 $stop; - | ^ -%Error-NEEDTIMINGOPT: t/t_timing_off.v:38:13: Use --timing or --no-timing to specify how delays should be handled + | ^ +%Error-NEEDTIMINGOPT: t/t_timing_off.v:38:12: Use --timing or --no-timing to specify how delays should be handled : ... In instance t 38 | initial #1 @(e1, e2) #1 $stop; - | ^ + | ^ %Error-NEEDTIMINGOPT: t/t_timing_off.v:38:15: Use --timing or --no-timing to specify how event controls should be handled : ... In instance t 38 | initial #1 @(e1, e2) #1 $stop; | ^ -%Error-NEEDTIMINGOPT: t/t_timing_off.v:38:26: Use --timing or --no-timing to specify how delays should be handled +%Error-NEEDTIMINGOPT: t/t_timing_off.v:38:25: Use --timing or --no-timing to specify how delays should be handled : ... In instance t 38 | initial #1 @(e1, e2) #1 $stop; - | ^ + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_timing_zerodly_unsup.out b/test_regress/t/t_timing_zerodly_unsup.out index fc3a2f5b0..05ffbd7fb 100644 --- a/test_regress/t/t_timing_zerodly_unsup.out +++ b/test_regress/t/t_timing_zerodly_unsup.out @@ -1,5 +1,5 @@ -%Error-ZERODLY: t/t_timing_zerodly_unsup.v:12:14: Unsupported: #0 delays do not schedule process resumption in the Inactive region +%Error-ZERODLY: t/t_timing_zerodly_unsup.v:12:13: Unsupported: #0 delays do not schedule process resumption in the Inactive region 12 | #0 if (v) $finish; - | ^ + | ^ ... For error description see https://verilator.org/warn/ZERODLY?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_wait.out b/test_regress/t/t_wait.out index f60b978eb..2de31cc23 100644 --- a/test_regress/t/t_wait.out +++ b/test_regress/t/t_wait.out @@ -15,25 +15,25 @@ : ... In instance t 20 | wait (value == 3) if (value != 3) $stop; | ^~~~ -%Warning-STMTDLY: t/t_wait.v:25:8: Ignoring delay on this statement due to --no-timing +%Warning-STMTDLY: t/t_wait.v:25:7: Ignoring delay on this statement due to --no-timing : ... In instance t 25 | #10; - | ^~ + | ^ ... Use "/* verilator lint_off STMTDLY */" and lint_on around source to disable this message. -%Warning-STMTDLY: t/t_wait.v:27:8: Ignoring delay on this statement due to --no-timing +%Warning-STMTDLY: t/t_wait.v:27:7: Ignoring delay on this statement due to --no-timing : ... In instance t 27 | #10; - | ^~ -%Warning-STMTDLY: t/t_wait.v:29:8: Ignoring delay on this statement due to --no-timing + | ^ +%Warning-STMTDLY: t/t_wait.v:29:7: Ignoring delay on this statement due to --no-timing : ... In instance t 29 | #10; - | ^~ -%Warning-STMTDLY: t/t_wait.v:31:8: Ignoring delay on this statement due to --no-timing + | ^ +%Warning-STMTDLY: t/t_wait.v:31:7: Ignoring delay on this statement due to --no-timing : ... In instance t 31 | #10; - | ^~ -%Warning-STMTDLY: t/t_wait.v:33:8: Ignoring delay on this statement due to --no-timing + | ^ +%Warning-STMTDLY: t/t_wait.v:33:7: Ignoring delay on this statement due to --no-timing : ... In instance t 33 | #10; - | ^~ + | ^ %Error: Exiting due to From 038d57070b539b2da2658edd7740d41ea069fd4f Mon Sep 17 00:00:00 2001 From: Arkadiusz Kozdra Date: Fri, 14 Oct 2022 14:55:55 +0200 Subject: [PATCH 106/177] Support standalone 'this' in classes (#3675) (#2594) (#3248) --- src/V3AstNodeMath.h | 16 +++++++++++++++ src/V3EmitCFunc.h | 6 ++++++ src/V3LinkDot.cpp | 33 ++++++++++++++++++++++-------- src/V3Width.cpp | 4 ++++ test_regress/t/t_class_uses_this.v | 16 +++++++++++++++ 5 files changed, 67 insertions(+), 8 deletions(-) diff --git a/src/V3AstNodeMath.h b/src/V3AstNodeMath.h index dc2d6f8ad..e6d752bc1 100644 --- a/src/V3AstNodeMath.h +++ b/src/V3AstNodeMath.h @@ -1273,6 +1273,22 @@ public: bool cleanOut() const override { return true; } bool same(const AstNode* /*samep*/) const override { return true; } }; +class AstThisRef final : public AstNodeMath { + // Reference to 'this'. + // @astgen op1 := childDTypep : Optional[AstClassRefDType] // dtype of the node +public: + explicit AstThisRef(FileLine* fl, AstClassRefDType* dtypep) + : ASTGEN_SUPER_ThisRef(fl) { + childDTypep(dtypep); + } + ASTGEN_MEMBERS_AstThisRef; + string emitC() override { return "this"; } + string emitVerilog() override { return "this"; } + bool same(const AstNode* /*samep*/) const override { return true; } + bool cleanOut() const override { return true; } + AstNodeDType* getChildDTypep() const override { return childDTypep(); } + virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); } +}; class AstUCFunc final : public AstNodeMath { // User's $c function // Perhaps this should be an AstNodeListop; but there's only one list math right now diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 8369386bd..28cd0b9e8 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -1168,6 +1168,12 @@ public: emitConstant(nodep, nullptr, ""); } } + void visit(AstThisRef* nodep) override { + putbs(nodep->dtypep()->cType("", false, false)); + puts("{"); + puts(m_useSelfForThis ? "vlSelf" : "this"); + puts("}"); + } // void visit(AstMTaskBody* nodep) override { diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 0611e6297..1f4757e89 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -2054,6 +2054,13 @@ private: } return false; } + VSymEnt* getThisClassSymp() { + VSymEnt* classSymp = m_ds.m_dotSymp; + do { + classSymp = classSymp->parentp(); + } while (classSymp && !VN_IS(classSymp->nodep(), Class)); + return classSymp; + } // VISITs void visit(AstNetlist* nodep) override { @@ -2201,10 +2208,7 @@ private: m_ds.m_dotPos = DP_SCOPE; if (VN_IS(nodep->lhsp(), ParseRef) && nodep->lhsp()->name() == "this") { - VSymEnt* classSymp = m_ds.m_dotSymp; - do { - classSymp = classSymp->parentp(); - } while (classSymp && !VN_IS(classSymp->nodep(), Class)); + VSymEnt* classSymp = getThisClassSymp(); if (!classSymp) { nodep->v3error("'this' used outside class (IEEE 1800-2017 8.11)"); m_ds.m_dotErr = true; @@ -2213,10 +2217,7 @@ private: UINFO(8, " this. " << m_ds.ascii() << endl); } } else if (VN_IS(nodep->lhsp(), ParseRef) && nodep->lhsp()->name() == "super") { - const VSymEnt* classSymp = m_ds.m_dotSymp; - do { - classSymp = classSymp->parentp(); - } while (classSymp && !VN_IS(classSymp->nodep(), Class)); + const VSymEnt* classSymp = getThisClassSymp(); if (!classSymp) { nodep->v3error("'super' used outside class (IEEE 1800-2017 8.15)"); m_ds.m_dotErr = true; @@ -2310,6 +2311,22 @@ private: nodep->v3warn(E_UNSUPPORTED, "Unsupported: super"); m_ds.m_dotErr = true; } + if (nodep->name() == "this") { + iterateChildren(nodep); + if (m_statep->forPrimary()) return; // The class might be parametrized somewhere + const VSymEnt* classSymp = getThisClassSymp(); + if (!classSymp) { + nodep->v3error("'this' used outside class (IEEE 1800-2017 8.11)"); + return; + } + AstClass* const classp = VN_AS(classSymp->nodep(), Class); + AstClassRefDType* const dtypep + = new AstClassRefDType{nodep->fileline(), classp, nullptr}; + AstThisRef* const newp = new AstThisRef{nodep->fileline(), dtypep}; + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } if (m_ds.m_dotPos == DP_FINAL && VN_IS(m_ds.m_unlinkedScopep, LambdaArgRef) && nodep->name() == "index") { // 'with' statement's 'item.index' diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 6717b91e6..ffb9e8d08 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2465,6 +2465,10 @@ private: userIterateChildren(nodep, nullptr); // First size all members nodep->repairCache(); } + void visit(AstThisRef* nodep) override { + if (nodep->didWidthAndSet()) return; + nodep->dtypep(iterateEditMoveDTypep(nodep, nodep->childDTypep())); + } void visit(AstClassRefDType* nodep) override { if (nodep->didWidthAndSet()) return; // TODO this maybe eventually required to properly resolve members, diff --git a/test_regress/t/t_class_uses_this.v b/test_regress/t/t_class_uses_this.v index 531c6e1cb..966f8bb23 100644 --- a/test_regress/t/t_class_uses_this.v +++ b/test_regress/t/t_class_uses_this.v @@ -11,13 +11,25 @@ class Cls; this.addr = addr; end : body endfunction + function void set2(bit [3:0] addr); + begin : body + Cls c2 = this; + c2.addr = addr; + end : body + endfunction extern function void setext(bit [3:0] addr); + extern function void setext2(bit [3:0] addr); endclass function void Cls::setext(bit [3:0] addr); this.addr = addr; endfunction +function void Cls::setext2(bit [3:0] addr); + Cls c2 = this; + c2.addr = addr; +endfunction + module t(/*AUTOARG*/ // Inputs clk @@ -34,8 +46,12 @@ module t(/*AUTOARG*/ $display(baz.addr); `endif if (bar.addr != 4) $stop; + bar.set2(1); + if (bar.addr != 1) $stop; bar.setext(2); if (bar.addr != 2) $stop; + bar.setext2(3); + if (bar.addr != 3) $stop; $write("*-* All Finished *-*\n"); $finish; end From 14f58ed6c7c552dd5a89856660f71c22c82ddafb Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 15 Oct 2022 06:21:34 -0400 Subject: [PATCH 107/177] Add error on real edge event control. --- Changes | 1 + src/V3Ast.h | 1 + src/V3Width.cpp | 4 ++++ test_regress/t/t_lint_edge_real.out | 5 +++++ test_regress/t/t_lint_edge_real.pl | 19 +++++++++++++++++++ test_regress/t/t_lint_edge_real.v | 18 ++++++++++++++++++ 6 files changed, 48 insertions(+) create mode 100644 test_regress/t/t_lint_edge_real.out create mode 100755 test_regress/t/t_lint_edge_real.pl create mode 100644 test_regress/t/t_lint_edge_real.v diff --git a/Changes b/Changes index bdb6af7f9..7a1acb363 100644 --- a/Changes +++ b/Changes @@ -40,6 +40,7 @@ Verilator 4.228 2022-10-01 * Add --build-jobs, and rework arguments for -j (#3623). [Kamil Rakoczy] * Rename --bin to --build-dep-bin. * Rename debug flags --dumpi-tree, --dumpi-graph, etc. [Geza Lore] +* Add error on real edge event control. * Fix thread saftey in SystemC VL_ASSIGN_SBW/WSB (#3494) (#3513). [Mladen Slijepcevic] * Fix crash in gate optimization of circular logic (#3543). [Bill Flynn] * Fix arguments in non-static method call (#3547) (#3582). [Gustav Svensk] diff --git a/src/V3Ast.h b/src/V3Ast.h index fa33caca9..dccd8ddc5 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -297,6 +297,7 @@ public: }; return clocked[m_e]; } + bool anEdge() const { return m_e == ET_BOTHEDGE || m_e == ET_POSEDGE || m_e == ET_NEGEDGE; } VEdgeType invert() const { switch (m_e) { case ET_CHANGED: return ET_CHANGED; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index ffb9e8d08..a2ec63a30 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -5131,6 +5131,10 @@ private: VL_DO_DANGLING(nodep->deleteTree(), nodep); } else { userIterateChildren(nodep, WidthVP(SELF, BOTH).p()); + if (nodep->edgeType().anEdge() && nodep->sensp()->dtypep()->skipRefp()->isDouble()) { + nodep->sensp()->v3error( + "Edge event control not legal on real type (IEEE 1800-2017 6.12.1)"); + } } } void visit(AstWait* nodep) override { diff --git a/test_regress/t/t_lint_edge_real.out b/test_regress/t/t_lint_edge_real.out new file mode 100644 index 000000000..bc9f0f0d5 --- /dev/null +++ b/test_regress/t/t_lint_edge_real.out @@ -0,0 +1,5 @@ +%Error: t/t_lint_edge_real.v:16:22: Edge event control not legal on real type (IEEE 1800-2017 6.12.1) + : ... In instance t + 16 | always @ (posedge rbad) $stop; + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_lint_edge_real.pl b/test_regress/t/t_lint_edge_real.pl new file mode 100755 index 000000000..07964a1b5 --- /dev/null +++ b/test_regress/t/t_lint_edge_real.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003-2009 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 + +scenarios(vlt => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_lint_edge_real.v b/test_regress/t/t_lint_edge_real.v new file mode 100644 index 000000000..58dfad8ad --- /dev/null +++ b/test_regress/t/t_lint_edge_real.v @@ -0,0 +1,18 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + rbad, rok + ); + input real rbad; + input real rok; + + always @ (rok) $stop; + + always @ (posedge rbad) $stop; + +endmodule From 5957156deeec2d84151777ec9fe180283db8f306 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 15 Oct 2022 06:57:12 -0400 Subject: [PATCH 108/177] Tests: Fix bad result check. --- test_regress/t/t_mailbox.v | 2 +- test_regress/t/t_semaphore.v | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test_regress/t/t_mailbox.v b/test_regress/t/t_mailbox.v index 06ea1f834..1ef2bc5e2 100644 --- a/test_regress/t/t_mailbox.v +++ b/test_regress/t/t_mailbox.v @@ -32,7 +32,7 @@ module t(/*AUTOARG*/); if (m.num() != 1) $stop; if (m.try_peek(out) <= 0) $stop; if (out != 123) $stop; - if (m.num() != 0) $stop; + if (m.num() != 1) $stop; out = 0; if (m.try_peek(out) <= 0) $stop; if (out != 123) $stop; diff --git a/test_regress/t/t_semaphore.v b/test_regress/t/t_semaphore.v index bc229ad75..c191b2c13 100644 --- a/test_regress/t/t_semaphore.v +++ b/test_regress/t/t_semaphore.v @@ -19,7 +19,11 @@ module t(/*AUTOARG*/); int msg; initial begin - s = new(4); + s = new(1); + if (s.try_get() == 0) $stop; + if (s.try_get() != 0) $stop; + + s = new(0); if (s.try_get() != 0) $stop; s.put(); @@ -31,7 +35,6 @@ module t(/*AUTOARG*/); s.put(2); if (s.try_get(2) <= 0) $stop; -`ifndef VERILATOR fork begin #10; // So later then get() starts below @@ -44,7 +47,6 @@ module t(/*AUTOARG*/); s.get(); end join -`endif s2 = new; if (s2.try_get() != 0) $stop; From e32ff0e1a6bc4e310d4af85ebff56b367ceb6a4c Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 15 Oct 2022 10:37:24 -0400 Subject: [PATCH 109/177] Tests: Better mailbox and semaphore tests. --- test_regress/t/t_mailbox.out | 8 ++-- test_regress/t/t_mailbox.v | 10 +++-- test_regress/t/t_mailbox_bad.out | 8 ++-- test_regress/t/t_mailbox_bad.pl | 3 -- test_regress/t/t_mailbox_class.out | 17 ++++++++ test_regress/t/t_mailbox_class.pl | 24 ++++++++++ test_regress/t/t_mailbox_class.v | 65 ++++++++++++++++++++++++++++ test_regress/t/t_semaphore.out | 8 ++-- test_regress/t/t_semaphore.v | 10 +++-- test_regress/t/t_semaphore_bad.out | 7 +-- test_regress/t/t_semaphore_bad.pl | 3 -- test_regress/t/t_semaphore_class.out | 6 +++ test_regress/t/t_semaphore_class.pl | 24 ++++++++++ test_regress/t/t_semaphore_class.v | 33 ++++++++++++++ 14 files changed, 197 insertions(+), 29 deletions(-) create mode 100644 test_regress/t/t_mailbox_class.out create mode 100755 test_regress/t/t_mailbox_class.pl create mode 100644 test_regress/t/t_mailbox_class.v create mode 100644 test_regress/t/t_semaphore_class.out create mode 100755 test_regress/t/t_semaphore_class.pl create mode 100644 test_regress/t/t_semaphore_class.v diff --git a/test_regress/t/t_mailbox.out b/test_regress/t/t_mailbox.out index 4d5432212..d09756948 100644 --- a/test_regress/t/t_mailbox.out +++ b/test_regress/t/t_mailbox.out @@ -1,6 +1,6 @@ -%Error: t/t_mailbox.v:20:4: Can't find typedef: 'mailbox' - 20 | mailbox #(int) m; +%Error: t/t_mailbox.v:24:4: Can't find typedef: 'mailbox' + 24 | mailbox #(int) m; | ^~~~~~~ -%Error: Internal Error: t/t_mailbox.v:20:14: ../V3LinkDot.cpp:#: Pin not under instance? - 20 | mailbox #(int) m; +%Error: Internal Error: t/t_mailbox.v:24:14: ../V3LinkDot.cpp:#: Pin not under instance? + 24 | mailbox #(int) m; | ^~~ diff --git a/test_regress/t/t_mailbox.v b/test_regress/t/t_mailbox.v index 1ef2bc5e2..64e2ba175 100644 --- a/test_regress/t/t_mailbox.v +++ b/test_regress/t/t_mailbox.v @@ -16,8 +16,12 @@ // function int try_peek( ref T message ); // endclass +`ifndef MAILBOX_T + `define MAILBOX_T mailbox +`endif + module t(/*AUTOARG*/); - mailbox #(int) m; + `MAILBOX_T #(int) m; int msg; int out; @@ -50,8 +54,8 @@ module t(/*AUTOARG*/); msg = 125; m.put(msg); m.put(msg); - m.try_put(msg); - m.try_put(msg); + if (m.try_put(msg) == 0) $stop; + if (m.try_put(msg) == 0) $stop; if (m.num() != 4) $stop; if (m.try_put(msg) != 0) $stop; if (m.num() != 4) $stop; diff --git a/test_regress/t/t_mailbox_bad.out b/test_regress/t/t_mailbox_bad.out index 4d5432212..218a34ad3 100644 --- a/test_regress/t/t_mailbox_bad.out +++ b/test_regress/t/t_mailbox_bad.out @@ -1,6 +1,6 @@ -%Error: t/t_mailbox.v:20:4: Can't find typedef: 'mailbox' - 20 | mailbox #(int) m; +%Error: t/t_mailbox_bad.v:8:4: Can't find typedef: 'mailbox' + 8 | mailbox #(int) m; | ^~~~~~~ -%Error: Internal Error: t/t_mailbox.v:20:14: ../V3LinkDot.cpp:#: Pin not under instance? - 20 | mailbox #(int) m; +%Error: Internal Error: t/t_mailbox_bad.v:8:14: ../V3LinkDot.cpp:#: Pin not under instance? + 8 | mailbox #(int) m; | ^~~ diff --git a/test_regress/t/t_mailbox_bad.pl b/test_regress/t/t_mailbox_bad.pl index 8de551634..a083f46f5 100755 --- a/test_regress/t/t_mailbox_bad.pl +++ b/test_regress/t/t_mailbox_bad.pl @@ -10,10 +10,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); -top_filename("t_mailbox.v"); - lint( - verilator_flags2 => ["--xml-only"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_mailbox_class.out b/test_regress/t/t_mailbox_class.out new file mode 100644 index 000000000..da24a9ec0 --- /dev/null +++ b/test_regress/t/t_mailbox_class.out @@ -0,0 +1,17 @@ +%Error-UNSUPPORTED: t/t_mailbox_class.v:21:25: Unsupported: event controls in methods + : ... In instance $unit::mailbox_cls + 21 | if (m_bound != 0) wait (m_q.size() < m_bound); + | ^~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_mailbox_class.v:35:7: Unsupported: event controls in methods + : ... In instance $unit::mailbox_cls + 35 | wait (m_q.size() != 0); + | ^~~~ +%Error-UNSUPPORTED: t/t_mailbox_class.v:49:7: Unsupported: event controls in methods + : ... In instance $unit::mailbox_cls + 49 | wait (m_q.size() != 0); + | ^~~~ +%Error-UNSUPPORTED: t/t_mailbox_class.v:21:31: Unsupported: Cannot detect changes on expression of complex type (see combinational cycles reported by UNOPTFLAT) + 21 | if (m_bound != 0) wait (m_q.size() < m_bound); + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_mailbox_class.pl b/test_regress/t/t_mailbox_class.pl new file mode 100755 index 000000000..830c5f044 --- /dev/null +++ b/test_regress/t/t_mailbox_class.pl @@ -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 2020 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--timing"], + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +execute( + check_finished => 1, + ) if !$Self->{vlt_all}; + +ok(1); +1; diff --git a/test_regress/t/t_mailbox_class.v b/test_regress/t/t_mailbox_class.v new file mode 100644 index 000000000..31619225c --- /dev/null +++ b/test_regress/t/t_mailbox_class.v @@ -0,0 +1,65 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +class mailbox_cls #(type T=int); + // Test an implementation similar to what Verilator will do internally + int m_bound; + T m_q[$]; + + function new(int bound = 0); + m_bound = bound; + endfunction + + function int num(); + return m_q.size(); + endfunction + + task put(T message); + if (m_bound != 0) wait (m_q.size() < m_bound); + m_q.push_back(message); + endtask + function int try_put(T message); + if (m_bound != 0 && m_q.size() < m_bound) begin + m_q.push_back(message); + return 1; + end + else begin + return 0; + end + endfunction + + task get(ref T message); + wait (m_q.size() != 0); + message = m_q.pop_front(); + endtask + function int try_get(ref T message); + if (m_q.size() != 0) begin + message = m_q.pop_front(); + return 1; + end + else begin + return 0; + end + endfunction + + task peek(ref T message); + wait (m_q.size() != 0); + message = m_q[0]; + endtask + function int try_peek(ref T message); + if (m_q.size() != 0) begin + message = m_q[0]; + return 1; + end + else begin + return 0; + end + endfunction +endclass + +`define MAILBOX_T mailbox_cls + +`include "t_mailbox.v" diff --git a/test_regress/t/t_semaphore.out b/test_regress/t/t_semaphore.out index 76d60e04d..95ce89973 100644 --- a/test_regress/t/t_semaphore.out +++ b/test_regress/t/t_semaphore.out @@ -1,7 +1,7 @@ -%Error: t/t_semaphore.v:17:4: Can't find typedef: 'semaphore' - 17 | semaphore s; +%Error: t/t_semaphore.v:21:4: Can't find typedef: 'semaphore' + 21 | semaphore s; | ^~~~~~~~~ -%Error: t/t_semaphore.v:18:4: Can't find typedef: 'semaphore' - 18 | semaphore s2; +%Error: t/t_semaphore.v:22:4: Can't find typedef: 'semaphore' + 22 | semaphore s2; | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_semaphore.v b/test_regress/t/t_semaphore.v index c191b2c13..500fac7e8 100644 --- a/test_regress/t/t_semaphore.v +++ b/test_regress/t/t_semaphore.v @@ -12,10 +12,14 @@ // function int try_get(int keyCount = 1); // endclass +`ifndef SEMAPHORE_T + `define SEMAPHORE_T semaphore +`endif + module t(/*AUTOARG*/); // From UVM: - semaphore s; - semaphore s2; + `SEMAPHORE_T s; + `SEMAPHORE_T s2; int msg; initial begin @@ -23,7 +27,7 @@ module t(/*AUTOARG*/); if (s.try_get() == 0) $stop; if (s.try_get() != 0) $stop; - s = new(0); + s = new; if (s.try_get() != 0) $stop; s.put(); diff --git a/test_regress/t/t_semaphore_bad.out b/test_regress/t/t_semaphore_bad.out index 76d60e04d..c07616459 100644 --- a/test_regress/t/t_semaphore_bad.out +++ b/test_regress/t/t_semaphore_bad.out @@ -1,7 +1,4 @@ -%Error: t/t_semaphore.v:17:4: Can't find typedef: 'semaphore' - 17 | semaphore s; - | ^~~~~~~~~ -%Error: t/t_semaphore.v:18:4: Can't find typedef: 'semaphore' - 18 | semaphore s2; +%Error: t/t_semaphore_bad.v:8:4: Can't find typedef: 'semaphore' + 8 | semaphore s; | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_semaphore_bad.pl b/test_regress/t/t_semaphore_bad.pl index 92483cce2..a083f46f5 100755 --- a/test_regress/t/t_semaphore_bad.pl +++ b/test_regress/t/t_semaphore_bad.pl @@ -10,10 +10,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(vlt => 1); -top_filename("t_semaphore.v"); - lint( - verilator_flags2 => ["--xml-only"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_semaphore_class.out b/test_regress/t/t_semaphore_class.out new file mode 100644 index 000000000..54be71860 --- /dev/null +++ b/test_regress/t/t_semaphore_class.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_semaphore_class.v:17:7: Unsupported: event controls in methods + : ... In instance $unit::semaphore_cls + 17 | wait (m_keys >= keyCount); + | ^~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_semaphore_class.pl b/test_regress/t/t_semaphore_class.pl new file mode 100755 index 000000000..830c5f044 --- /dev/null +++ b/test_regress/t/t_semaphore_class.pl @@ -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 2020 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--timing"], + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +execute( + check_finished => 1, + ) if !$Self->{vlt_all}; + +ok(1); +1; diff --git a/test_regress/t/t_semaphore_class.v b/test_regress/t/t_semaphore_class.v new file mode 100644 index 000000000..9460c6cf4 --- /dev/null +++ b/test_regress/t/t_semaphore_class.v @@ -0,0 +1,33 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +class semaphore_cls; + // Test an implementation similar to what Verilator will do internally + int m_keys; + function new(int keyCount = 0); + m_keys = keyCount; + endfunction + function void put(int keyCount = 1); + m_keys += keyCount; + endfunction + task get(int keyCount = 1); + wait (m_keys >= keyCount); + m_keys -= keyCount; + endtask + function int try_get(int keyCount = 1); + if (m_keys >= keyCount) begin + m_keys -= keyCount; + return 1; + end + else begin + return 0; + end + endfunction +endclass + +`define SEMAPHORE_T semaphore_cls + +`include "t_semaphore.v" From d9a0d0ade2c7380686cc38131e8bf8033eb1b2b3 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 15 Oct 2022 10:57:46 -0400 Subject: [PATCH 110/177] Commentary (fix earlier commit) --- Changes | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 7a1acb363..385708ca3 100644 --- a/Changes +++ b/Changes @@ -23,6 +23,10 @@ Verilator 5.001 devel significant performance improvements on some designs. [Geza Lore, Shunyao CAD] * Add --binary option as alias of --main --exe --build --timing (#3625). +**Minor:** + +* Add error on real edge event control. + Verilator 4.228 2022-10-01 ========================== @@ -40,7 +44,6 @@ Verilator 4.228 2022-10-01 * Add --build-jobs, and rework arguments for -j (#3623). [Kamil Rakoczy] * Rename --bin to --build-dep-bin. * Rename debug flags --dumpi-tree, --dumpi-graph, etc. [Geza Lore] -* Add error on real edge event control. * Fix thread saftey in SystemC VL_ASSIGN_SBW/WSB (#3494) (#3513). [Mladen Slijepcevic] * Fix crash in gate optimization of circular logic (#3543). [Bill Flynn] * Fix arguments in non-static method call (#3547) (#3582). [Gustav Svensk] From 732d5bea105961746c95034f756eb005133cd911 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 15 Oct 2022 10:59:31 -0400 Subject: [PATCH 111/177] Commentary: Standard format for company contributions --- Changes | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Changes b/Changes index 385708ca3..6e5f1cecc 100644 --- a/Changes +++ b/Changes @@ -39,7 +39,7 @@ Verilator 4.228 2022-10-01 **Minor:** -* Support some IEEE signal strengths (#3601) (#3629). [Ryszard Rozak/Antmicro] +* Support some IEEE signal strengths (#3601) (#3629). [Ryszard Rozak, Antmicro Ltd] * Add --main to generate main() C++ (previously was experimental only). * Add --build-jobs, and rework arguments for -j (#3623). [Kamil Rakoczy] * Rename --bin to --build-dep-bin. @@ -50,7 +50,7 @@ Verilator 4.228 2022-10-01 * Fix default --mod-prefix when --prefix is repeated (#3603). [Geza Lore] * Fix calling trace() after open() segfault (#3610) (#3627). [Yu-Sheng Lin] * Fix typedef'ed class conversion to boolean (#3616). [Aleksander Kiryk] -* Fix Verilation speed when disabled warnings (#3632). [Kamil Rakoczy/Antmicro] +* Fix Verilation speed when disabled warnings (#3632). [Kamil Rakoczy, Antmicro Ltd] Verilator 4.226 2022-08-31 @@ -59,19 +59,19 @@ Verilator 4.226 2022-08-31 **Minor:** * Add --future0 and --future1 options. -* Support class parameters (#2231) (#3541). [Arkadiusz Kozdra/Antmicro] -* Support wildcard index associative arrays (#3501). [Arkadiusz Kozdra/Antmicro] +* Support class parameters (#2231) (#3541). [Arkadiusz Kozdra, Antmicro Ltd] +* Support wildcard index associative arrays (#3501). [Arkadiusz Kozdra, Antmicro Ltd] * Support negated properties (#3572). [Aleksander Kiryk] * Support $test$plusargs(expr) (#3489). * Rename trace rolloverSize() (#3570). * Improve Verilation speed with --threads on large designs. [Geza Lore] -* Improve Verilation memory by reducing V3Number (#3521). [Mariusz Glebocki/Antmicro] +* Improve Verilation memory by reducing V3Number (#3521). [Mariusz Glebocki, Antmicro Ltd] * Fix struct pattern assignment (#2328) (#3517). [Mostafa Gamal] * Fix public combo propagation issues (#2905). [Todd Strader] * Fix incorrect tristate logic (#3399) [shareefj, Vighnesh Iyer] * Fix incorrect bit op tree optimization (#3470). [algrobman] * Fix bisonpre for MSYS2 (#3471). -* Fix max memory usage (#3483). [Kamil Rakoczy/Antmicro] +* Fix max memory usage (#3483). [Kamil Rakoczy, Antmicro Ltd] * Fix empty string arguments to display (#3484). [Grulfen] * Fix table misoptimizing away display (#3488). [Stefan Post] * Fix unique_ptr memory header for MinGW64 (#3493). @@ -82,8 +82,8 @@ Verilator 4.226 2022-08-31 * Fix segfault exporting non-existant package (#3535). * Fix void-cast queue pop_front or pop_back (#3542) (#3364). [Drew Ranck] * Fix case statement comparing string literal (#3544). [Gustav Svensk] -* Fix === with some tristate constants (#3551). [Ryszard Rozak/Antmicro] -* Fix converting subclasses to string (#3552). [Arkadiusz Kozdra/Antmicro] +* Fix === with some tristate constants (#3551). [Ryszard Rozak, Antmicro Ltd] +* Fix converting subclasses to string (#3552). [Arkadiusz Kozdra, Antmicro Ltd] * Fix --hierarchical with order-based pin connections (#3583) (#3585). [Kelin9298] @@ -105,7 +105,7 @@ Verilator 4.224 2022-06-19 * Improve conditional merging optimization (#3125). [Geza Lore, Shunyao CAD] * Define VM_TRACE_VCD when tracing in VCD format. [Geza Lore, Shunyao CAD] * Add assert when VerilatedContext is mis-deleted (#3121). [Rupert Swarbrick] -* Internal prep work towards timing control. [Krzysztof Bieganski/Antmicro] +* Internal prep work towards timing control. [Krzysztof Bieganski, Antmicro Ltd] * Fix hang with large case statement optimization (#3405). [Mike Urbach] * Fix UNOPTFLAT warning from initial static var (#3406). [Kamil Rakoczy] * Fix compile error when enable VL_LEAK_CHECKS (#3411). [HungMingWu] @@ -272,7 +272,7 @@ Verilator 4.212 2021-09-01 * Fix re-evaluation of logic dependent on state set in DPI exports (#3091). [Geza Lore] * Support unpacked array localparams in tasks/functions (#3078). [Geza Lore] * Support timeunit/timeprecision in $unit. -* Support assignment patterns as children of pins (#3041). [Krzysztof Bieganski/Antmicro] +* Support assignment patterns as children of pins (#3041). [Krzysztof Bieganski, Antmicro Ltd] * Add --instr-count-dpi to tune assumed DPI import cost for multithreaded model scheduling. Default value changed to 200 (#3068). [Yinan Xu] * Output files are split based on the set of headers required @@ -337,7 +337,7 @@ Verilator 4.204 2021-06-12 * Prep work towards better ccache hashing/performance. [Geza Lore] * Fix assertion failure in bitOpTree optimization (#2891) (#2899). [Raynard Qiao] * Fix DPI functions not seen as vpiModule (#2893). [Todd Strader] -* Fix bounds check in VL_SEL_IWII (#2910). [Krzysztof Bieganski/Antmicro] +* Fix bounds check in VL_SEL_IWII (#2910). [Krzysztof Bieganski, Antmicro Ltd] * Fix slowdown in elaboration (#2911). [Nathan Graybeal] * Fix initialization of assoc in assoc array (#2914). [myftptoyman] * Fix make support for gmake 3.x (#2920) (#2921). [Philipp Wagner] @@ -452,7 +452,7 @@ Verilator 4.108 2021-01-10 **Major:** * Many VPI changes for IEEE compatibility, which may alter behavior from previous releases. -* Support randomize() class method and rand (#2607). [Krzysztof Bieganski/Antmicro] +* Support randomize() class method and rand (#2607). [Krzysztof Bieganski, Antmicro Ltd] **Minor:** @@ -512,7 +512,7 @@ Verilator 4.104 2020-11-14 * Support queue and associative array 'with' statements (#2616). * Support queue slicing (#2326). * Support associative array pattern assignments and defaults. -* Support static methods and typedefs in classes (#2615). [Krzysztof Bieganski/Antmicro] +* Support static methods and typedefs in classes (#2615). [Krzysztof Bieganski, Antmicro Ltd] * Add error on typedef referencing self (#2539). [Cody Piersall] * With --debug, turn off address space layout randomization. * Fix iteration over mutating list bug in VPI (#2588). [Kaleb Barrett] From b16b607b980e46460d8f0e95cd7c02685e663399 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 15 Oct 2022 11:04:03 -0400 Subject: [PATCH 112/177] Commentary: Changes update --- Changes | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Changes b/Changes index 6e5f1cecc..270e8890f 100644 --- a/Changes +++ b/Changes @@ -25,7 +25,13 @@ Verilator 5.001 devel **Minor:** +* Support standalone 'this' in classes (#2594) (#3248) (#3675). [Arkadiusz Kozdra, Antmicro Ltd] +* Support tristate select/extend (#3604). [Ryszard Rozak, Antmicro Ltd> +* Support linting for top module interfaces (#3635). [Kanad Kanhere] +* Add --dump-tree-dot to enable dumping Ast Tree .dot files (#3636). [Marcel Chang] * Add error on real edge event control. +* Fix linker errors in user-facing timing functions (#3657). [Krzysztof Bieganski, Antmicro Ltd] +* Fix null access on optimized-out fork statements (#3658). [Krzysztof Bieganski, Antmicro Ltd] Verilator 4.228 2022-10-01 From 916a3d9066841130778fac282011b21f2941f35d Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 15 Oct 2022 13:24:38 -0400 Subject: [PATCH 113/177] Fix --main --trace missing initial timestep (#3678). --- src/V3EmitCMain.cpp | 4 --- test_regress/t/t_timing_debug1.out | 8 ------ test_regress/t/t_timing_debug2.out | 8 ------ test_regress/t/t_trace_timing1.out | 27 +++++++++++++++++++++ test_regress/t/t_trace_timing1.pl | 35 +++++++++++++++++++++++++++ test_regress/t/t_trace_timing1.v | 39 ++++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 test_regress/t/t_trace_timing1.out create mode 100755 test_regress/t/t_trace_timing1.pl create mode 100644 test_regress/t/t_trace_timing1.v diff --git a/src/V3EmitCMain.cpp b/src/V3EmitCMain.cpp index cfe83de6b..ca6cc31ea 100644 --- a/src/V3EmitCMain.cpp +++ b/src/V3EmitCMain.cpp @@ -74,10 +74,6 @@ private: + "{contextp.get()}};\n"); puts("\n"); - puts("// Evaluate initials\n"); - puts("topp->eval(); // Evaluate\n"); - puts("\n"); - puts("// Simulate until $finish\n"); puts("while (!contextp->gotFinish()) {\n"); puts(/**/ "// Evaluate model\n"); diff --git a/test_regress/t/t_timing_debug1.out b/test_regress/t/t_timing_debug1.out index 0ad01cbe5..eff0962b3 100644 --- a/test_regress/t/t_timing_debug1.out +++ b/test_regress/t/t_timing_debug1.out @@ -67,14 +67,6 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} No triggers active --V{t#,#}+ Vt_timing_debug1___024root___timing_commit --V{t#,#}+++++TOP Evaluate Vt_timing_debug1::eval_step --V{t#,#}+ Vt_timing_debug1___024root___eval_debug_assertions --V{t#,#}+ Eval --V{t#,#}+ Vt_timing_debug1___024root___eval --V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act --V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 4 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out index 0e3f6d873..08eab7b7d 100644 --- a/test_regress/t/t_timing_debug2.out +++ b/test_regress/t/t_timing_debug2.out @@ -30,14 +30,6 @@ -V{t#,#}+ Vt_timing_debug2___024root___eval -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#} No triggers active --V{t#,#}+ Vt_timing_debug2___024root___timing_commit --V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step --V{t#,#}+ Vt_timing_debug2___024root___eval_debug_assertions --V{t#,#}+ Eval --V{t#,#}+ Vt_timing_debug2___024root___eval --V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act --V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime()) -V{t#,#}+ Vt_timing_debug2___024root___timing_commit -V{t#,#}+ Vt_timing_debug2___024root___timing_resume diff --git a/test_regress/t/t_trace_timing1.out b/test_regress/t/t_trace_timing1.out new file mode 100644 index 000000000..1cd5d550c --- /dev/null +++ b/test_regress/t/t_trace_timing1.out @@ -0,0 +1,27 @@ +$version Generated by VerilatedVcd $end +$date Sat Oct 15 13:17:45 2022 $end +$timescale 1ps $end + + $scope module TOP $end + $scope module t $end + $var wire 32 % CLOCK_CYCLE [31:0] $end + $var wire 1 $ clk $end + $var wire 1 # rst $end + $upscope $end + $upscope $end +$enddefinitions $end + + +#0 +1# +0$ +b00000000000000000000000000001010 % +#5 +1$ +#10 +0# +0$ +#15 +1$ +#20 +1# diff --git a/test_regress/t/t_trace_timing1.pl b/test_regress/t/t_trace_timing1.pl new file mode 100755 index 000000000..2ae74250a --- /dev/null +++ b/test_regress/t/t_trace_timing1.pl @@ -0,0 +1,35 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + compile( + verilator_flags => [# Custom as don't want -cc + "-Mdir $Self->{obj_dir}", + "--debug-check", ], + verilator_flags2 => ['--binary --trace'], + verilator_make_cmake => 0, + verilator_make_gmake => 0, + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +vcd_identical("$Self->{obj_dir}/simx.vcd", $Self->{golden_filename}); + +ok(1); +1; diff --git a/test_regress/t/t_trace_timing1.v b/test_regress/t/t_trace_timing1.v new file mode 100644 index 000000000..26aef7ea9 --- /dev/null +++ b/test_regress/t/t_trace_timing1.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`define STRINGIFY(x) `"x`" + +module t(/*AUTOARG*/); + + localparam CLOCK_CYCLE = 10; + + logic rst; + logic clk; + + initial begin + $dumpfile({`STRINGIFY(`TEST_OBJ_DIR),"/simx.vcd"}); + $dumpvars; + end + + always #(CLOCK_CYCLE/2) clk = ~clk; + + always begin + rst = 1; + clk = 0; + $display("[%0t] rst: %d, rst: %d", $time, rst, rst); + + #CLOCK_CYCLE; + rst = 0; + $display("[%0t] rst: %d, rst: %d", $time, rst, rst); + + #CLOCK_CYCLE; + $display("[%0t] rst: %d, rst: %d", $time, rst, rst); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule From 379a947379af53dc28dc1a1ebab9c11ab35f4102 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 15 Oct 2022 13:59:07 -0400 Subject: [PATCH 114/177] Tests: Fix some internal code coverage holes --- test_regress/t/t_assoc_wildcard_method.v | 2 +- test_regress/t/t_program_anonymous.out | 5 ++++ test_regress/t/t_program_anonymous.pl | 23 ++++++++++++++++ test_regress/t/t_program_anonymous.v | 34 ++++++++++++++++++++++++ test_regress/t/t_program_extern.out | 5 ++++ test_regress/t/t_program_extern.pl | 23 ++++++++++++++++ test_regress/t/t_program_extern.v | 23 ++++++++++++++++ test_regress/t/t_trace_two_a.v | 2 +- 8 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 test_regress/t/t_program_anonymous.out create mode 100755 test_regress/t/t_program_anonymous.pl create mode 100644 test_regress/t/t_program_anonymous.v create mode 100644 test_regress/t/t_program_extern.out create mode 100755 test_regress/t/t_program_extern.pl create mode 100644 test_regress/t/t_program_extern.v diff --git a/test_regress/t/t_assoc_wildcard_method.v b/test_regress/t/t_assoc_wildcard_method.v index c61086e7b..cb90ad605 100644 --- a/test_regress/t/t_assoc_wildcard_method.v +++ b/test_regress/t/t_assoc_wildcard_method.v @@ -11,7 +11,7 @@ module t (/*AUTOARG*/); initial begin int q[*]; - int qe[*]; // Empty + int qe [ * ]; // Empty - Note spaces around [*] for parsing coverage int qv[$]; // Value returns int qi[$]; // Index returns int i; diff --git a/test_regress/t/t_program_anonymous.out b/test_regress/t/t_program_anonymous.out new file mode 100644 index 000000000..d4a02c68a --- /dev/null +++ b/test_regress/t/t_program_anonymous.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_program_anonymous.v:7:1: Unsupported: Anonymous programs + 7 | program; + | ^~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_program_anonymous.pl b/test_regress/t/t_program_anonymous.pl new file mode 100755 index 000000000..be66c40e6 --- /dev/null +++ b/test_regress/t/t_program_anonymous.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +execute( + check_finished => 1, + ) if !$Self->{vlt_all}; + +ok(1); +1; diff --git a/test_regress/t/t_program_anonymous.v b/test_regress/t/t_program_anonymous.v new file mode 100644 index 000000000..5df52f197 --- /dev/null +++ b/test_regress/t/t_program_anonymous.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +program; + task atask; + endtask + function int afunc(input int i); + return i+1; + endfunction +class acls; + static int i = 10; +endclass +endprogram + +program t(/*AUTOARG*/); + + int i; + + initial begin + atask(); + + i = afunc(2); + if (i != 3) $stop; + + if (acls::i != 10) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end + +endprogram diff --git a/test_regress/t/t_program_extern.out b/test_regress/t/t_program_extern.out new file mode 100644 index 000000000..de3c1a979 --- /dev/null +++ b/test_regress/t/t_program_extern.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_program_extern.v:7:1: Unsupported: extern program + 7 | extern program pgm; + | ^~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_program_extern.pl b/test_regress/t/t_program_extern.pl new file mode 100755 index 000000000..be66c40e6 --- /dev/null +++ b/test_regress/t/t_program_extern.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +execute( + check_finished => 1, + ) if !$Self->{vlt_all}; + +ok(1); +1; diff --git a/test_regress/t/t_program_extern.v b/test_regress/t/t_program_extern.v new file mode 100644 index 000000000..67bf42028 --- /dev/null +++ b/test_regress/t/t_program_extern.v @@ -0,0 +1,23 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +extern program pgm; + +program pgm; + task ptask; + endtask +endprogram + +module t(/*AUTOARG*/); + + pgm sub (); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_trace_two_a.v b/test_regress/t/t_trace_two_a.v index a9b87db05..83f3ed363 100644 --- a/test_regress/t/t_trace_two_a.v +++ b/test_regress/t/t_trace_two_a.v @@ -32,7 +32,7 @@ module t (/*AUTOARG*/ `ifdef TEST_DUMP $dumpfile(filename); - $dumpvars(0, top); + $dumpvars(0); // Intentionally no ", top" for parsing coverage with just (expr) $dumplimit(10 * 1024 * 1024); `elsif TEST_DUMPPORTS $dumpports(top, filename); From c0739e908cfc0df9a1b8259497c6cb91d6e55c31 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 15 Oct 2022 18:37:44 -0400 Subject: [PATCH 115/177] Fix internal traceActivity to be zero reset not randomized. --- src/V3Trace.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index 10243983d..e0fa8ce8b 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -447,7 +447,7 @@ private: // read-modify-write on the C type), and the speed of the tracing code // is the same on largish designs. FileLine* const flp = m_topScopep->fileline(); - AstNodeDType* const newScalarDtp = new AstBasicDType(flp, VFlagLogicPacked(), 1); + AstNodeDType* const newScalarDtp = new AstBasicDType{flp, VFlagBitPacked{}, 1}; v3Global.rootp()->typeTablep()->addTypesp(newScalarDtp); AstRange* const newArange = new AstRange{flp, VNumRange{static_cast(m_activityNumber) - 1, 0}}; From 3cd2c8532de2c985dfb082f4eacb43d9b9b63692 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 15 Oct 2022 18:47:10 -0400 Subject: [PATCH 116/177] Internals: Cleanup spacing of Vi for loops. --- src/V3EmitCFunc.cpp | 4 ++-- src/V3EmitCFunc.h | 2 +- src/V3EmitCImp.cpp | 8 ++++---- src/V3EmitCModel.cpp | 4 ++-- src/V3EmitCSyms.cpp | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index e17d72150..e1b35f8ae 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -627,7 +627,7 @@ void EmitCFunc::emitVarReset(AstVar* varp) { } } else if (AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { if (initarp->defaultp()) { - puts("for (int __Vi=0; __Vi<" + cvtToStr(adtypep->elementsConst())); + puts("for (int __Vi = 0; __Vi < " + cvtToStr(adtypep->elementsConst())); puts("; ++__Vi) {\n"); emitSetVarConstant(varNameProtected + "[__Vi]", VN_AS(initarp->defaultp(), Const)); puts("}\n"); @@ -677,7 +677,7 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP UASSERT_OBJ(adtypep->hi() >= adtypep->lo(), varp, "Should have swapped msb & lsb earlier."); const string ivar = string("__Vi") + cvtToStr(depth); - const string pre = ("for (int " + ivar + "=" + cvtToStr(0) + "; " + ivar + "<" + const string pre = ("for (int " + ivar + " = " + cvtToStr(0) + "; " + ivar + " < " + cvtToStr(adtypep->elementsConst()) + "; ++" + ivar + ") {\n"); const string below = emitVarResetRecurse(varp, varNameProtected, adtypep->subDTypep(), depth + 1, suffix + "[" + ivar + "]"); diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 28cd0b9e8..166dd60de 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -240,7 +240,7 @@ public: // "+" in the debug indicates a print from the model puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ "); - for (int i = 0; i < m_modp->level(); ++i) { puts(" "); } + for (int i = 0; i < m_modp->level(); ++i) puts(" "); puts(prefixNameProtect(m_modp)); puts(nodep->isLoose() ? "__" : "::"); puts(nodep->nameProtect() + "\\n\"); );\n"); diff --git a/src/V3EmitCImp.cpp b/src/V3EmitCImp.cpp index 0c0cdd379..05749c259 100644 --- a/src/V3EmitCImp.cpp +++ b/src/V3EmitCImp.cpp @@ -393,8 +393,8 @@ class EmitCImp final : EmitCFunc { UASSERT_OBJ(arrayp->hi() >= arrayp->lo(), varp, "Should have swapped msb & lsb earlier."); const string ivar = string("__Vi") + cvtToStr(vecnum); - puts("for (int __Vi" + cvtToStr(vecnum) + "=" + cvtToStr(0)); - puts("; " + ivar + "<" + cvtToStr(arrayp->elementsConst())); + puts("for (int __Vi" + cvtToStr(vecnum) + " = " + cvtToStr(0)); + puts("; " + ivar + " < " + cvtToStr(arrayp->elementsConst())); puts("; ++" + ivar + ") {\n"); elementp = arrayp->subDTypep()->skipRefp(); } @@ -407,8 +407,8 @@ class EmitCImp final : EmitCFunc { && !(basicp && basicp->keyword() == VBasicDTypeKwd::STRING)) { const int vecnum = vects++; const string ivar = string("__Vi") + cvtToStr(vecnum); - puts("for (int __Vi" + cvtToStr(vecnum) + "=" + cvtToStr(0)); - puts("; " + ivar + "<" + cvtToStr(elementp->widthWords())); + puts("for (int __Vi" + cvtToStr(vecnum) + " = " + cvtToStr(0)); + puts("; " + ivar + " < " + cvtToStr(elementp->widthWords())); puts("; ++" + ivar + ") {\n"); } puts("os" + op + varp->nameProtect()); diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index f3425caff..517d2618c 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -303,9 +303,9 @@ class EmitCModel final : public EmitCFunc { UASSERT_OBJ(arrayp->hi() >= arrayp->lo(), varp, "Should have swapped msb & lsb earlier."); const string ivar = std::string{"__Vi"} + cvtToStr(vecnum); - puts("for (int __Vi" + cvtToStr(vecnum) + "=" + puts("for (int __Vi" + cvtToStr(vecnum) + " = " + cvtToStr(arrayp->lo())); - puts("; " + ivar + "<=" + cvtToStr(arrayp->hi())); + puts("; " + ivar + " <= " + cvtToStr(arrayp->hi())); puts("; ++" + ivar + ") {\n"); } puts("sensitive << " + varp->nameProtect()); diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index 752fe77c6..6dcfab8b1 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -866,7 +866,7 @@ void EmitCSyms::emitSymImp() { if (v3Global.dpi()) { m_ofpBase->puts("// Setup export functions\n"); - m_ofpBase->puts("for (int __Vfinal=0; __Vfinal<2; __Vfinal++) {\n"); + m_ofpBase->puts("for (int __Vfinal = 0; __Vfinal < 2; ++__Vfinal) {\n"); for (auto it = m_scopeFuncs.begin(); it != m_scopeFuncs.end(); ++it) { AstScopeName* const scopep = it->second.m_scopep; AstCFunc* const funcp = it->second.m_cfuncp; From 76ccd332a60939abe1df454f4dc322d334baeb51 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 16 Oct 2022 09:41:39 -0400 Subject: [PATCH 117/177] Internals: Remove DETECTARRAY, dead code. --- docs/guide/warnings.rst | 12 ++++-------- src/V3Error.h | 3 +-- test_regress/t/t_detectarray_1.v | 2 -- test_regress/t/t_detectarray_2.v | 4 ---- test_regress/t/t_detectarray_3.v | 2 -- 5 files changed, 5 insertions(+), 18 deletions(-) diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 00f929b04..1da3907b6 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -465,15 +465,11 @@ List Of Warnings .. option:: DETECTARRAY - .. TODO better example + Historical, never issued since version 3.862. - Error when Verilator tries to deal with a combinatorial loop that could - not be flattened, and which involves a datatype which Verilator cannot - handle, such as an unpacked struct or a large unpacked array. This - typically occurs when :vlopt:`-Wno-UNOPTFLAT ` has been used - to override an UNOPTFLAT warning (see below). - - The solution is to break the loop, as described for UNOPTFLAT. + Was an error when Verilator tried to deal with a combinatorial loop that + could not be flattened, and which involves a datatype which Verilator + could not handle, such as an unpacked struct or a large unpacked array. .. option:: DIDNOTCONVERGE diff --git a/src/V3Error.h b/src/V3Error.h index d103866d6..b68941e55 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -54,7 +54,6 @@ public: I_DEF_NETTYPE_WIRE, // `default_nettype is WIRE (false=NONE) I_TIMING, // Enable timing from /*verilator timing_on/off*/ // Error codes: - E_DETECTARRAY, // Error: Unsupported: Can't detect changes on arrayed variable E_ENCAPSULATED, // Error: local/protected violation E_PORTSHORT, // Error: Output port is connected to a constant, electrical short E_UNSUPPORTED, // Error: Unsupported (generally) @@ -167,7 +166,7 @@ public: // Boolean " I_CELLDEFINE", " I_COVERAGE", " I_TRACING", " I_LINT", " I_DEF_NETTYPE_WIRE", " I_TIMING", // Errors - "DETECTARRAY", "ENCAPSULATED", "PORTSHORT", "UNSUPPORTED", "TASKNSVAR", "NEEDTIMINGOPT", "NOTIMING", + "ENCAPSULATED", "PORTSHORT", "UNSUPPORTED", "TASKNSVAR", "NEEDTIMINGOPT", "NOTIMING", // Warnings " EC_FIRST_WARN", "ALWCOMBORDER", "ASSIGNDLY", "ASSIGNIN", "BADSTDPRAGMA", diff --git a/test_regress/t/t_detectarray_1.v b/test_regress/t/t_detectarray_1.v index aa2f20e04..df266bb06 100644 --- a/test_regress/t/t_detectarray_1.v +++ b/test_regress/t/t_detectarray_1.v @@ -1,7 +1,5 @@ // DESCRIPTION: Verilator: Simple test of unoptflat // -// Trigger the DETECTARRAY error. -// // This file ONLY is placed into the Public Domain, for any use, // without warranty, 2013 by Jeremy Bennett. // SPDX-License-Identifier: CC0-1.0 diff --git a/test_regress/t/t_detectarray_2.v b/test_regress/t/t_detectarray_2.v index 72df7ad80..141fc500e 100644 --- a/test_regress/t/t_detectarray_2.v +++ b/test_regress/t/t_detectarray_2.v @@ -1,9 +1,5 @@ // DESCRIPTION: Verilator: Simple test of unoptflat // -// This should trigger the DETECTARRAY error like t_detectarray_1.v, but in -// fact it casuses a broken link error. The only difference is that the struct -// is defined using a constant rather than a localparam. -// // This file ONLY is placed into the Public Domain, for any use, // without warranty, 2013 by Jeremy Bennett. // SPDX-License-Identifier: CC0-1.0 diff --git a/test_regress/t/t_detectarray_3.v b/test_regress/t/t_detectarray_3.v index 13b1e0592..07e7c1be2 100644 --- a/test_regress/t/t_detectarray_3.v +++ b/test_regress/t/t_detectarray_3.v @@ -1,7 +1,5 @@ // DESCRIPTION: Verilator: Simple test of unoptflat // -// Trigger the DETECTARRAY error on packed structure. -// // This file ONLY is placed into the Public Domain, for any use, // without warranty, 2014 by Jie Xu. // SPDX-License-Identifier: CC0-1.0 From f3292a365146483f1cf1f9af374d93a00a91b425 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 16 Oct 2022 10:36:18 -0400 Subject: [PATCH 118/177] Tests: Prove fixed (#2410) --- test_regress/t/t_delay.v | 9 ++++++ test_regress/t/t_delay_stmtdly_bad.out | 38 ++++++++++++++++---------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/test_regress/t/t_delay.v b/test_regress/t/t_delay.v index 161362387..628bc3c32 100644 --- a/test_regress/t/t_delay.v +++ b/test_regress/t/t_delay.v @@ -17,11 +17,15 @@ module t (/*AUTOARG*/ reg [31:0] dly0; wire [31:0] dly1; wire [31:0] dly2 = dly1 + 32'h1; + wire [31:0] dly3; typedef struct packed { int dly; } dly_s_t; dly_s_t dly_s; assign #(1.2000000000000000) dly1 = dly0 + 32'h1; + assign #(sub.delay) dly3 = dly1 + 1; + + sub sub(); always @ (posedge clk) begin cyc <= cyc + 1; @@ -41,9 +45,14 @@ module t (/*AUTOARG*/ //dly0 <= # dly_s.dly 32'h55; // Unsupported, issue-2410 end else if (cyc == 99) begin + if (dly3 !== 32'h57) $stop; $write("*-* All Finished *-*\n"); #100 $finish; end end endmodule + +module sub; + realtime delay = 2.3; +endmodule diff --git a/test_regress/t/t_delay_stmtdly_bad.out b/test_regress/t/t_delay_stmtdly_bad.out index 8d13754c8..1a38b7b3d 100644 --- a/test_regress/t/t_delay_stmtdly_bad.out +++ b/test_regress/t/t_delay_stmtdly_bad.out @@ -1,31 +1,39 @@ -%Warning-ASSIGNDLY: t/t_delay.v:24:11: Ignoring timing control on this assignment/primitive due to --no-timing +%Warning-ASSIGNDLY: t/t_delay.v:25:11: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 24 | assign #(1.2000000000000000) dly1 = dly0 + 32'h1; + 25 | assign #(1.2000000000000000) dly1 = dly0 + 32'h1; | ^ ... For warning description see https://verilator.org/warn/ASSIGNDLY?v=latest ... Use "/* verilator lint_off ASSIGNDLY */" and lint_on around source to disable this message. -%Warning-ASSIGNDLY: t/t_delay.v:29:18: Ignoring timing control on this assignment/primitive due to --no-timing +%Warning-ASSIGNDLY: t/t_delay.v:26:11: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 29 | dly0 <= #0 32'h11; - | ^ -%Warning-ASSIGNDLY: t/t_delay.v:32:18: Ignoring timing control on this assignment/primitive due to --no-timing + 26 | assign #(sub.delay) dly3 = dly1 + 1; + | ^ +%Warning-ASSIGNDLY: t/t_delay.v:33:18: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 32 | dly0 <= #0.12 dly0 + 32'h12; + 33 | dly0 <= #0 32'h11; | ^ -%Warning-ASSIGNDLY: t/t_delay.v:40:18: Ignoring timing control on this assignment/primitive due to --no-timing +%Warning-ASSIGNDLY: t/t_delay.v:36:18: Ignoring timing control on this assignment/primitive due to --no-timing : ... In instance t - 40 | dly0 <= #(dly_s.dly) 32'h55; + 36 | dly0 <= #0.12 dly0 + 32'h12; | ^ -%Warning-STMTDLY: t/t_delay.v:45:10: Ignoring delay on this statement due to --no-timing +%Warning-ASSIGNDLY: t/t_delay.v:44:18: Ignoring timing control on this assignment/primitive due to --no-timing + : ... In instance t + 44 | dly0 <= #(dly_s.dly) 32'h55; + | ^ +%Warning-STMTDLY: t/t_delay.v:50:10: Ignoring delay on this statement due to --no-timing : ... In instance t - 45 | #100 $finish; + 50 | #100 $finish; | ^ -%Warning-UNUSED: t/t_delay.v:22:12: Signal is not used: 'dly_s' +%Warning-UNUSED: t/t_delay.v:23:12: Signal is not used: 'dly_s' : ... In instance t - 22 | dly_s_t dly_s; + 23 | dly_s_t dly_s; | ^~~~~ -%Warning-BLKSEQ: t/t_delay.v:39:20: Blocking assignment '=' in sequential logic process +%Warning-UNUSED: t/t_delay.v:57:13: Signal is not used: 'delay' + : ... In instance t.sub + 57 | realtime delay = 2.3; + | ^~~~~ +%Warning-BLKSEQ: t/t_delay.v:43:20: Blocking assignment '=' in sequential logic process : ... Suggest using delayed assignment '<=' - 39 | dly_s.dly = 55; + 43 | dly_s.dly = 55; | ^ %Error: Exiting due to From cb7b024e8fd8c0c675926d11321d2c234b8d4c8a Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 16 Oct 2022 11:10:41 -0400 Subject: [PATCH 119/177] Commentary: Spelling, and add upgrade notes (#3462) --- Changes | 24 +++++++++++++++--------- docs/guide/connecting.rst | 2 +- docs/guide/exe_verilator.rst | 2 +- docs/guide/files.rst | 2 +- docs/guide/warnings.rst | 2 +- docs/spelling.txt | 17 +++++++++++++++++ 6 files changed, 36 insertions(+), 13 deletions(-) diff --git a/Changes b/Changes index 270e8890f..1d06c5041 100644 --- a/Changes +++ b/Changes @@ -14,14 +14,20 @@ Verilator 5.001 devel **Major:** * This is a major new release. -* Fully support the Active and NBA scheduling regions as defined by the +* Require C++20 for the new --timing features. Upgrading to a C++20 or + newer compiler is strongly recommended. +* Support the Active and NBA scheduling regions as defined by the SystemVerilog standard (IEEE 1800-2017 chapter 4). This means all generated clocks are now simulated correctly (#3278, #3384). [Geza Lore, Shunyao CAD] * Support timing controls (delays, event controls in any location, wait - statements) and forks. See docs for details. [Krzysztof Bieganski, Antmicro Ltd] + statements) and forks. [Krzysztof Bieganski, Antmicro Ltd] + This may require adding --timing or --no-timing. See docs for details. * Introduce a new combinational logic optimizer (DFG), that can yield significant performance improvements on some designs. [Geza Lore, Shunyao CAD] * Add --binary option as alias of --main --exe --build --timing (#3625). + For designs where C++ was only used to make a simple testbench we + recommend abandoning that C++, and instead letting Verilator build it + with --binary (or --main). **Minor:** @@ -39,7 +45,7 @@ Verilator 4.228 2022-10-01 **Announcement:** -* The next release is anticipated primere Verilator Version 5. Please +* The next release is anticipated to premiere Verilator Version 5. Please consider beta-testing the github 'develop-v5' branch, which will soon merge into the github 'master' branch (#3383). @@ -50,12 +56,12 @@ Verilator 4.228 2022-10-01 * Add --build-jobs, and rework arguments for -j (#3623). [Kamil Rakoczy] * Rename --bin to --build-dep-bin. * Rename debug flags --dumpi-tree, --dumpi-graph, etc. [Geza Lore] -* Fix thread saftey in SystemC VL_ASSIGN_SBW/WSB (#3494) (#3513). [Mladen Slijepcevic] +* Fix thread safety in SystemC VL_ASSIGN_SBW/WSB (#3494) (#3513). [Mladen Slijepcevic] * Fix crash in gate optimization of circular logic (#3543). [Bill Flynn] * Fix arguments in non-static method call (#3547) (#3582). [Gustav Svensk] * Fix default --mod-prefix when --prefix is repeated (#3603). [Geza Lore] * Fix calling trace() after open() segfault (#3610) (#3627). [Yu-Sheng Lin] -* Fix typedef'ed class conversion to boolean (#3616). [Aleksander Kiryk] +* Fix typedef'ed class conversion to Boolean (#3616). [Aleksander Kiryk] * Fix Verilation speed when disabled warnings (#3632). [Kamil Rakoczy, Antmicro Ltd] @@ -79,17 +85,17 @@ Verilator 4.226 2022-08-31 * Fix bisonpre for MSYS2 (#3471). * Fix max memory usage (#3483). [Kamil Rakoczy, Antmicro Ltd] * Fix empty string arguments to display (#3484). [Grulfen] -* Fix table misoptimizing away display (#3488). [Stefan Post] +* Fix table optimizing away display (#3488). [Stefan Post] * Fix unique_ptr memory header for MinGW64 (#3493). -* Fix $dump systemtask with --output-split-cfuncs (#3495) (#3497). [Varun Koyyalagunta] +* Fix $dump system task with --output-split-cfuncs (#3495) (#3497). [Varun Koyyalagunta] * Fix wrong bit op tree optimization (#3509). [Nathan Graybeal] * Fix nested default assignment for struct pattern (#3511) (#3524). [Mostafa Gamal] * Fix sformat string incorrectly cleared (#3515) (#3519). [Gustav Svensk] -* Fix segfault exporting non-existant package (#3535). +* Fix segfault exporting non-existent package (#3535). * Fix void-cast queue pop_front or pop_back (#3542) (#3364). [Drew Ranck] * Fix case statement comparing string literal (#3544). [Gustav Svensk] * Fix === with some tristate constants (#3551). [Ryszard Rozak, Antmicro Ltd] -* Fix converting subclasses to string (#3552). [Arkadiusz Kozdra, Antmicro Ltd] +* Fix converting classes to string (#3552). [Arkadiusz Kozdra, Antmicro Ltd] * Fix --hierarchical with order-based pin connections (#3583) (#3585). [Kelin9298] diff --git a/docs/guide/connecting.rst b/docs/guide/connecting.rst index 876f15487..638f5c33d 100644 --- a/docs/guide/connecting.rst +++ b/docs/guide/connecting.rst @@ -447,7 +447,7 @@ wrapper which calls these two functions. the user should call: * :code:`designp->eventsPending()`, which returns :code:`true` if there are - any delayed events penging, + any delayed events pending, * :code:`designp->nextTimeSlot()`, which returns the simulation time of the next delayed event. This method can only be called if :code:`designp->nextTimeSlot()` returned :code:`true`. diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 4b1e0a458..786b1d275 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -522,7 +522,7 @@ Summary: .. option:: -fno-dfg-peephole- - Disable individula DFG peephole optimizer pattern. + Disable individual DFG peephole optimizer pattern. .. option:: -fno-dfg-pre-inline diff --git a/docs/guide/files.rst b/docs/guide/files.rst index a71214983..2b797c62a 100644 --- a/docs/guide/files.rst +++ b/docs/guide/files.rst @@ -93,7 +93,7 @@ For --cc/--sc, it creates: * - *{prefix}{each_verilog_module}{__n}*\ .cpp - Additional lower C++ files * - *{prefix}{each_verilog_module}{__DepSet_hash__n}*\ .cpp - - Additional lower C++ files (hased to reduce build times) + - Additional lower C++ files (hashed to reduce build times) For --hierarchy mode, it creates: diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 1da3907b6..508b381bd 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -327,7 +327,7 @@ List Of Warnings The warning could be disabled without affecting the simulation result. But it was recommended to check the warning as it may have - degrated the performance of the Verilated model. + degraded the performance of the Verilated model. .. option:: CMPCONST diff --git a/docs/spelling.txt b/docs/spelling.txt index 0e423ba26..3e1b7e7d3 100644 --- a/docs/spelling.txt +++ b/docs/spelling.txt @@ -7,12 +7,14 @@ Ami Amir Anastasiadis Anikin +Antmicro Antonin Antwerpen Arasanipalai Arjen Asciidoc Ashutosh +Ast Atmel Aurelien Bagri @@ -108,8 +110,10 @@ Goessling Gonnen Goorah Gossner +Graphviz Graybeal Grobman +Grulfen Gunter Guo Hao @@ -131,6 +135,7 @@ Iles Inlines Inout Iru +Iyer Iztok Jacko Jae @@ -157,6 +162,7 @@ Karge Karlsson Katz Katzman +Kelin Keren Keyi Kimmitt @@ -340,6 +346,7 @@ Verilating Verilation Verilator Verilog +Vighnesh Viktor Vm Vukobratovic @@ -376,6 +383,7 @@ agrobman ahouska al ala +algrobman andit ar architected @@ -397,6 +405,7 @@ bbox benchmarking biguint biops +bisonpre bitOpTree bitOpTree bitop @@ -441,6 +450,7 @@ const constexpr constpool coredump +coroutine countbits countones cout @@ -463,6 +473,7 @@ defenv defname defparam demangling +dep der dereference desassign @@ -480,6 +491,7 @@ dsvf dtor dumpall dumpfile +dumpi dumplimit dumpoff dumpon @@ -537,6 +549,7 @@ filesystem filt flto flushCall +fno fopen forceable foreach @@ -600,6 +613,7 @@ inouts inserted instantiation instantiations +intra iostream ish isunbounded @@ -725,6 +739,7 @@ profiler prototyptes ps pthread +ptr pulldown pulldowns pullup @@ -750,6 +765,7 @@ reloop resetall respecified rodata +rolloverSize rr rst runtime @@ -764,6 +780,7 @@ seg setuphold sformat sformatf +shareefj shortint shortreal signame From 5e796529221c70bd0fcba6e661ab84ff78a4f7fd Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Mon, 17 Oct 2022 12:36:37 +0200 Subject: [PATCH 120/177] Test tracing with `--timing` and `--main` (#3656) Add a test for tracing with `--main` and `--timing`. Signed-off-by: Krzysztof Bieganski --- test_regress/t/t_timing_clkgen1.v | 9 +++++++++ test_regress/t/t_timing_clkgen_unsup.out | 4 ++-- test_regress/t/t_timing_trace.out | 4 ++-- test_regress/t/t_timing_trace.pl | 4 ++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/test_regress/t/t_timing_clkgen1.v b/test_regress/t/t_timing_clkgen1.v index 12cc19a41..3ef565d9b 100644 --- a/test_regress/t/t_timing_clkgen1.v +++ b/test_regress/t/t_timing_clkgen1.v @@ -4,6 +4,8 @@ // any use, without warranty, 2020 by Wilson Snyder. // SPDX-License-Identifier: CC0-1.0 +`define STRINGIFY(x) `"x`" + module clkgen(output bit clk); initial begin #(8.0:5:3) clk = 1; // Middle is default @@ -38,4 +40,11 @@ module t(/*AUTOARG*/); $finish; end end + +`ifdef TEST_TRACING + initial begin + $dumpfile({`STRINGIFY(`TEST_OBJ_DIR),"/simx.vcd"}); + $dumpvars; + end +`endif endmodule diff --git a/test_regress/t/t_timing_clkgen_unsup.out b/test_regress/t/t_timing_clkgen_unsup.out index 8ee56209f..fbbd81192 100644 --- a/test_regress/t/t_timing_clkgen_unsup.out +++ b/test_regress/t/t_timing_clkgen_unsup.out @@ -1,5 +1,5 @@ -%Warning-MINTYPMAXDLY: t/t_timing_clkgen1.v:9:13: Unsupported: minimum/typical/maximum delay expressions. Using the typical delay - 9 | #(8.0:5:3) clk = 1; +%Warning-MINTYPMAXDLY: t/t_timing_clkgen1.v:11:13: Unsupported: minimum/typical/maximum delay expressions. Using the typical delay + 11 | #(8.0:5:3) clk = 1; | ^ ... For warning description see https://verilator.org/warn/MINTYPMAXDLY?v=latest ... Use "/* verilator lint_off MINTYPMAXDLY */" and lint_on around source to disable this message. diff --git a/test_regress/t/t_timing_trace.out b/test_regress/t/t_timing_trace.out index c21672923..52df60e90 100644 --- a/test_regress/t/t_timing_trace.out +++ b/test_regress/t/t_timing_trace.out @@ -1,8 +1,8 @@ $version Generated by VerilatedVcd $end -$date Thu Aug 25 09:56:30 2022 $end +$date Wed Oct 5 13:59:40 2022 $end $timescale 1ps $end - $scope module top $end + $scope module TOP $end $scope module t $end $var wire 1 # clk $end $var wire 32 $ cyc [31:0] $end diff --git a/test_regress/t/t_timing_trace.pl b/test_regress/t/t_timing_trace.pl index 5dc845188..f4d0cd88e 100755 --- a/test_regress/t/t_timing_trace.pl +++ b/test_regress/t/t_timing_trace.pl @@ -17,8 +17,8 @@ else { top_filename("t/t_timing_clkgen1.v"); compile( - verilator_flags2 => ["--timing --trace -Wno-MINTYPMAXDLY"], - timing_loop => 1 + verilator_flags2 => ["--exe --main --timing --trace -Wno-MINTYPMAXDLY -DTEST_TRACING"], + make_main => 0, ); execute( From 840e26b69a3cbd98872176577f49ea12e56b2bb9 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 17 Oct 2022 14:36:32 +0100 Subject: [PATCH 121/177] Fix incorrect return in DFG decomposition Fixes #3676 --- src/V3DfgDecomposition.cpp | 4 ++-- test_regress/t/t_dfg_3676.pl | 16 ++++++++++++++++ test_regress/t/t_dfg_3676.v | 24 ++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100755 test_regress/t/t_dfg_3676.pl create mode 100644 test_regress/t/t_dfg_3676.v diff --git a/src/V3DfgDecomposition.cpp b/src/V3DfgDecomposition.cpp index 86b6cb46f..d12e9946e 100644 --- a/src/V3DfgDecomposition.cpp +++ b/src/V3DfgDecomposition.cpp @@ -405,14 +405,14 @@ class ExtractCyclicComponents final { if (!varp->hasSinks() && varp->arity() == 0) { VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); } - return; + continue; } if (DfgVarArray* const varp = vtxp->cast()) { varp->packSources(); if (!varp->hasSinks() && varp->arity() == 0) { VL_DO_DANGLING(varp->unlinkDelete(dfg), varp); } - return; + continue; } } } diff --git a/test_regress/t/t_dfg_3676.pl b/test_regress/t/t_dfg_3676.pl new file mode 100755 index 000000000..84ae125be --- /dev/null +++ b/test_regress/t/t_dfg_3676.pl @@ -0,0 +1,16 @@ +#!/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 => 1); + +compile(); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_3676.v b/test_regress/t/t_dfg_3676.v new file mode 100644 index 000000000..8f01e471a --- /dev/null +++ b/test_regress/t/t_dfg_3676.v @@ -0,0 +1,24 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +// verilator lint_off UNOPTFLAT + +module t( + input wire [3:0] i, + output wire [2:0][3:0] o +); + + wire [2:0][3:0] v; + + // This circular logic used to trip up DFG decomposition + + assign v[0] = i; + assign v[1][0] = v[0][1] | v[0][0]; + + assign o[1][2] = v[0][2]; + assign o[2][1:0] = {v[1][0] , o[1][0]}; + +endmodule From 5c65e0cfa1e8e1833e9703243f870e481ab916b5 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 17 Oct 2022 15:03:30 +0100 Subject: [PATCH 122/177] Dfg: Fix incorrect folding of associative expressions with shared terms Fixes #3679 --- src/V3DfgPeephole.cpp | 10 ++-------- test_regress/t/t_dfg_3679.pl | 21 ++++++++++++++++++++ test_regress/t/t_dfg_3679.v | 38 ++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 8 deletions(-) create mode 100755 test_regress/t/t_dfg_3679.pl create mode 100644 test_regress/t/t_dfg_3679.v diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 4b9e8f9b6..af98ca8da 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -248,10 +248,7 @@ class V3DfgPeephole final : public DfgVisitor { foldOp(constp->num(), lConstp->num(), rlConstp->num()); // Replace vertex - if VL_CONSTEXPR_CXX17 (!std::is_same::value) { - rVtxp->lhsp(constp); - vtxp->replaceWith(rVtxp); - } else if (!rVtxp->hasMultipleSinks()) { + if (!rVtxp->hasMultipleSinks()) { rVtxp->lhsp(constp); rVtxp->dtypep(vtxp->dtypep()); vtxp->replaceWith(rVtxp); @@ -279,10 +276,7 @@ class V3DfgPeephole final : public DfgVisitor { foldOp(constp->num(), lrConstp->num(), rConstp->num()); // Replace vertex - if VL_CONSTEXPR_CXX17 (!std::is_same::value) { - lVtxp->rhsp(constp); - vtxp->replaceWith(lVtxp); - } else if (!lVtxp->hasMultipleSinks()) { + if (!lVtxp->hasMultipleSinks()) { lVtxp->rhsp(constp); lVtxp->dtypep(vtxp->dtypep()); vtxp->replaceWith(lVtxp); diff --git a/test_regress/t/t_dfg_3679.pl b/test_regress/t/t_dfg_3679.pl new file mode 100755 index 000000000..1aa73f80a --- /dev/null +++ b/test_regress/t/t_dfg_3679.pl @@ -0,0 +1,21 @@ +#!/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 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 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_dfg_3679.v b/test_regress/t/t_dfg_3679.v new file mode 100644 index 000000000..206dd4691 --- /dev/null +++ b/test_regress/t/t_dfg_3679.v @@ -0,0 +1,38 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + + input clk; + integer cyc=1; + + reg [31:0] dly0; + + // DFG can fold this into 'dly3 = dly1 = dly0 + 1' and 'dly2 = dly0 + 2', + // but the 'dly0 + 1' term having multiple sinks needs to considered. + wire [31:0] dly1 = dly0 + 32'h1; + wire [31:0] dly2 = dly1 + 32'h1; + wire [31:0] dly3 = dly0 + 32'h1; + + always @ (posedge clk) begin + $display("[%0t] dly0=%h dly1=%h dly2=%h dly3=%h", $time, dly0, dly1, dly2, dly3); + cyc <= cyc + 1; + if (cyc == 1) begin + dly0 <= 32'h55; + end + else if (cyc == 3) begin + if (dly1 !== 32'h56) $stop; + if (dly2 !== 32'h57) $stop; + if (dly3 !== 32'h56) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule From 22ce36012e3406c476ec03704bb95a0c4cf16d46 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Mon, 17 Oct 2022 18:18:56 -0400 Subject: [PATCH 123/177] Add VERILATOR_TIMING define (#3684) --- docs/guide/extensions.rst | 7 +++++++ src/V3Options.cpp | 3 +++ test_regress/t/t_notiming.v | 4 ++++ test_regress/t/t_timing_events.v | 4 ++++ 4 files changed, 18 insertions(+) diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index e0622beb3..3e0161499 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -151,6 +151,13 @@ or "`ifdef`"'s may break other tools. Take remaining text up to the next :option:`\`verilog` mode switch and treat it as Verilator configuration commands. See :ref:`Configuration Files`. +.. option:: `VERILATOR_TIMING + + The VERILATOR_TIMING define is set when :vlopt:`--timing` is used to + allow an "\`ifdef" of code dependent on this feature. Note this define + is not affected by the :option:`timing_off` configuration file option + nor timing metacomments. + .. option:: `verilog Switch back to processing Verilog code after a diff --git a/src/V3Options.cpp b/src/V3Options.cpp index a13c15c48..3ec99660f 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -833,6 +833,9 @@ void V3Options::notify() { if (!m_dumpLevel.count("tree") && m_dumpLevel.count("tree-dot")) { m_dumpLevel["tree"] = m_dumpLevel["tree-dot"]; } + + // Preprocessor defines based on options used + if (timing().isSetTrue()) V3PreShell::defineCmdLine("VERILATOR_TIMING", "1"); } //###################################################################### diff --git a/test_regress/t/t_notiming.v b/test_regress/t/t_notiming.v index 8c822d4d2..649888e1f 100644 --- a/test_regress/t/t_notiming.v +++ b/test_regress/t/t_notiming.v @@ -26,3 +26,7 @@ module t; initial #1 ->e; initial #2 $stop; // timeout endmodule + +`ifdef VERILATOR_TIMING +`error "VERILATOR_TIMING should not be defined with --no-timing" +`endif diff --git a/test_regress/t/t_timing_events.v b/test_regress/t/t_timing_events.v index 583c66808..24065256b 100644 --- a/test_regress/t/t_timing_events.v +++ b/test_regress/t/t_timing_events.v @@ -30,3 +30,7 @@ module t; initial #21 $stop; // timeout endmodule + +`ifndef VERILATOR_TIMING +`error "VERILATOR_TIMING should have been defined as have --timing" +`endif From 46c5764383a40b3e7f685c98bc357eb8c327bd31 Mon Sep 17 00:00:00 2001 From: Topa Topino Date: Mon, 17 Oct 2022 19:51:13 -0400 Subject: [PATCH 124/177] Split UNUSED warning into genvar, param, and signal warnings (#3607) --- docs/CONTRIBUTORS | 1 + docs/guide/exe_verilator.rst | 8 ++-- docs/guide/warnings.rst | 24 +++++++++- src/V3Config.cpp | 7 ++- src/V3Coverage.cpp | 4 +- src/V3Error.h | 19 ++++++-- src/V3FileLine.cpp | 19 +++++++- src/V3FileLine.h | 2 + src/V3Options.cpp | 12 +++++ src/V3Undriven.cpp | 46 ++++++++++-------- src/verilog.y | 8 +++- test_regress/t/t_delay_stmtdly_bad.out | 8 ++-- test_regress/t/t_flag_context_bad.out | 4 +- test_regress/t/t_lint_once_bad.out | 12 ++--- test_regress/t/t_lint_unused_bad.out | 56 +++++++++++++++------- test_regress/t/t_lint_unused_bad.v | 24 ++++++++++ test_regress/t/t_lint_unused_iface_bad.out | 4 +- test_regress/t/t_waiveroutput.out | 2 +- 18 files changed, 193 insertions(+), 67 deletions(-) diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index df73f955e..480e34d5e 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -118,6 +118,7 @@ Tobias Rosenkranz Tobias Wölfel Todd Strader Tomasz Gorochowik +Topa Topino Tymoteusz Blazejczyk Udi Finkelstein Unai Martinez-Corral diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 786b1d275..e4ece604e 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -1475,7 +1475,8 @@ Summary: equivalent to ``-Wno-ALWCOMBORDER -Wno-BSSPACE -Wno-CASEINCOMPLETE -Wno-CASEOVERLAP -Wno-CASEX -Wno-CASTCONST -Wno-CASEWITHX -Wno-CMPCONST -Wno-COLONPLUS -Wno-ENDLABEL -Wno-IMPLICIT -Wno-LITENDIAN -Wno-PINCONNECTEMPTY - -Wno-PINMISSING -Wno-SYNCASYNCNET -Wno-UNDRIVEN -Wno-UNSIGNED -Wno-UNUSED + -Wno-PINMISSING -Wno-SYNCASYNCNET -Wno-UNDRIVEN -Wno-UNSIGNED + -Wno-UNUSEDGENVAR -Wno-UNUSEDPARAM -Wno-UNUSEDSIGNAL -Wno-WIDTH`` plus the list shown for Wno-style. It is strongly recommended you cleanup your code rather than using this @@ -1487,7 +1488,8 @@ Summary: Disable all code style related warning messages (note by default they are already disabled). This is equivalent to ``-Wno-DECLFILENAME -Wno-DEFPARAM -Wno-EOFNEWLINE -Wno-IMPORTSTAR -Wno-INCABSPATH -Wno-PINCONNECTEMPTY - -Wno-PINNOCONNECT -Wno-SYNCASYNCNET -Wno-UNDRIVEN -Wno-UNUSED + -Wno-PINNOCONNECT -Wno-SYNCASYNCNET -Wno-UNDRIVEN + -Wno-UNUSEDGENVAR -Wno-UNUSEDPARAM -Wno-UNUSEDSIGNAL -Wno-VARHIDDEN``. .. option:: -Wpedantic @@ -1516,7 +1518,7 @@ Summary: Enable all code style related warning messages. This is equivalent to ``-Wwarn ASSIGNDLY -Wwarn-DECLFILENAME -Wwarn-DEFPARAM -Wwarn-EOFNEWLINE -Wwarn-INCABSPATH -Wwarn-PINNOCONNECT -Wwarn-SYNCASYNCNET -Wwarn-UNDRIVEN - -Wwarn-UNUSED -Wwarn-VARHIDDEN``. + -Wwarn-UNUSEDGENVAR -Wwarn-UNUSEDPARAM -Wwarn-UNUSEDSIGNAL -Wwarn-VARHIDDEN``. .. option:: --x-assign 0 diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 508b381bd..681f11ab5 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -1542,9 +1542,31 @@ List Of Warnings .. option:: UNUSED + Disabling/enabling UNUSED is equivalent to disabling/enabling the + `UNUSEDGENVAR`, `UNUSEDPARAM` and `UNUSEDSIGNAL` warnings. + + Never issued since version 5.000. Historically warned that a variable, + parameter, or signal was unused. + +.. option:: UNUSEDGENVAR + .. TODO better example - Warns that the specified signal or parameter is never used/consumed. + Warns that the specified genvar is never used/consumed. + + +.. option:: UNUSEDPARAM + + .. TODO better example + + Warns that the specified parameter is never used/consumed. + + +.. option:: UNUSEDSIGNAL + + .. TODO better example + + Warns that the specified signal is never used/consumed. Verilator is fairly liberal in the usage calculations; making a signal public, a signal matching the :vlopt:`--unused-regexp` option (default "\*unused\*" or accessing only a single array element marks the entire diff --git a/src/V3Config.cpp b/src/V3Config.cpp index abfd4f295..95e2b557e 100644 --- a/src/V3Config.cpp +++ b/src/V3Config.cpp @@ -332,8 +332,11 @@ public: } bool waive(V3ErrorCode code, const string& match) { for (const auto& itr : m_waivers) { - if (((itr.first == code) || (itr.first == V3ErrorCode::I_LINT)) - && VString::wildmatch(match, itr.second)) { + if ( ( (itr.first == code) + || (itr.first == V3ErrorCode::I_LINT) + || (code.unusedError() && itr.first == V3ErrorCode::I_UNUSED) + ) + && VString::wildmatch(match, itr.second)) { return true; } } diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index 324e6f7a4..8a5781ddc 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -128,7 +128,7 @@ private: AstVar* const varp = new AstVar(incp->fileline(), VVarType::MODULETEMP, trace_var_name, incp->findUInt32DType()); varp->trace(true); - varp->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true); + varp->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true); m_modp->addStmtsp(varp); UINFO(5, "New coverage trace: " << varp << endl); AstAssign* const assp = new AstAssign( @@ -283,7 +283,7 @@ private: const string newvarname = std::string{"__Vtogcov__"} + nodep->shortName(); AstVar* const chgVarp = new AstVar(nodep->fileline(), VVarType::MODULETEMP, newvarname, nodep); - chgVarp->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true); + chgVarp->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true); m_modp->addStmtsp(chgVarp); // Create bucket for each dimension * bit. diff --git a/src/V3Error.h b/src/V3Error.h index b68941e55..b8a90b6e0 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -51,6 +51,7 @@ public: I_COVERAGE, // Coverage is on/off from /*verilator coverage_on/off*/ I_TRACING, // Tracing is on/off from /*verilator tracing_on/off*/ I_LINT, // All lint messages + I_UNUSED, // Unused genvar, parameter or signal message (Backward Compatibility) I_DEF_NETTYPE_WIRE, // `default_nettype is WIRE (false=NONE) I_TIMING, // Enable timing from /*verilator timing_on/off*/ // Error codes: @@ -134,7 +135,9 @@ public: UNOPTTHREADS, // Thread partitioner unable to fill all requested threads UNPACKED, // Unsupported unpacked UNSIGNED, // Comparison is constant due to unsigned arithmetic - UNUSED, // No receivers + UNUSEDGENVAR, // No receivers for genvar + UNUSEDPARAM, // No receivers for parameters + UNUSEDSIGNAL, // No receivers for signals USERERROR, // Elaboration time $error USERFATAL, // Elaboration time $fatal USERINFO, // Elaboration time $info @@ -164,7 +167,7 @@ public: // Leading spaces indicate it can't be disabled. " MIN", " INFO", " FATAL", " FATALEXIT", " FATALSRC", " ERROR", " FIRST_NAMED", // Boolean - " I_CELLDEFINE", " I_COVERAGE", " I_TRACING", " I_LINT", " I_DEF_NETTYPE_WIRE", " I_TIMING", + " I_CELLDEFINE", " I_COVERAGE", " I_TRACING", " I_LINT", " I_UNUSED", " I_DEF_NETTYPE_WIRE", " I_TIMING", // Errors "ENCAPSULATED", "PORTSHORT", "UNSUPPORTED", "TASKNSVAR", "NEEDTIMINGOPT", "NOTIMING", // Warnings @@ -185,7 +188,7 @@ public: "SELRANGE", "SHORTREAL", "SPLITVAR", "STMTDLY", "SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN", "UNOPT", "UNOPTFLAT", "UNOPTTHREADS", - "UNPACKED", "UNSIGNED", "UNUSED", + "UNPACKED", "UNSIGNED", "UNUSEDGENVAR", "UNUSEDPARAM", "UNUSEDSIGNAL", "USERERROR", "USERFATAL", "USERINFO", "USERWARN", "VARHIDDEN", "WAITCONST", "WIDTH", "WIDTHCONCAT", "ZERODLY", " MAX" @@ -224,9 +227,17 @@ public: return (m_e == ASSIGNDLY // More than style, but for backward compatibility || m_e == BLKSEQ || m_e == DEFPARAM || m_e == DECLFILENAME || m_e == EOFNEWLINE || m_e == IMPORTSTAR || m_e == INCABSPATH || m_e == PINCONNECTEMPTY - || m_e == PINNOCONNECT || m_e == SYNCASYNCNET || m_e == UNDRIVEN || m_e == UNUSED + || m_e == PINNOCONNECT || m_e == SYNCASYNCNET || m_e == UNDRIVEN + || m_e == UNUSEDGENVAR || m_e == UNUSEDPARAM || m_e == UNUSEDSIGNAL || m_e == VARHIDDEN); } + // Warnings that are unused only + bool unusedError() const { + return (m_e == UNUSEDGENVAR || m_e == UNUSEDPARAM || m_e == UNUSEDSIGNAL); + } + static bool unusedMsg(const char* msgp) { + return 0 == VL_STRCASECMP(msgp, "UNUSED"); + } }; constexpr bool operator==(const V3ErrorCode& lhs, const V3ErrorCode& rhs) { return lhs.m_e == rhs.m_e; diff --git a/src/V3FileLine.cpp b/src/V3FileLine.cpp index 8ebb88436..7d00b2a9f 100644 --- a/src/V3FileLine.cpp +++ b/src/V3FileLine.cpp @@ -332,7 +332,15 @@ std::ostream& operator<<(std::ostream& os, FileLine* fileline) { } bool FileLine::warnOff(const string& msg, bool flag) { - const V3ErrorCode code(msg.c_str()); + const char *cmsg = msg.c_str(); + // Backward compatibility with msg="UNUSED" + if (V3ErrorCode::unusedMsg(cmsg)) { + warnOff(V3ErrorCode::UNUSEDGENVAR, flag); + warnOff(V3ErrorCode::UNUSEDPARAM , flag); + warnOff(V3ErrorCode::UNUSEDSIGNAL, flag); + return true; + } + const V3ErrorCode code(cmsg); if (code < V3ErrorCode::EC_FIRST_WARN) { return false; } else { @@ -355,12 +363,21 @@ void FileLine::warnStyleOff(bool flag) { } } +void FileLine::warnUnusedOff(bool flag) { + warnOff(V3ErrorCode::UNUSEDGENVAR, flag); + warnOff(V3ErrorCode::UNUSEDPARAM, flag); + warnOff(V3ErrorCode::UNUSEDSIGNAL, flag); +} + bool FileLine::warnIsOff(V3ErrorCode code) const { if (!msgEn().test(code)) return true; if (!defaultFileLine().msgEn().test(code)) return true; // Global overrides local if ((code.lintError() || code.styleError()) && !msgEn().test(V3ErrorCode::I_LINT)) { return true; } + if ((code.unusedError()) && !msgEn().test(V3ErrorCode::I_UNUSED)) { + return true; + } return false; } diff --git a/src/V3FileLine.h b/src/V3FileLine.h index f6c53bfb1..b2cdb5823 100644 --- a/src/V3FileLine.h +++ b/src/V3FileLine.h @@ -264,6 +264,7 @@ public: bool warnIsOff(V3ErrorCode code) const; void warnLintOff(bool flag); void warnStyleOff(bool flag); + void warnUnusedOff(bool flag); void warnStateFrom(const FileLine& from) { m_msgEnIdx = from.m_msgEnIdx; } void warnResetDefault() { warnStateFrom(defaultFileLine()); } bool lastWarnWaived() const { return m_waive; } @@ -284,6 +285,7 @@ public: static string builtInFilename() { return ""; } static void globalWarnLintOff(bool flag) { defaultFileLine().warnLintOff(flag); } static void globalWarnStyleOff(bool flag) { defaultFileLine().warnStyleOff(flag); } + static void globalWarnUnusedOff(bool flag) { defaultFileLine().warnUnusedOff(flag); } static void globalWarnOff(V3ErrorCode code, bool flag) { defaultFileLine().warnOff(code, flag); } diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 3ec99660f..c89d06975 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1472,6 +1472,11 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char FileLine::globalWarnLintOff(false); FileLine::globalWarnStyleOff(false); }); + DECL_OPTION("-Werror-UNUSED", CbCall, []() { + V3Error::pretendError(V3ErrorCode::UNUSEDGENVAR, true); + V3Error::pretendError(V3ErrorCode::UNUSEDPARAM, true); + V3Error::pretendError(V3ErrorCode::UNUSEDSIGNAL, true); + }); DECL_OPTION("-Werror-", CbPartialMatch, [this, fl](const char* optp) { const V3ErrorCode code(optp); if (code == V3ErrorCode::EC_ERROR) { @@ -1502,6 +1507,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char FileLine::globalWarnStyleOff(true); }); DECL_OPTION("-Wno-style", CbCall, []() { FileLine::globalWarnStyleOff(true); }); + DECL_OPTION("-Wno-UNUSED", CbCall, []() { FileLine::globalWarnUnusedOff(true); }); DECL_OPTION("-Wwarn-", CbPartialMatch, [this, fl, &parser](const char* optp) { const V3ErrorCode code{optp}; if (code == V3ErrorCode::EC_ERROR) { @@ -1517,6 +1523,12 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char }); DECL_OPTION("-Wwarn-lint", CbCall, []() { FileLine::globalWarnLintOff(false); }); DECL_OPTION("-Wwarn-style", CbCall, []() { FileLine::globalWarnStyleOff(false); }); + DECL_OPTION("-Wwarn-UNUSED", CbCall, []() { + FileLine::globalWarnUnusedOff(false); + V3Error::pretendError(V3ErrorCode::UNUSEDGENVAR, false); + V3Error::pretendError(V3ErrorCode::UNUSEDSIGNAL, false); + V3Error::pretendError(V3ErrorCode::UNUSEDPARAM, false); + }); DECL_OPTION("-waiver-output", Set, &m_waiverOutput); DECL_OPTION("-x-assign", CbVal, [this, fl](const char* valp) { diff --git a/src/V3Undriven.cpp b/src/V3Undriven.cpp index 716005892..ce6498f63 100644 --- a/src/V3Undriven.cpp +++ b/src/V3Undriven.cpp @@ -151,7 +151,18 @@ public: void reportViolations() { // Combine bits into overall state AstVar* const nodep = m_varp; - { + + if (nodep->isGenVar()) { // Genvar + if (!nodep->isIfaceRef() && !nodep->isUsedParam() && !unusedMatch(nodep)) { + nodep->v3warn(UNUSEDGENVAR, "Genvar is not used: " << nodep->prettyNameQ()); + nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDGENVAR, true); // Warn only once + } + } else if(nodep->isParam()) { // Parameter + if (!nodep->isIfaceRef() && !nodep->isUsedParam() && !unusedMatch(nodep)) { + nodep->v3warn(UNUSEDPARAM, "Parameter is not used: " << nodep->prettyNameQ()); + nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDPARAM, true); // Warn only once + } + } else { // Signal bool allU = true; bool allD = true; bool anyU = m_wholeFlags[FLAG_USED]; @@ -170,13 +181,8 @@ public: anyDnotU |= !used && driv; anynotDU |= !used && !driv; } - if ((nodep->isGenVar() || nodep->isParam()) && nodep->isUsedParam()) - allD = allU = true; if (allU) m_wholeFlags[FLAG_USED] = true; if (allD) m_wholeFlags[FLAG_DRIVEN] = true; - const char* const what = nodep->isParam() ? "parameter" - : nodep->isGenVar() ? "genvar" - : "signal"; // Test results if (nodep->isIfaceRef()) { // For interface top level we don't do any tracking @@ -188,43 +194,41 @@ public: // UNDRIVEN is considered more serious - as is more likely a bug, // thus undriven+unused bits get UNUSED warnings, as they're not as buggy. if (!unusedMatch(nodep)) { - nodep->v3warn(UNUSED, ucfirst(what) << " is not driven, nor used: " + nodep->v3warn(UNUSEDSIGNAL, "Signal is not driven, nor used: " << nodep->prettyNameQ()); - nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true); // Warn only once + nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true); // Warn only once } } else if (allD && !anyU) { if (!unusedMatch(nodep)) { - nodep->v3warn(UNUSED, ucfirst(what) - << " is not used: " << nodep->prettyNameQ()); - nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true); // Warn only once + nodep->v3warn(UNUSEDSIGNAL, "Signal is not used: " + << nodep->prettyNameQ()); + nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true); // Warn only once } } else if (!anyD && allU) { - nodep->v3warn(UNDRIVEN, ucfirst(what) - << " is not driven: " << nodep->prettyNameQ()); + nodep->v3warn(UNDRIVEN, "Signal is not driven: " + << nodep->prettyNameQ()); nodep->fileline()->modifyWarnOff(V3ErrorCode::UNDRIVEN, true); // Warn only once } else { // Bits have different dispositions bool setU = false; bool setD = false; if (anynotDU && !unusedMatch(nodep)) { - nodep->v3warn(UNUSED, "Bits of " << what << " are not driven, nor used: " + nodep->v3warn(UNUSEDSIGNAL, "Bits of signal are not driven, nor used: " << nodep->prettyNameQ() << bitNames(BN_BOTH)); setU = true; } if (anyDnotU && !unusedMatch(nodep)) { - nodep->v3warn(UNUSED, "Bits of " << what - << " are not used: " << nodep->prettyNameQ() - << bitNames(BN_UNUSED)); + nodep->v3warn(UNUSEDSIGNAL, "Bits of signal are not used: " + << nodep->prettyNameQ() << bitNames(BN_UNUSED)); setU = true; } if (anyUnotD) { - nodep->v3warn(UNDRIVEN, - "Bits of " << what << " are not driven: " << nodep->prettyNameQ() - << bitNames(BN_UNDRIVEN)); + nodep->v3warn(UNDRIVEN, "Bits of signal are not driven: " + << nodep->prettyNameQ() << bitNames(BN_UNDRIVEN)); setD = true; } if (setU) { // Warn only once - nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSED, true); + nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true); } if (setD) { // Warn only once nodep->fileline()->modifyWarnOff(V3ErrorCode::UNDRIVEN, true); diff --git a/src/verilog.y b/src/verilog.y index 47ea44310..86f8edc58 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -6637,7 +6637,9 @@ vltOffFront: | yVLT_TRACING_OFF { $$ = V3ErrorCode::I_TRACING; } | yVLT_LINT_OFF { $$ = V3ErrorCode::I_LINT; } | yVLT_LINT_OFF yVLT_D_RULE idAny - { $$ = V3ErrorCode{(*$3).c_str()}; + { const char *codemsg = (*$3).c_str(); + if (V3ErrorCode::unusedMsg(codemsg)) {$$ = V3ErrorCode::I_UNUSED; } + else {$$ = V3ErrorCode{codemsg}; } if ($$ == V3ErrorCode::EC_ERROR) { $1->v3error("Unknown Error Code: " << *$3); } } ; @@ -6647,7 +6649,9 @@ vltOnFront: | yVLT_TRACING_ON { $$ = V3ErrorCode::I_TRACING; } | yVLT_LINT_ON { $$ = V3ErrorCode::I_LINT; } | yVLT_LINT_ON yVLT_D_RULE idAny - { $$ = V3ErrorCode{(*$3).c_str()}; + { const char *codemsg = (*$3).c_str(); + if (V3ErrorCode::unusedMsg(codemsg)) {$$ = V3ErrorCode::I_UNUSED; } + else {$$ = V3ErrorCode{codemsg}; } if ($$ == V3ErrorCode::EC_ERROR) { $1->v3error("Unknown Error Code: " << *$3); } } ; diff --git a/test_regress/t/t_delay_stmtdly_bad.out b/test_regress/t/t_delay_stmtdly_bad.out index 1a38b7b3d..ed8235f57 100644 --- a/test_regress/t/t_delay_stmtdly_bad.out +++ b/test_regress/t/t_delay_stmtdly_bad.out @@ -24,12 +24,12 @@ : ... In instance t 50 | #100 $finish; | ^ -%Warning-UNUSED: t/t_delay.v:23:12: Signal is not used: 'dly_s' - : ... In instance t +%Warning-UNUSEDSIGNAL: t/t_delay.v:23:12: Signal is not used: 'dly_s' + : ... In instance t 23 | dly_s_t dly_s; | ^~~~~ -%Warning-UNUSED: t/t_delay.v:57:13: Signal is not used: 'delay' - : ... In instance t.sub +%Warning-UNUSEDSIGNAL: t/t_delay.v:57:13: Signal is not used: 'delay' + : ... In instance t.sub 57 | realtime delay = 2.3; | ^~~~~ %Warning-BLKSEQ: t/t_delay.v:43:20: Blocking assignment '=' in sequential logic process diff --git a/test_regress/t/t_flag_context_bad.out b/test_regress/t/t_flag_context_bad.out index bfe90fd33..13052d8dd 100644 --- a/test_regress/t/t_flag_context_bad.out +++ b/test_regress/t/t_flag_context_bad.out @@ -2,6 +2,6 @@ : ... In instance t ... For warning description see https://verilator.org/warn/WIDTH?v=latest ... Use "/* verilator lint_off WIDTH */" and lint_on around source to disable this message. -%Warning-UNUSED: t/t_flag_context_bad.v:9:15: Signal is not used: 'foo' - : ... In instance t +%Warning-UNUSEDSIGNAL: t/t_flag_context_bad.v:9:15: Signal is not used: 'foo' + : ... In instance t %Error: Exiting due to diff --git a/test_regress/t/t_lint_once_bad.out b/test_regress/t/t_lint_once_bad.out index 04dca3c21..2f4ffa38d 100644 --- a/test_regress/t/t_lint_once_bad.out +++ b/test_regress/t/t_lint_once_bad.out @@ -1,11 +1,11 @@ -%Warning-UNUSED: t/t_lint_once_bad.v:19:14: Signal is not driven, nor used: 'unus1' - : ... In instance t.sub1 +%Warning-UNUSEDSIGNAL: t/t_lint_once_bad.v:19:14: Signal is not driven, nor used: 'unus1' + : ... In instance t.sub1 19 | reg [A:0] unus1; reg [A:0] unus2; | ^~~~~ - ... For warning description see https://verilator.org/warn/UNUSED?v=latest - ... Use "/* verilator lint_off UNUSED */" and lint_on around source to disable this message. -%Warning-UNUSED: t/t_lint_once_bad.v:19:34: Signal is not driven, nor used: 'unus2' - : ... In instance t.sub1 + ... For warning description see https://verilator.org/warn/UNUSEDSIGNAL?v=latest + ... Use "/* verilator lint_off UNUSEDSIGNAL */" and lint_on around source to disable this message. +%Warning-UNUSEDSIGNAL: t/t_lint_once_bad.v:19:34: Signal is not driven, nor used: 'unus2' + : ... In instance t.sub1 19 | reg [A:0] unus1; reg [A:0] unus2; | ^~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_lint_unused_bad.out b/test_regress/t/t_lint_unused_bad.out index e8ebe7df3..4c2025413 100644 --- a/test_regress/t/t_lint_unused_bad.out +++ b/test_regress/t/t_lint_unused_bad.out @@ -1,39 +1,63 @@ -%Warning-UNUSED: t/t_lint_unused_bad.v:17:15: Bits of signal are not used: 'assunu1'[5:1] - : ... In instance t.sub +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:17:15: Bits of signal are not used: 'assunu1'[5:1] + : ... In instance t.sub 17 | wire [5:0] assunu1 = 0; | ^~~~~~~ - ... For warning description see https://verilator.org/warn/UNUSED?v=latest - ... Use "/* verilator lint_off UNUSED */" and lint_on around source to disable this message. + ... For warning description see https://verilator.org/warn/UNUSEDSIGNAL?v=latest + ... Use "/* verilator lint_off UNUSEDSIGNAL */" and lint_on around source to disable this message. %Warning-UNDRIVEN: t/t_lint_unused_bad.v:21:17: Bits of signal are not driven: 'udrb2'[14:13,11] : ... In instance t.sub 21 | wire [15:10] udrb2; | ^~~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:26:15: Signal is not driven, nor used: 'unu3' - : ... In instance t.sub +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:26:15: Signal is not driven, nor used: 'unu3' + : ... In instance t.sub 26 | wire unu3; | ^~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:28:15: Bits of signal are not driven, nor used: 'mixed'[3] - : ... In instance t.sub +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:28:15: Bits of signal are not driven, nor used: 'mixed'[3] + : ... In instance t.sub 28 | wire [3:0] mixed; | ^~~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:28:15: Bits of signal are not used: 'mixed'[2] - : ... In instance t.sub +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:28:15: Bits of signal are not used: 'mixed'[2] + : ... In instance t.sub 28 | wire [3:0] mixed; | ^~~~~ %Warning-UNDRIVEN: t/t_lint_unused_bad.v:28:15: Bits of signal are not driven: 'mixed'[1] : ... In instance t.sub 28 | wire [3:0] mixed; | ^~~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:37:14: Parameter is not used: 'UNUSED_P' - : ... In instance t.sub +%Warning-UNUSEDPARAM: t/t_lint_unused_bad.v:37:14: Parameter is not used: 'UNUSED_P' + : ... In instance t.sub 37 | parameter UNUSED_P = 1; | ^~~~~~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:38:15: Parameter is not used: 'UNUSED_LP' - : ... In instance t.sub +%Warning-UNUSEDPARAM: t/t_lint_unused_bad.v:38:15: Parameter is not used: 'UNUSED_LP' + : ... In instance t.sub 38 | localparam UNUSED_LP = 2; | ^~~~~~~~~ -%Warning-UNUSED: t/t_lint_unused_bad.v:40:15: Genvar is not driven, nor used: 'unused_gv' - : ... In instance t.sub +%Warning-UNUSEDGENVAR: t/t_lint_unused_bad.v:40:15: Genvar is not used: 'unused_gv' + : ... In instance t.sub 40 | genvar unused_gv; | ^~~~~~~~~ +%Warning-UNUSEDPARAM: t/t_lint_unused_bad.v:45:15: Parameter is not used: 'linter_param1' + : ... In instance t.sub + 45 | localparam linter_param1 = 1; + | ^~~~~~~~~~~~~ +%Warning-UNUSEDGENVAR: t/t_lint_unused_bad.v:46:11: Genvar is not used: 'linter_genvar1' + : ... In instance t.sub + 46 | genvar linter_genvar1; + | ^~~~~~~~~~~~~~ +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:50:9: Signal is not driven, nor used: 'linter_sig2' + : ... In instance t.sub + 50 | wire linter_sig2; + | ^~~~~~~~~~~ +%Warning-UNUSEDGENVAR: t/t_lint_unused_bad.v:52:11: Genvar is not used: 'linter_genvar2' + : ... In instance t.sub + 52 | genvar linter_genvar2; + | ^~~~~~~~~~~~~~ +%Warning-UNUSEDSIGNAL: t/t_lint_unused_bad.v:56:9: Signal is not driven, nor used: 'linter_sig3' + : ... In instance t.sub + 56 | wire linter_sig3; + | ^~~~~~~~~~~ +%Warning-UNUSEDPARAM: t/t_lint_unused_bad.v:57:15: Parameter is not used: 'linter_param3' + : ... In instance t.sub + 57 | localparam linter_param3 = 3; + | ^~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_lint_unused_bad.v b/test_regress/t/t_lint_unused_bad.v index ade5152d3..cb2908289 100644 --- a/test_regress/t/t_lint_unused_bad.v +++ b/test_regress/t/t_lint_unused_bad.v @@ -40,6 +40,30 @@ module sub; genvar unused_gv; genvar ok_gv; + // verilator lint_off UNUSEDSIGNAL + wire linter_sig1; + localparam linter_param1 = 1; + genvar linter_genvar1; + // verilator lint_on UNUSEDSIGNAL + + // verilator lint_off UNUSEDPARAM + wire linter_sig2; + localparam linter_param2 = 2; + genvar linter_genvar2; + // verilator lint_on UNUSEDPARAM + + // verilator lint_off UNUSEDGENVAR + wire linter_sig3; + localparam linter_param3 = 3; + genvar linter_genvar3; + // verilator lint_on UNUSEDGENVAR + + // verilator lint_off UNUSED + wire linter_sig4; + localparam linter_param4 = 4; + genvar linter_genvar4; + // verilator lint_on UNUSED + initial begin if (0 && assunu1[0] != 0 && udrb2 != 0) begin end if (0 && assunub2[THREE] && assunub2[1:0]!=0) begin end diff --git a/test_regress/t/t_lint_unused_iface_bad.out b/test_regress/t/t_lint_unused_iface_bad.out index 2615f6433..ba0a9294e 100644 --- a/test_regress/t/t_lint_unused_iface_bad.out +++ b/test_regress/t/t_lint_unused_iface_bad.out @@ -4,8 +4,8 @@ | ^~~~~~~~ ... For warning description see https://verilator.org/warn/UNDRIVEN?v=latest ... Use "/* verilator lint_off UNDRIVEN */" and lint_on around source to disable this message. -%Warning-UNUSED: t/t_lint_unused_iface_bad.v:9:10: Signal is not used: 'sig_uusd' - : ... In instance t.sub +%Warning-UNUSEDSIGNAL: t/t_lint_unused_iface_bad.v:9:10: Signal is not used: 'sig_uusd' + : ... In instance t.sub 9 | logic sig_uusd; | ^~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_waiveroutput.out b/test_regress/t/t_waiveroutput.out index 01132a95d..e0b0f6767 100644 --- a/test_regress/t/t_waiveroutput.out +++ b/test_regress/t/t_waiveroutput.out @@ -9,5 +9,5 @@ // lint_off -rule WIDTH -file "*t/t_waiveroutput.v" -match "Operator ASSIGN expects 1 bits on the Assign RHS, but Assign RHS's CONST '2'h3' generates 2 bits." -// lint_off -rule UNUSED -file "*t/t_waiveroutput.v" -match "Signal is not used: 'width_warn'" +// lint_off -rule UNUSEDSIGNAL -file "*t/t_waiveroutput.v" -match "Signal is not used: 'width_warn'" From c05784776072f00826192603d3f3b250e2567e1d Mon Sep 17 00:00:00 2001 From: github action Date: Mon, 17 Oct 2022 23:52:01 +0000 Subject: [PATCH 125/177] Apply 'make format' --- src/V3Config.cpp | 8 +++----- src/V3Error.h | 4 +--- src/V3FileLine.cpp | 8 +++----- src/V3Undriven.cpp | 37 ++++++++++++++++++++----------------- 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/V3Config.cpp b/src/V3Config.cpp index 95e2b557e..9419a6e6b 100644 --- a/src/V3Config.cpp +++ b/src/V3Config.cpp @@ -332,11 +332,9 @@ public: } bool waive(V3ErrorCode code, const string& match) { for (const auto& itr : m_waivers) { - if ( ( (itr.first == code) - || (itr.first == V3ErrorCode::I_LINT) - || (code.unusedError() && itr.first == V3ErrorCode::I_UNUSED) - ) - && VString::wildmatch(match, itr.second)) { + if (((itr.first == code) || (itr.first == V3ErrorCode::I_LINT) + || (code.unusedError() && itr.first == V3ErrorCode::I_UNUSED)) + && VString::wildmatch(match, itr.second)) { return true; } } diff --git a/src/V3Error.h b/src/V3Error.h index b8a90b6e0..96244a46b 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -235,9 +235,7 @@ public: bool unusedError() const { return (m_e == UNUSEDGENVAR || m_e == UNUSEDPARAM || m_e == UNUSEDSIGNAL); } - static bool unusedMsg(const char* msgp) { - return 0 == VL_STRCASECMP(msgp, "UNUSED"); - } + static bool unusedMsg(const char* msgp) { return 0 == VL_STRCASECMP(msgp, "UNUSED"); } }; constexpr bool operator==(const V3ErrorCode& lhs, const V3ErrorCode& rhs) { return lhs.m_e == rhs.m_e; diff --git a/src/V3FileLine.cpp b/src/V3FileLine.cpp index 7d00b2a9f..f2750e2a4 100644 --- a/src/V3FileLine.cpp +++ b/src/V3FileLine.cpp @@ -332,11 +332,11 @@ std::ostream& operator<<(std::ostream& os, FileLine* fileline) { } bool FileLine::warnOff(const string& msg, bool flag) { - const char *cmsg = msg.c_str(); + const char* cmsg = msg.c_str(); // Backward compatibility with msg="UNUSED" if (V3ErrorCode::unusedMsg(cmsg)) { warnOff(V3ErrorCode::UNUSEDGENVAR, flag); - warnOff(V3ErrorCode::UNUSEDPARAM , flag); + warnOff(V3ErrorCode::UNUSEDPARAM, flag); warnOff(V3ErrorCode::UNUSEDSIGNAL, flag); return true; } @@ -375,9 +375,7 @@ bool FileLine::warnIsOff(V3ErrorCode code) const { if ((code.lintError() || code.styleError()) && !msgEn().test(V3ErrorCode::I_LINT)) { return true; } - if ((code.unusedError()) && !msgEn().test(V3ErrorCode::I_UNUSED)) { - return true; - } + if ((code.unusedError()) && !msgEn().test(V3ErrorCode::I_UNUSED)) { return true; } return false; } diff --git a/src/V3Undriven.cpp b/src/V3Undriven.cpp index ce6498f63..c2a757a9b 100644 --- a/src/V3Undriven.cpp +++ b/src/V3Undriven.cpp @@ -152,17 +152,19 @@ public: // Combine bits into overall state AstVar* const nodep = m_varp; - if (nodep->isGenVar()) { // Genvar + if (nodep->isGenVar()) { // Genvar if (!nodep->isIfaceRef() && !nodep->isUsedParam() && !unusedMatch(nodep)) { nodep->v3warn(UNUSEDGENVAR, "Genvar is not used: " << nodep->prettyNameQ()); - nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDGENVAR, true); // Warn only once + nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDGENVAR, + true); // Warn only once } - } else if(nodep->isParam()) { // Parameter + } else if (nodep->isParam()) { // Parameter if (!nodep->isIfaceRef() && !nodep->isUsedParam() && !unusedMatch(nodep)) { nodep->v3warn(UNUSEDPARAM, "Parameter is not used: " << nodep->prettyNameQ()); - nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDPARAM, true); // Warn only once + nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDPARAM, + true); // Warn only once } - } else { // Signal + } else { // Signal bool allU = true; bool allD = true; bool anyU = m_wholeFlags[FLAG_USED]; @@ -194,19 +196,19 @@ public: // UNDRIVEN is considered more serious - as is more likely a bug, // thus undriven+unused bits get UNUSED warnings, as they're not as buggy. if (!unusedMatch(nodep)) { - nodep->v3warn(UNUSEDSIGNAL, "Signal is not driven, nor used: " - << nodep->prettyNameQ()); - nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true); // Warn only once + nodep->v3warn(UNUSEDSIGNAL, + "Signal is not driven, nor used: " << nodep->prettyNameQ()); + nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, + true); // Warn only once } } else if (allD && !anyU) { if (!unusedMatch(nodep)) { - nodep->v3warn(UNUSEDSIGNAL, "Signal is not used: " - << nodep->prettyNameQ()); - nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, true); // Warn only once + nodep->v3warn(UNUSEDSIGNAL, "Signal is not used: " << nodep->prettyNameQ()); + nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDSIGNAL, + true); // Warn only once } } else if (!anyD && allU) { - nodep->v3warn(UNDRIVEN, "Signal is not driven: " - << nodep->prettyNameQ()); + nodep->v3warn(UNDRIVEN, "Signal is not driven: " << nodep->prettyNameQ()); nodep->fileline()->modifyWarnOff(V3ErrorCode::UNDRIVEN, true); // Warn only once } else { // Bits have different dispositions @@ -214,17 +216,18 @@ public: bool setD = false; if (anynotDU && !unusedMatch(nodep)) { nodep->v3warn(UNUSEDSIGNAL, "Bits of signal are not driven, nor used: " - << nodep->prettyNameQ() << bitNames(BN_BOTH)); + << nodep->prettyNameQ() << bitNames(BN_BOTH)); setU = true; } if (anyDnotU && !unusedMatch(nodep)) { - nodep->v3warn(UNUSEDSIGNAL, "Bits of signal are not used: " - << nodep->prettyNameQ() << bitNames(BN_UNUSED)); + nodep->v3warn(UNUSEDSIGNAL, + "Bits of signal are not used: " << nodep->prettyNameQ() + << bitNames(BN_UNUSED)); setU = true; } if (anyUnotD) { nodep->v3warn(UNDRIVEN, "Bits of signal are not driven: " - << nodep->prettyNameQ() << bitNames(BN_UNDRIVEN)); + << nodep->prettyNameQ() << bitNames(BN_UNDRIVEN)); setD = true; } if (setU) { // Warn only once From 54e3f15dcef2d5ed96cc3f2099e4786155fa3031 Mon Sep 17 00:00:00 2001 From: Kamil Rakoczy Date: Tue, 18 Oct 2022 11:15:33 +0200 Subject: [PATCH 126/177] Internals: Add attribute when using clang to VL_MT_SAFE and VL_MT_UNSAFE (#3685) --- include/verilatedos.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/include/verilatedos.h b/include/verilatedos.h index d4d64ea96..de89e822e 100644 --- a/include/verilatedos.h +++ b/include/verilatedos.h @@ -163,14 +163,22 @@ // Comment tag that Function is pure (and thus also VL_MT_SAFE) #define VL_PURE // Comment tag that function is threadsafe when VL_THREADED -#define VL_MT_SAFE +#if defined(__clang__) +# define VL_MT_SAFE __attribute__((annotate("MT_SAFE"))) +#else +# define VL_MT_SAFE +#endif // Comment tag that function is threadsafe when VL_THREADED, only // during normal operation (post-init) #define VL_MT_SAFE_POSTINIT // Attribute that function is clang threadsafe and uses given mutex #define VL_MT_SAFE_EXCLUDES(mutex) VL_EXCLUDES(mutex) // Comment tag that function is not threadsafe when VL_THREADED -#define VL_MT_UNSAFE +#if defined(__clang__) +# define VL_MT_UNSAFE __attribute__((annotate("MT_UNSAFE"))) +#else +# define VL_MT_UNSAFE +#endif // Comment tag that function is not threadsafe when VL_THREADED, // protected to make sure single-caller #define VL_MT_UNSAFE_ONE From b6c116d4bfd8bed5da3ccb447e9e2d48529f87ce Mon Sep 17 00:00:00 2001 From: Kamil Rakoczy Date: Tue, 18 Oct 2022 23:07:09 +0200 Subject: [PATCH 127/177] Internals: Add VL_MT_SAFE annotations to const functions (#3681) --- include/verilated.h | 12 ++-- src/V3Ast.cpp | 2 +- src/V3Ast.h | 153 +++++++++++++++++++++++-------------------- src/V3AstInlines.h | 18 ++--- src/V3AstNodeDType.h | 149 ++++++++++++++++++++++------------------- src/V3AstNodeMath.h | 4 +- src/V3AstNodeOther.h | 68 +++++++++---------- src/V3AstNodes.cpp | 8 +-- src/V3Broken.cpp | 2 +- src/V3EmitCBase.h | 2 +- src/V3Error.h | 28 ++++---- src/V3FileLine.cpp | 18 ++--- src/V3FileLine.h | 50 +++++++------- src/V3Global.h | 6 +- src/V3Hash.cpp | 2 +- src/V3Hash.h | 6 +- src/V3LangCode.h | 2 +- src/V3Number.cpp | 8 +-- src/V3Number.h | 94 +++++++++++++------------- src/V3Options.cpp | 12 ++-- src/V3Options.h | 56 ++++++++-------- src/V3Stats.h | 14 ++-- src/astgen | 8 +-- 23 files changed, 380 insertions(+), 342 deletions(-) diff --git a/include/verilated.h b/include/verilated.h index ed7f7955a..66ae6017b 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -164,7 +164,7 @@ public: ~VerilatedMutex() = default; const VerilatedMutex& operator!() const { return *this; } // For -fthread_safety /// Acquire/lock mutex - void lock() VL_ACQUIRE() { + void lock() VL_ACQUIRE() VL_MT_SAFE { // Try to acquire the lock by spinning. If the wait is short, // avoids a trap to the OS plus OS scheduler overhead. if (VL_LIKELY(try_lock())) return; // Short circuit loop @@ -176,9 +176,9 @@ public: m_mutex.lock(); } /// Release/unlock mutex - void unlock() VL_RELEASE() { m_mutex.unlock(); } + void unlock() VL_RELEASE() VL_MT_SAFE { m_mutex.unlock(); } /// Try to acquire mutex. Returns true on success, and false on failure. - bool try_lock() VL_TRY_ACQUIRE(true) { return m_mutex.try_lock(); } + bool try_lock() VL_TRY_ACQUIRE(true) VL_MT_SAFE { return m_mutex.try_lock(); } }; /// Lock guard for mutex (ala std::unique_lock), wrapped to allow -fthread_safety checks @@ -190,16 +190,16 @@ private: public: /// Construct and hold given mutex lock until destruction or unlock() - explicit VerilatedLockGuard(VerilatedMutex& mutexr) VL_ACQUIRE(mutexr) + explicit VerilatedLockGuard(VerilatedMutex& mutexr) VL_ACQUIRE(mutexr) VL_MT_SAFE : m_mutexr(mutexr) { // Need () or GCC 4.8 false warning m_mutexr.lock(); } /// Destruct and unlock the mutex ~VerilatedLockGuard() VL_RELEASE() { m_mutexr.unlock(); } /// Unlock the mutex - void lock() VL_ACQUIRE() { m_mutexr.lock(); } + void lock() VL_ACQUIRE() VL_MT_SAFE { m_mutexr.lock(); } /// Lock the mutex - void unlock() VL_RELEASE() { m_mutexr.unlock(); } + void unlock() VL_RELEASE() VL_MT_SAFE { m_mutexr.unlock(); } }; #else // !VL_THREADED diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 27b52f353..0c9f0442b 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -1184,7 +1184,7 @@ void AstNode::dumpTreeDotFile(const string& filename, bool append, bool doDump) } } -void AstNode::v3errorEndFatal(std::ostringstream& str) const { +void AstNode::v3errorEndFatal(std::ostringstream& str) const VL_MT_SAFE { v3errorEnd(str); assert(0); // LCOV_EXCL_LINE VL_UNREACHABLE; diff --git a/src/V3Ast.h b/src/V3Ast.h index dccd8ddc5..3814493ae 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -95,13 +95,14 @@ public: // cppcheck-suppress uninitVar // responsibility of each subclass VNType() = default; // cppcheck-suppress noExplicitConstructor - constexpr VNType(en _e) - : m_e{_e} {} + constexpr VNType(en _e) VL_MT_SAFE : m_e{_e} {} explicit VNType(int _e) : m_e(static_cast(_e)) {} // Need () or GCC 4.8 false warning - constexpr operator en() const { return m_e; } + constexpr operator en() const VL_MT_SAFE { return m_e; } }; -constexpr bool operator==(const VNType& lhs, const VNType& rhs) { return lhs.m_e == rhs.m_e; } +constexpr bool operator==(const VNType& lhs, const VNType& rhs) VL_MT_SAFE { + return lhs.m_e == rhs.m_e; +} constexpr bool operator==(const VNType& lhs, VNType::en rhs) { return lhs.m_e == rhs; } constexpr bool operator==(VNType::en lhs, const VNType& rhs) { return lhs == rhs.m_e; } inline std::ostream& operator<<(std::ostream& os, const VNType& rhs) { return os << rhs.ascii(); } @@ -207,8 +208,8 @@ public: explicit VSigning(int _e) : m_e(static_cast(_e)) {} // Need () or GCC 4.8 false warning constexpr operator en() const { return m_e; } - bool isSigned() const { return m_e == SIGNED; } - bool isNosign() const { return m_e == NOSIGN; } + bool isSigned() const VL_MT_SAFE { return m_e == SIGNED; } + bool isNosign() const VL_MT_SAFE { return m_e == NOSIGN; } // No isUnsigned() as it's ambiguous if NOSIGN should be included or not. }; constexpr bool operator==(const VSigning& lhs, const VSigning& rhs) { return lhs.m_e == rhs.m_e; } @@ -590,17 +591,17 @@ public: return (m_e == BIT || m_e == BYTE || m_e == CHANDLE || m_e == INT || m_e == LONGINT || 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 VL_MT_SAFE { // IE not a simple number we can bit optimize return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR || m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DELAY_SCHEDULER || m_e == TRIGGER_SCHEDULER || m_e == FORK_SYNC || m_e == DOUBLE); } - bool isDouble() const { return m_e == DOUBLE; } + bool isDouble() const VL_MT_SAFE { return m_e == DOUBLE; } bool isEvent() const { return m_e == EVENT; } - bool isString() const { return m_e == STRING; } - bool isMTaskState() const { return m_e == MTASKSTATE; } + bool isString() const VL_MT_SAFE { return m_e == STRING; } + bool isMTaskState() const VL_MT_SAFE { return m_e == MTASKSTATE; } // Does this represent a C++ LiteralType? (can be constexpr) - bool isLiteralType() const { + bool isLiteralType() const VL_MT_SAFE { switch (m_e) { case BIT: case BYTE: @@ -619,13 +620,13 @@ public: } } }; -constexpr bool operator==(const VBasicDTypeKwd& lhs, const VBasicDTypeKwd& rhs) { +constexpr bool operator==(const VBasicDTypeKwd& lhs, const VBasicDTypeKwd& rhs) VL_MT_SAFE { return lhs.m_e == rhs.m_e; } -constexpr bool operator==(const VBasicDTypeKwd& lhs, VBasicDTypeKwd::en rhs) { +constexpr bool operator==(const VBasicDTypeKwd& lhs, VBasicDTypeKwd::en rhs) VL_MT_SAFE { return lhs.m_e == rhs; } -constexpr bool operator==(VBasicDTypeKwd::en lhs, const VBasicDTypeKwd& rhs) { +constexpr bool operator==(VBasicDTypeKwd::en lhs, const VBasicDTypeKwd& rhs) VL_MT_SAFE { return lhs == rhs.m_e; } @@ -642,7 +643,7 @@ public: : m_e{_e} {} explicit VDirection(int _e) : m_e(static_cast(_e)) {} // Need () or GCC 4.8 false warning - constexpr operator en() const { return m_e; } + constexpr operator en() const VL_MT_SAFE { return m_e; } const char* ascii() const { static const char* const names[] = {"NONE", "INPUT", "OUTPUT", "INOUT", "REF", "CONSTREF"}; return names[m_e]; @@ -662,9 +663,9 @@ public: bool isNonOutput() const { return m_e == INPUT || m_e == INOUT || m_e == REF || m_e == CONSTREF; } - bool isReadOnly() const { return m_e == INPUT || m_e == CONSTREF; } - bool isWritable() const { return m_e == OUTPUT || m_e == INOUT || m_e == REF; } - bool isRefOrConstRef() const { return m_e == REF || m_e == CONSTREF; } + bool isReadOnly() const VL_MT_SAFE { return m_e == INPUT || m_e == CONSTREF; } + bool isWritable() const VL_MT_SAFE { return m_e == OUTPUT || m_e == INOUT || m_e == REF; } + bool isRefOrConstRef() const VL_MT_SAFE { return m_e == REF || m_e == CONSTREF; } }; constexpr bool operator==(const VDirection& lhs, const VDirection& rhs) { return lhs.m_e == rhs.m_e; @@ -777,11 +778,9 @@ public: MEMBER }; enum en m_e; - VVarType() - : m_e{UNKNOWN} {} + VVarType() VL_MT_SAFE : m_e{UNKNOWN} {} // cppcheck-suppress noExplicitConstructor - constexpr VVarType(en _e) - : m_e{_e} {} + constexpr VVarType(en _e) VL_MT_SAFE : m_e{_e} {} explicit VVarType(int _e) : m_e(static_cast(_e)) {} // Need () or GCC 4.8 false warning constexpr operator en() const { return m_e; } @@ -815,10 +814,16 @@ public: return (m_e == BLOCKTEMP || m_e == MODULETEMP || m_e == STMTTEMP || m_e == XTEMP); } }; -constexpr bool operator==(const VVarType& lhs, const VVarType& rhs) { return lhs.m_e == rhs.m_e; } -constexpr bool operator==(const VVarType& lhs, VVarType::en rhs) { return lhs.m_e == rhs; } -constexpr bool operator==(VVarType::en lhs, const VVarType& rhs) { return lhs == rhs.m_e; } -inline std::ostream& operator<<(std::ostream& os, const VVarType& rhs) { +constexpr bool operator==(const VVarType& lhs, const VVarType& rhs) VL_MT_SAFE { + return lhs.m_e == rhs.m_e; +} +constexpr bool operator==(const VVarType& lhs, VVarType::en rhs) VL_MT_SAFE { + return lhs.m_e == rhs; +} +constexpr bool operator==(VVarType::en lhs, const VVarType& rhs) VL_MT_SAFE { + return lhs == rhs.m_e; +} +inline std::ostream& operator<<(std::ostream& os, const VVarType& rhs) VL_MT_SAFE { return os << rhs.ascii(); } @@ -1117,10 +1122,14 @@ public: } int left() const { return m_left; } int right() const { return m_right; } - int hi() const { return m_left > m_right ? m_left : m_right; } // How to show a declaration - int lo() const { return m_left > m_right ? m_right : m_left; } // How to show a declaration + int hi() const VL_MT_SAFE { + return m_left > m_right ? m_left : m_right; + } // How to show a declaration + int lo() const VL_MT_SAFE { + return m_left > m_right ? m_right : m_left; + } // How to show a declaration int leftToRightInc() const { return littleEndian() ? 1 : -1; } - int elements() const { return hi() - lo() + 1; } + int elements() const VL_MT_SAFE { return hi() - lo() + 1; } bool ranged() const { return m_ranged; } bool littleEndian() const { return m_left < m_right; } int hiMaxSelect() const { @@ -1234,12 +1243,12 @@ public: ~VNUser() = default; // Casters template - typename std::enable_if::value, T>::type to() const { + typename std::enable_if::value, T>::type to() const VL_MT_SAFE { return reinterpret_cast(m_u.up); } WidthVP* c() const { return to(); } VSymEnt* toSymEnt() const { return to(); } - AstNode* toNodep() const { return to(); } + AstNode* toNodep() const VL_MT_SAFE { return to(); } V3GraphVertex* toGraphVertex() const { return to(); } int toInt() const { return m_u.ui; } static VNUser fromInt(int i) { return VNUser{i}; } @@ -1542,7 +1551,7 @@ class AstNode VL_NOT_FINAL { private: AstNode* cloneTreeIter(); AstNode* cloneTreeIterList(); - void checkTreeIter(const AstNode* backp) const; + void checkTreeIter(const AstNode* backp) const VL_MT_SAFE; bool gateTreeIter() const; static bool sameTreeIter(const AstNode* node1p, const AstNode* node2p, bool ignNext, bool gateOnly); @@ -1596,16 +1605,16 @@ protected: public: // ACCESSORS - VNType type() const { return m_type; } - const char* typeName() const { return type().ascii(); } // See also prettyTypeName - AstNode* nextp() const { return m_nextp; } - AstNode* backp() const { return m_backp; } + VNType type() const VL_MT_SAFE { return m_type; } + const char* typeName() const VL_MT_SAFE { return type().ascii(); } // See also prettyTypeName + AstNode* nextp() const VL_MT_SAFE { return m_nextp; } + AstNode* backp() const VL_MT_SAFE { return m_backp; } AstNode* abovep() const; // Parent node above, only when no nextp() as otherwise slow - AstNode* op1p() const { return m_op1p; } - AstNode* op2p() const { return m_op2p; } - AstNode* op3p() const { return m_op3p; } - AstNode* op4p() const { return m_op4p; } - AstNodeDType* dtypep() const { return m_dtypep; } + AstNode* op1p() const VL_MT_SAFE { return m_op1p; } + AstNode* op2p() const VL_MT_SAFE { return m_op2p; } + AstNode* op3p() const VL_MT_SAFE { return m_op3p; } + AstNode* op4p() const VL_MT_SAFE { return m_op4p; } + AstNodeDType* dtypep() const VL_MT_SAFE { return m_dtypep; } AstNode* clonep() const { return ((m_cloneCnt == s_cloneCntGbl) ? m_clonep : nullptr); } AstNode* firstAbovep() const { // Returns nullptr when second or later in list return ((backp() && backp()->nextp() != this) ? backp() : nullptr); @@ -1620,7 +1629,7 @@ public: // If we're first in the list, check what backp() thinks of us: || (backp() && backp()->isFirstInMyListOfStatements(this))); } - uint8_t brokenState() const { return m_brokenState; } + uint8_t brokenState() const VL_MT_SAFE { return m_brokenState; } void brokenState(uint8_t value) { m_brokenState = value; } // Used by AstNode::broken() @@ -1652,7 +1661,7 @@ public: static constexpr int INSTR_COUNT_PLI = 20; // PLI routines // ACCESSORS - virtual string name() const { return ""; } + virtual string name() const VL_MT_SAFE { return ""; } virtual string origName() const { return ""; } virtual void name(const string& name) { this->v3fatalSrc("name() called on object without name() method"); @@ -1660,7 +1669,7 @@ public: virtual void tag(const string& text) {} virtual string tag() const { return ""; } virtual string verilogKwd() const { return ""; } - string nameProtect() const; // Name with --protect-id applied + string nameProtect() const VL_MT_SAFE; // Name with --protect-id applied string origNameProtect() const; // origName with --protect-id applied string shortName() const; // Name with __PVT__ removed for concatenating scopes static string dedotName(const string& namein); // Name with dots removed @@ -1672,11 +1681,11 @@ public: encodeName(const string& namein); // Encode user name into internal C representation static string encodeNumber(int64_t num); // Encode number into internal C representation static string vcdName(const string& namein); // Name for printing out to vcd files - string prettyName() const { return prettyName(name()); } + string prettyName() const VL_MT_SAFE { return prettyName(name()); } string prettyNameQ() const { return prettyNameQ(name()); } string prettyTypeName() const; // "VARREF" for error messages (NOT dtype's pretty name) virtual string prettyOperatorName() const { return "operator " + prettyTypeName(); } - FileLine* fileline() const { return m_fileline; } + FileLine* fileline() const VL_MT_SAFE { return m_fileline; } void fileline(FileLine* fl) { m_fileline = fl; } inline bool width1() const; inline int widthInstrs() const; @@ -1689,29 +1698,29 @@ public: } bool doingWidth() const { return m_flags.doingWidth; } void doingWidth(bool flag) { m_flags.doingWidth = flag; } - bool protect() const { return m_flags.protect; } + bool protect() const VL_MT_SAFE { return m_flags.protect; } void protect(bool flag) { m_flags.protect = flag; } // TODO stomp these width functions out, and call via dtypep() instead - inline int width() const; + inline int width() const VL_MT_SAFE; inline int widthMin() const; int widthMinV() const { return v3Global.widthMinUsage() == VWidthMinUsage::VERILOG_WIDTH ? widthMin() : width(); } int widthWords() const { return VL_WORDS_I(width()); } - bool isQuad() const { return (width() > VL_IDATASIZE && width() <= VL_QUADSIZE); } - bool isWide() const { return (width() > VL_QUADSIZE); } + bool isQuad() const VL_MT_SAFE { return (width() > VL_IDATASIZE && width() <= VL_QUADSIZE); } + bool isWide() const VL_MT_SAFE { return (width() > VL_QUADSIZE); } inline bool isDouble() const; inline bool isSigned() const; inline bool isString() const; // clang-format off - VNUser user1u() const { + VNUser user1u() const VL_MT_SAFE { // Slows things down measurably, so disabled by default //UASSERT_STATIC(VNUser1InUse::s_userBusy, "userp set w/o busy"); return ((m_user1Cnt==VNUser1InUse::s_userCntGbl) ? m_user1u : VNUser{0}); } - AstNode* user1p() const { return user1u().toNodep(); } + AstNode* user1p() const VL_MT_SAFE { return user1u().toNodep(); } void user1u(const VNUser& user) { m_user1u=user; m_user1Cnt=VNUser1InUse::s_userCntGbl; } void user1p(void* userp) { user1u(VNUser{userp}); } int user1() const { return user1u().toInt(); } @@ -1720,12 +1729,12 @@ public: int user1SetOnce() { int v=user1(); if (!v) user1(1); return v; } // Better for cache than user1Inc() static void user1ClearTree() { VNUser1InUse::clear(); } // Clear userp()'s across the entire tree - VNUser user2u() const { + VNUser user2u() const VL_MT_SAFE { // Slows things down measurably, so disabled by default //UASSERT_STATIC(VNUser2InUse::s_userBusy, "userp set w/o busy"); return ((m_user2Cnt==VNUser2InUse::s_userCntGbl) ? m_user2u : VNUser{0}); } - AstNode* user2p() const { return user2u().toNodep(); } + AstNode* user2p() const VL_MT_SAFE { return user2u().toNodep(); } void user2u(const VNUser& user) { m_user2u=user; m_user2Cnt=VNUser2InUse::s_userCntGbl; } void user2p(void* userp) { user2u(VNUser{userp}); } int user2() const { return user2u().toInt(); } @@ -1734,12 +1743,12 @@ public: int user2SetOnce() { int v=user2(); if (!v) user2(1); return v; } // Better for cache than user2Inc() static void user2ClearTree() { VNUser2InUse::clear(); } // Clear userp()'s across the entire tree - VNUser user3u() const { + VNUser user3u() const VL_MT_SAFE { // Slows things down measurably, so disabled by default //UASSERT_STATIC(VNUser3InUse::s_userBusy, "userp set w/o busy"); return ((m_user3Cnt==VNUser3InUse::s_userCntGbl) ? m_user3u : VNUser{0}); } - AstNode* user3p() const { return user3u().toNodep(); } + AstNode* user3p() const VL_MT_SAFE { return user3u().toNodep(); } void user3u(const VNUser& user) { m_user3u=user; m_user3Cnt=VNUser3InUse::s_userCntGbl; } void user3p(void* userp) { user3u(VNUser{userp}); } int user3() const { return user3u().toInt(); } @@ -1748,12 +1757,12 @@ public: int user3SetOnce() { int v=user3(); if (!v) user3(1); return v; } // Better for cache than user3Inc() static void user3ClearTree() { VNUser3InUse::clear(); } // Clear userp()'s across the entire tree - VNUser user4u() const { + VNUser user4u() const VL_MT_SAFE { // Slows things down measurably, so disabled by default //UASSERT_STATIC(VNUser4InUse::s_userBusy, "userp set w/o busy"); return ((m_user4Cnt==VNUser4InUse::s_userCntGbl) ? m_user4u : VNUser{0}); } - AstNode* user4p() const { return user4u().toNodep(); } + AstNode* user4p() const VL_MT_SAFE { return user4u().toNodep(); } void user4u(const VNUser& user) { m_user4u=user; m_user4Cnt=VNUser4InUse::s_userCntGbl; } void user4p(void* userp) { user4u(VNUser{userp}); } int user4() const { return user4u().toInt(); } @@ -1762,12 +1771,12 @@ public: int user4SetOnce() { int v=user4(); if (!v) user4(1); return v; } // Better for cache than user4Inc() static void user4ClearTree() { VNUser4InUse::clear(); } // Clear userp()'s across the entire tree - VNUser user5u() const { + VNUser user5u() const VL_MT_SAFE { // Slows things down measurably, so disabled by default //UASSERT_STATIC(VNUser5InUse::s_userBusy, "userp set w/o busy"); return ((m_user5Cnt==VNUser5InUse::s_userCntGbl) ? m_user5u : VNUser{0}); } - AstNode* user5p() const { return user5u().toNodep(); } + AstNode* user5p() const VL_MT_SAFE { return user5u().toNodep(); } void user5u(const VNUser& user) { m_user5u=user; m_user5Cnt=VNUser5InUse::s_userCntGbl; } void user5p(void* userp) { user5u(VNUser{userp}); } int user5() const { return user5u().toInt(); } @@ -1785,8 +1794,8 @@ public: #else void editCountInc() { ++s_editCntGbl; } #endif - static uint64_t editCountLast() { return s_editCntLast; } - static uint64_t editCountGbl() { return s_editCntGbl; } + static uint64_t editCountLast() VL_MT_SAFE { return s_editCntLast; } + static uint64_t editCountGbl() VL_MT_SAFE { return s_editCntGbl; } static void editCountSetLast() { s_editCntLast = editCountGbl(); } // ACCESSORS for specific types @@ -1851,8 +1860,8 @@ public: static AstBasicDType* findInsertSameDType(AstBasicDType* nodep); // METHODS - dump and error - void v3errorEnd(std::ostringstream& str) const; - void v3errorEndFatal(std::ostringstream& str) const VL_ATTR_NORETURN; + void v3errorEnd(std::ostringstream& str) const VL_MT_SAFE; + void v3errorEndFatal(std::ostringstream& str) const VL_ATTR_NORETURN VL_MT_SAFE; string warnContextPrimary() const { return fileline()->warnContextPrimary(); } string warnContextSecondary() const { return fileline()->warnContextSecondary(); } string warnMore() const { return fileline()->warnMore(); } @@ -1901,7 +1910,7 @@ public: // Does tree of this == node2p?, not allowing non-isGateOptimizable inline bool sameGateTree(const AstNode* node2p) const; void deleteTree(); // Always deletes the next link - void checkTree() const { + void checkTree() const VL_MT_SAFE { if (v3Global.opt.debugCheck()) checkTreeIter(backp()); } void checkIter() const; @@ -1943,13 +1952,13 @@ public: virtual int instrCount() const { return 0; } virtual bool same(const AstNode*) const { return true; } // Iff has a data type; dtype() must be non null - virtual bool hasDType() const { return false; } + virtual bool hasDType() const VL_MT_SAFE { return false; } // Iff has a non-null childDTypep(), as generic node function virtual AstNodeDType* getChildDTypep() const { return nullptr; } // Iff has a non-null child2DTypep(), as generic node function virtual AstNodeDType* getChild2DTypep() const { return nullptr; } // Another AstNode* may have a pointer into this node, other then normal front/back/etc. - virtual bool maybePointedTo() const { return false; } + virtual bool maybePointedTo() const VL_MT_SAFE { return false; } // Don't reclaim this node in V3Dead virtual bool undead() const { return false; } // Check if node is consistent, return nullptr if ok, else reason string @@ -2003,7 +2012,7 @@ private: public: // For use via the VN_IS macro only template - static bool privateIs(const AstNode* nodep) { + static bool privateIs(const AstNode* nodep) VL_MT_SAFE { static_assert(!uselessCast(), "Unnecessary VN_IS, node known to have target type."); static_assert(!impossibleCast(), "Unnecessary VN_IS, node cannot be this type."); return nodep && privateTypeTest(nodep); @@ -2011,14 +2020,14 @@ public: // For use via the VN_CAST macro only template - static T* privateCast(AstNode* nodep) { + static T* privateCast(AstNode* nodep) VL_MT_SAFE { static_assert(!uselessCast(), "Unnecessary VN_CAST, node known to have target type."); static_assert(!impossibleCast(), "Unnecessary VN_CAST, node cannot be this type."); return nodep && privateTypeTest(nodep) ? reinterpret_cast(nodep) : nullptr; } template - static const T* privateCast(const AstNode* nodep) { + static const T* privateCast(const AstNode* nodep) VL_MT_SAFE { static_assert(!uselessCast(), "Unnecessary VN_CAST, node known to have target type."); static_assert(!impossibleCast(), "Unnecessary VN_CAST, node cannot be this type."); @@ -2027,7 +2036,7 @@ public: // For use via the VN_AS macro only template - static T* privateAs(AstNode* nodep) { + static T* privateAs(AstNode* nodep) VL_MT_SAFE { static_assert(!uselessCast(), "Unnecessary VN_AS, node known to have target type."); static_assert(!impossibleCast(), "Unnecessary VN_AS, node cannot be this type."); UASSERT_OBJ(!nodep || privateTypeTest(nodep), nodep, @@ -2036,7 +2045,7 @@ public: return reinterpret_cast(nodep); } template - static const T* privateAs(const AstNode* nodep) { + static const T* privateAs(const AstNode* nodep) VL_MT_SAFE { static_assert(!uselessCast(), "Unnecessary VN_AS, node known to have target type."); static_assert(!impossibleCast(), "Unnecessary VN_AS, node cannot be this type."); UASSERT_OBJ(!nodep || privateTypeTest(nodep), nodep, diff --git a/src/V3AstInlines.h b/src/V3AstInlines.h index 94ab3b96f..144419d05 100644 --- a/src/V3AstInlines.h +++ b/src/V3AstInlines.h @@ -33,10 +33,10 @@ bool AstNode::width1() const { // V3Const uses to know it can optimize int AstNode::widthInstrs() const { return (!dtypep() ? 1 : (dtypep()->isWide() ? dtypep()->widthWords() : 1)); } -bool AstNode::isDouble() const { +bool AstNode::isDouble() const VL_MT_SAFE { return dtypep() && VN_IS(dtypep(), BasicDType) && VN_AS(dtypep(), BasicDType)->isDouble(); } -bool AstNode::isString() const { +bool AstNode::isString() const VL_MT_SAFE { return dtypep() && dtypep()->basicp() && dtypep()->basicp()->isString(); } bool AstNode::isSigned() const { return dtypep() && dtypep()->isSigned(); } @@ -61,12 +61,12 @@ bool AstNode::sameGateTree(const AstNode* node2p) const { return sameTreeIter(this, node2p, true, true); } -int AstNodeArrayDType::left() const { return rangep()->leftConst(); } -int AstNodeArrayDType::right() const { return rangep()->rightConst(); } -int AstNodeArrayDType::hi() const { return rangep()->hiConst(); } -int AstNodeArrayDType::lo() const { return rangep()->loConst(); } -int AstNodeArrayDType::elementsConst() const { return rangep()->elementsConst(); } -VNumRange AstNodeArrayDType::declRange() const { return VNumRange{left(), right()}; } +int AstNodeArrayDType::left() const VL_MT_SAFE { return rangep()->leftConst(); } +int AstNodeArrayDType::right() const VL_MT_SAFE { return rangep()->rightConst(); } +int AstNodeArrayDType::hi() const VL_MT_SAFE { return rangep()->hiConst(); } +int AstNodeArrayDType::lo() const VL_MT_SAFE { return rangep()->loConst(); } +int AstNodeArrayDType::elementsConst() const VL_MT_SAFE { return rangep()->elementsConst(); } +VNumRange AstNodeArrayDType::declRange() const VL_MT_SAFE { return VNumRange{left(), right()}; } AstRange::AstRange(FileLine* fl, int left, int right) : ASTGEN_SUPER_Range(fl) { @@ -87,7 +87,7 @@ int AstRange::rightConst() const { return (constp ? constp->toSInt() : 0); } -int AstQueueDType::boundConst() const { +int AstQueueDType::boundConst() const VL_MT_SAFE { AstConst* const constp = VN_CAST(boundp(), Const); return (constp ? constp->toSInt() : 0); } diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index c3f4d096e..736eefd8a 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -103,19 +103,20 @@ public: m_numeric = nodep->m_numeric; } // - int width() const { return m_width; } + int width() const VL_MT_SAFE { return m_width; } void numeric(VSigning flag) { m_numeric = flag; } - bool isSigned() const { return m_numeric.isSigned(); } - bool isNosign() const { return m_numeric.isNosign(); } + bool isSigned() const VL_MT_SAFE { return m_numeric.isSigned(); } + bool isNosign() const VL_MT_SAFE { return m_numeric.isNosign(); } VSigning numeric() const { return m_numeric; } - int widthWords() const { return VL_WORDS_I(width()); } - int widthMin() const { // If sized, the size, if unsized the min digits to represent it + int widthWords() const VL_MT_SAFE { return VL_WORDS_I(width()); } + int widthMin() const VL_MT_SAFE { // If sized, the size, + // if unsized the min digits to represent it return m_widthMin ? m_widthMin : m_width; } int widthPow2() const; void widthMinFromWidth() { m_widthMin = m_width; } - bool widthSized() const { return !m_widthMin || m_widthMin == m_width; } - bool generic() const { return m_generic; } + bool widthSized() const VL_MT_SAFE { return !m_widthMin || m_widthMin == m_width; } + bool generic() const VL_MT_SAFE { return m_generic; } void generic(bool flag) { m_generic = flag; } std::pair dimensions(bool includeBasic); uint32_t arrayUnpackedElements(); // 1, or total multiplication of all dimensions @@ -123,12 +124,12 @@ public: const char* charIQWN() const { return (isString() ? "N" : isWide() ? "W" : isQuad() ? "Q" : "I"); } - string cType(const string& name, bool forFunc, bool isRef) const; - bool isLiteralType() const; // Does this represent a C++ LiteralType? (can be constexpr) + string cType(const string& name, bool forFunc, bool isRef) const VL_MT_SAFE; + bool isLiteralType() const VL_MT_SAFE; // Represents a C++ LiteralType? (can be constexpr) private: class CTypeRecursed; - CTypeRecursed cTypeRecurse(bool compound) const; + CTypeRecursed cTypeRecurse(bool compound) const VL_MT_SAFE; }; class AstNodeArrayDType VL_NOT_FINAL : public AstNodeDType { // Array data type, ie "some_dtype var_name [2:0]" @@ -167,15 +168,17 @@ public: && subDTypep()->skipRefp()->similarDType(asamep->subDTypep()->skipRefp())); } AstNodeDType* getChildDTypep() const override { return childDTypep(); } - AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } + AstNodeDType* subDTypep() const override VL_MT_SAFE { + return m_refDTypep ? m_refDTypep : childDTypep(); + } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } // METHODS - AstBasicDType* basicp() const override { + AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); } // (Slow) recurse down to find basic data type - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); } @@ -225,7 +228,7 @@ public: : VN_AS(findBitRangeDType(VNumRange{width() - 1, 0}, width(), numeric()), BasicDType)); } - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } // (Slow) recurses - Structure alignment 1,2,4 or 8 bytes (arrays affect this) @@ -237,7 +240,7 @@ public: } string name() const override { return m_name; } void name(const string& flag) override { m_name = flag; } - bool packed() const { return m_packed; } + bool packed() const VL_MT_SAFE { return m_packed; } // packed() but as don't support unpacked, presently all structs static bool packedUnsup() { return true; } void isFourstate(bool flag) { m_isFourstate = flag; } @@ -327,18 +330,22 @@ public: void dumpSmall(std::ostream& str) const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } AstNodeDType* getChild2DTypep() const override { return keyChildDTypep(); } - AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } + AstNodeDType* subDTypep() const override VL_MT_SAFE { + return m_refDTypep ? m_refDTypep : childDTypep(); + } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } AstNodeDType* virtRefDType2p() const override { return m_keyDTypep; } void virtRefDType2p(AstNodeDType* nodep) override { keyDTypep(nodep); } // - AstNodeDType* keyDTypep() const { return m_keyDTypep ? m_keyDTypep : keyChildDTypep(); } + AstNodeDType* keyDTypep() const VL_MT_SAFE { + return m_keyDTypep ? m_keyDTypep : keyChildDTypep(); + } void keyDTypep(AstNodeDType* nodep) { m_keyDTypep = nodep; } // METHODS - AstBasicDType* basicp() const override { return nullptr; } - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); } @@ -410,8 +417,8 @@ public: } } // METHODS - AstBasicDType* basicp() const override { return (AstBasicDType*)this; } - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstBasicDType* basicp() const override VL_MT_SAFE { return (AstBasicDType*)this; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } // (Slow) recurses - Structure alignment 1,2,4 or 8 bytes (arrays affect this) @@ -419,18 +426,22 @@ public: // (Slow) recurses - Width in bytes rounding up 1,2,4,8,12,... int widthTotalBytes() const override; bool isFourstate() const override { return keyword().isFourstate(); } - VBasicDTypeKwd keyword() const { // Avoid using - use isSomething accessors instead + VBasicDTypeKwd keyword() const VL_MT_SAFE { // Avoid using - use isSomething accessors instead return m.m_keyword; } bool isBitLogic() const { return keyword().isBitLogic(); } - bool isDouble() const { return keyword().isDouble(); } - bool isEvent() const { return keyword() == VBasicDTypeKwd::EVENT; } - bool isTriggerVec() const { return keyword() == VBasicDTypeKwd::TRIGGERVEC; } - bool isForkSync() const { return keyword() == VBasicDTypeKwd::FORK_SYNC; } - bool isDelayScheduler() const { return keyword() == VBasicDTypeKwd::DELAY_SCHEDULER; } - bool isTriggerScheduler() const { return keyword() == VBasicDTypeKwd::TRIGGER_SCHEDULER; } - bool isOpaque() const { return keyword().isOpaque(); } - bool isString() const { return keyword().isString(); } + bool isDouble() const VL_MT_SAFE { return keyword().isDouble(); } + bool isEvent() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::EVENT; } + bool isTriggerVec() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::TRIGGERVEC; } + bool isForkSync() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::FORK_SYNC; } + bool isDelayScheduler() const VL_MT_SAFE { + return keyword() == VBasicDTypeKwd::DELAY_SCHEDULER; + } + bool isTriggerScheduler() const VL_MT_SAFE { + return keyword() == VBasicDTypeKwd::TRIGGER_SCHEDULER; + } + bool isOpaque() const VL_MT_SAFE { return keyword().isOpaque(); } + bool isString() const VL_MT_SAFE { return keyword().isString(); } bool isZeroInit() const { return keyword().isZeroInit(); } bool isRanged() const { return rangep() || m.m_nrange.ranged(); } bool isDpiBitVec() const { // DPI uses svBitVecVal @@ -474,8 +485,8 @@ public: // Will be removed in V3Width, which relies on this // being a child not a dtype pointed node bool maybePointedTo() const override { return false; } - AstBasicDType* basicp() const override { return nullptr; } - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } int widthAlignBytes() const override { V3ERROR_NA_RETURN(0); } @@ -509,8 +520,8 @@ public: void dump(std::ostream& str = std::cout) const override; void dumpSmall(std::ostream& str) const override; string name() const override; - AstBasicDType* basicp() const override { return nullptr; } - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } int widthAlignBytes() const override { return 0; } @@ -561,8 +572,8 @@ public: AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } // METHODS - AstBasicDType* basicp() const override { return subDTypep()->basicp(); } - AstNodeDType* skipRefp() const override { return subDTypep()->skipRefp(); } + AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return subDTypep()->skipRefp(); } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return subDTypep()->skipRefToEnump(); } int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); } @@ -608,8 +619,8 @@ public: // METHODS // op1 = Range of variable AstNodeDType* dtypeSkipRefp() const { return dtypep()->skipRefp(); } - AstBasicDType* basicp() const override { return subDTypep()->basicp(); } - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } int widthAlignBytes() const override { return dtypep()->widthAlignBytes(); } @@ -657,13 +668,15 @@ public: string prettyDTypeName() const override; void dumpSmall(std::ostream& str) const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } - AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } + AstNodeDType* subDTypep() const override VL_MT_SAFE { + return m_refDTypep ? m_refDTypep : childDTypep(); + } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } // METHODS - AstBasicDType* basicp() const override { return nullptr; } - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); } @@ -686,9 +699,9 @@ public: AstNodeDType* virtRefDTypep() const override { return nullptr; } void virtRefDTypep(AstNodeDType* nodep) override {} bool similarDType(AstNodeDType* samep) const override { return this == samep; } - AstBasicDType* basicp() const override { return nullptr; } + AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; } // cppcheck-suppress csyleCast - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } // cppcheck-suppress csyleCast AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } // cppcheck-suppress csyleCast @@ -739,8 +752,8 @@ public: string name() const override { return m_name; } void name(const string& flag) override { m_name = flag; } // METHODS - AstBasicDType* basicp() const override { return subDTypep()->basicp(); } - AstNodeDType* skipRefp() const override { return subDTypep()->skipRefp(); } + AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return subDTypep()->skipRefp(); } AstNodeDType* skipRefToConstp() const override { return subDTypep()->skipRefToConstp(); } // cppcheck-suppress csyleCast AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } @@ -783,8 +796,8 @@ public: void dump(std::ostream& str = std::cout) const override; void dumpSmall(std::ostream& str) const override; void cloneRelink() override; - AstBasicDType* basicp() const override { return nullptr; } - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } bool similarDType(AstNodeDType* samep) const override { return this == samep; } @@ -852,10 +865,10 @@ public: // // (Slow) recurse down to find basic data type (Note don't need virtual - // AstVar isn't a NodeDType) - AstBasicDType* basicp() const override { return subDTypep()->basicp(); } + AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); } // op1 = Range of variable (Note don't need virtual - AstVar isn't a NodeDType) AstNodeDType* dtypeSkipRefp() const { return subDTypep()->skipRefp(); } - AstNodeDType* skipRefp() const override { return subDTypep()->skipRefp(); } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return subDTypep()->skipRefp(); } AstNodeDType* skipRefToConstp() const override { return subDTypep()->skipRefToConstp(); } AstNodeDType* skipRefToEnump() const override { return subDTypep()->skipRefToEnump(); } // (Slow) recurses - Structure alignment 1,2,4 or 8 bytes (arrays affect this) @@ -892,8 +905,8 @@ public: ASTGEN_MEMBERS_AstParamTypeDType; AstNodeDType* getChildDTypep() const override { return childDTypep(); } AstNodeDType* subDTypep() const override { return dtypep() ? dtypep() : childDTypep(); } - AstBasicDType* basicp() const override { return subDTypep()->basicp(); } - AstNodeDType* skipRefp() const override { return subDTypep()->skipRefp(); } + AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return subDTypep()->skipRefp(); } AstNodeDType* skipRefToConstp() const override { return subDTypep()->skipRefToConstp(); } AstNodeDType* skipRefToEnump() const override { return subDTypep()->skipRefToEnump(); } bool similarDType(AstNodeDType* samep) const override { @@ -927,8 +940,8 @@ public: AstNodeDType* dtypep() const { return nullptr; } // METHODS bool similarDType(AstNodeDType* samep) const override { return this == samep; } - AstBasicDType* basicp() const override { return nullptr; } - AstNodeDType* skipRefp() const override { return nullptr; } + AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return nullptr; } // cppcheck-suppress csyleCast AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } // cppcheck-suppress csyleCast @@ -982,15 +995,17 @@ public: void dumpSmall(std::ostream& str) const override; string prettyDTypeName() const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } - AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } + AstNodeDType* subDTypep() const override VL_MT_SAFE { + return m_refDTypep ? m_refDTypep : childDTypep(); + } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } inline int boundConst() const; AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } // METHODS - AstBasicDType* basicp() const override { return nullptr; } + AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; } // cppcheck-suppress csyleCast - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } // cppcheck-suppress csyleCast AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } // cppcheck-suppress csyleCast @@ -1043,11 +1058,11 @@ public: string prettyDTypeName() const override { return subDTypep() ? subDTypep()->name() : prettyName(); } - AstBasicDType* basicp() const override { + AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep() ? subDTypep()->basicp() : nullptr; } AstNodeDType* subDTypep() const override; - AstNodeDType* skipRefp() const override { + AstNodeDType* skipRefp() const override VL_MT_SAFE { // Skip past both the Ref and the Typedef if (subDTypep()) { return subDTypep()->skipRefp(); @@ -1119,8 +1134,8 @@ public: AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } // METHODS - AstBasicDType* basicp() const override { return subDTypep()->basicp(); } - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } int widthAlignBytes() const override { return subDTypep()->widthAlignBytes(); } @@ -1143,9 +1158,9 @@ public: AstNodeDType* virtRefDTypep() const override { return nullptr; } void virtRefDTypep(AstNodeDType* nodep) override {} bool similarDType(AstNodeDType* samep) const override { return this == samep; } - AstBasicDType* basicp() const override { return nullptr; } + AstBasicDType* basicp() const override VL_MT_SAFE { return nullptr; } // cppcheck-suppress csyleCast - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } // cppcheck-suppress csyleCast AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } // cppcheck-suppress csyleCast @@ -1179,13 +1194,15 @@ public: bool similarDType(AstNodeDType* samep) const override; void dumpSmall(std::ostream& str) const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } - AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); } + AstNodeDType* subDTypep() const override VL_MT_SAFE { + return m_refDTypep ? m_refDTypep : childDTypep(); + } void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } // METHODS - AstBasicDType* basicp() const override { return subDTypep()->basicp(); } - AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); } + AstNodeDType* skipRefp() const override VL_MT_SAFE { return (AstNodeDType*)this; } AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } int widthAlignBytes() const override { return sizeof(std::map); } @@ -1235,7 +1252,7 @@ public: // Outer dimension comes first. The first element is this node. std::vector unpackDimensions(); void isCompound(bool flag) { m_isCompound = flag; } - bool isCompound() const override { return m_isCompound; } + bool isCompound() const override VL_MT_SAFE { return m_isCompound; } }; // === AstNodeUOrStructDType === diff --git a/src/V3AstNodeMath.h b/src/V3AstNodeMath.h index e6d752bc1..398e57832 100644 --- a/src/V3AstNodeMath.h +++ b/src/V3AstNodeMath.h @@ -574,10 +574,10 @@ public: } ASTGEN_MEMBERS_AstConst; string name() const override { return num().ascii(); } // * = Value - const V3Number& num() const { return m_num; } // * = Value + const V3Number& num() const VL_MT_SAFE { return m_num; } // * = Value V3Number& num() { return m_num; } // * = Value uint32_t toUInt() const { return num().toUInt(); } - int32_t toSInt() const { return num().toSInt(); } + int32_t toSInt() const VL_MT_SAFE { return num().toSInt(); } uint64_t toUQuad() const { return num().toUQuad(); } string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 618a33533..ec4612587 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -236,13 +236,13 @@ public: // ACCESSORS void name(const string& name) override { m_name = name; } string origName() const override { return m_origName; } - string someInstanceName() const { return m_someInstanceName; } + string someInstanceName() const VL_MT_SAFE { return m_someInstanceName; } void someInstanceName(const string& name) { m_someInstanceName = name; } bool inLibrary() const { return m_inLibrary; } void inLibrary(bool flag) { m_inLibrary = flag; } void level(int level) { m_level = level; } - int level() const { return m_level; } - bool isTop() const { return level() == 1; } + int level() const VL_MT_SAFE { return m_level; } + bool isTop() const VL_MT_SAFE { return level() == 1; } void modPublic(bool flag) { m_modPublic = flag; } bool modPublic() const { return m_modPublic; } void modTrace(bool flag) { m_modTrace = flag; } @@ -574,7 +574,7 @@ public: const AstNodeText* asamep = static_cast(samep); return text() == asamep->text(); } - const string& text() const { return m_text; } + const string& text() const VL_MT_SAFE { return m_text; } void text(const string& value) { m_text = value; } }; class AstNodeSimpleText VL_NOT_FINAL : public AstNodeText { @@ -758,7 +758,7 @@ public: void isConst(VBoolOrUnknown flag) { m_isConst = flag; } bool isStatic() const { return m_isStatic; } void isStatic(bool flag) { m_isStatic = flag; } - bool isTrace() const { return m_isTrace; } + bool isTrace() const VL_MT_SAFE { return m_isTrace; } void isTrace(bool flag) { m_isTrace = flag; } void cname(const string& name) { m_cname = name; } string cname() const { return m_cname; } @@ -771,7 +771,7 @@ public: bool dontInline() const { return dontCombine() || slow() || funcPublic(); } bool declPrivate() const { return m_declPrivate; } void declPrivate(bool flag) { m_declPrivate = flag; } - bool slow() const { return m_slow; } + bool slow() const VL_MT_SAFE { return m_slow; } void slow(bool flag) { m_slow = flag; } bool funcPublic() const { return m_funcPublic; } void funcPublic(bool flag) { m_funcPublic = flag; } @@ -800,11 +800,11 @@ public: void pure(bool flag) { m_pure = flag; } bool dpiContext() const { return m_dpiContext; } void dpiContext(bool flag) { m_dpiContext = flag; } - bool dpiExportDispatcher() const { return m_dpiExportDispatcher; } + bool dpiExportDispatcher() const VL_MT_SAFE { return m_dpiExportDispatcher; } void dpiExportDispatcher(bool flag) { m_dpiExportDispatcher = flag; } bool dpiExportImpl() const { return m_dpiExportImpl; } void dpiExportImpl(bool flag) { m_dpiExportImpl = flag; } - bool dpiImportPrototype() const { return m_dpiImportPrototype; } + bool dpiImportPrototype() const VL_MT_SAFE { return m_dpiImportPrototype; } void dpiImportPrototype(bool flag) { m_dpiImportPrototype = flag; } bool dpiImportWrapper() const { return m_dpiImportWrapper; } void dpiImportWrapper(bool flag) { m_dpiImportWrapper = flag; } @@ -1712,9 +1712,9 @@ public: string nameVlSym() const { return ((string("vlSymsp->")) + nameDotless()); } AstNodeModule* modp() const { return m_modp; } // - AstScope* aboveScopep() const { return m_aboveScopep; } + AstScope* aboveScopep() const VL_MT_SAFE { return m_aboveScopep; } AstCell* aboveCellp() const { return m_aboveCellp; } - bool isTop() const { return aboveScopep() == nullptr; } // At top of hierarchy + bool isTop() const VL_MT_SAFE { 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); @@ -2132,18 +2132,18 @@ public: } ASTGEN_MEMBERS_AstVar; void dump(std::ostream& str) const override; - string name() const override { return m_name; } // * = Var name + string name() const override VL_MT_SAFE { return m_name; } // * = Var name bool hasDType() const override { return true; } bool maybePointedTo() const override { return true; } string origName() const override { return m_origName; } // * = Original name void origName(const string& name) { m_origName = name; } - VVarType varType() const { return m_varType; } // * = Type of variable + VVarType varType() const VL_MT_SAFE { return m_varType; } // * = Type of variable void direction(const VDirection& flag) { m_direction = flag; if (m_direction == VDirection::INOUT) m_tristate = true; } - VDirection direction() const { return m_direction; } - bool isIO() const { return m_direction != VDirection::NONE; } + VDirection direction() const VL_MT_SAFE { return m_direction; } + bool isIO() const VL_MT_SAFE { return m_direction != VDirection::NONE; } void declDirection(const VDirection& flag) { m_declDirection = flag; } VDirection declDirection() const { return m_declDirection; } void varType(VVarType type) { m_varType = type; } @@ -2163,17 +2163,19 @@ public: string dpiTmpVarType(const string& varName) const; // Return Verilator internal type for argument: CData, SData, IData, WData string vlArgType(bool named, bool forReturn, bool forFunc, const string& namespc = "", - bool asRef = false) const; + bool asRef = false) const VL_MT_SAFE; string vlEnumType() const; // Return VerilatorVarType: VLVT_UINT32, etc string vlEnumDir() const; // Return VerilatorVarDir: VLVD_INOUT, etc string vlPropDecl(const string& propName) const; // Return VerilatorVarProps declaration void combineType(VVarType type); AstNodeDType* getChildDTypep() const override { return childDTypep(); } - AstNodeDType* dtypeSkipRefp() const { return subDTypep()->skipRefp(); } + AstNodeDType* dtypeSkipRefp() const VL_MT_SAFE { return subDTypep()->skipRefp(); } // (Slow) recurse down to find basic data type (Note don't need virtual - // AstVar isn't a NodeDType) - AstBasicDType* basicp() const { return subDTypep()->basicp(); } - virtual AstNodeDType* subDTypep() const { return dtypep() ? dtypep() : childDTypep(); } + AstBasicDType* basicp() const VL_MT_SAFE { return subDTypep()->basicp(); } + virtual AstNodeDType* subDTypep() const VL_MT_SAFE { + return dtypep() ? dtypep() : childDTypep(); + } void ansi(bool flag) { m_ansi = flag; } void declTyped(bool flag) { m_declTyped = flag; } void attrClocker(VVarAttrClocker flag) { m_attrClocker = flag; } @@ -2209,7 +2211,7 @@ public: void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; } bool hasStrengthAssignment() { return m_hasStrengthAssignment; } void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; } - bool isDpiOpenArray() const { return m_isDpiOpenArray; } + bool isDpiOpenArray() const VL_MT_SAFE { return m_isDpiOpenArray; } bool isHideLocal() const { return m_isHideLocal; } void isHideLocal(bool flag) { m_isHideLocal = flag; } bool isHideProtected() const { return m_isHideProtected; } @@ -2238,8 +2240,8 @@ public: bool isDeclTyped() const { return m_declTyped; } bool isInoutish() const { return m_direction.isInoutish(); } bool isNonOutput() const { return m_direction.isNonOutput(); } - bool isReadOnly() const { return m_direction.isReadOnly(); } - bool isWritable() const { return m_direction.isWritable(); } + bool isReadOnly() const VL_MT_SAFE { return m_direction.isReadOnly(); } + bool isWritable() const VL_MT_SAFE { return m_direction.isWritable(); } bool isTristate() const { return m_tristate; } bool isPrimaryIO() const { return m_primaryIO; } bool isPrimaryInish() const { return isPrimaryIO() && isNonOutput(); } @@ -2257,7 +2259,7 @@ public: bool isClassMember() const { return varType() == VVarType::MEMBER; } bool isStatementTemp() const { return (varType() == VVarType::STMTTEMP); } bool isXTemp() const { return (varType() == VVarType::XTEMP); } - bool isParam() const { + bool isParam() const VL_MT_SAFE { return (varType() == VVarType::LPARAM || varType() == VVarType::GPARAM); } bool isGParam() const { return (varType() == VVarType::GPARAM); } @@ -2269,7 +2271,7 @@ public: bool isUsedClock() const { return m_usedClock; } bool isUsedParam() const { return m_usedParam; } bool isUsedLoopIdx() const { return m_usedLoopIdx; } - bool isSc() const { return m_sc; } + bool isSc() const VL_MT_SAFE { return m_sc; } bool isScQuad() const; bool isScBv() const; bool isScUint() const; @@ -2281,8 +2283,8 @@ public: bool isSigUserRWPublic() const { return m_sigUserRWPublic; } bool isTrace() const { return m_trace; } bool isRand() const { return m_isRand; } - bool isConst() const { return m_isConst; } - bool isStatic() const { return m_isStatic; } + bool isConst() const VL_MT_SAFE { return m_isConst; } + bool isStatic() const VL_MT_SAFE { return m_isStatic; } bool isLatched() const { return m_isLatched; } bool isFuncLocal() const { return m_funcLocal; } bool isFuncReturn() const { return m_funcReturn; } @@ -2456,7 +2458,7 @@ public: bool source() const { return m_source; } void source(bool flag) { m_source = flag; } bool support() const { return m_support; } - void support(bool flag) { m_support = flag; } + void support(bool flag) VL_MT_SAFE { m_support = flag; } }; class AstVFile final : public AstNodeFile { // Verilog output file @@ -2490,7 +2492,7 @@ public: const char* broken() const override; void cloneRelink() override; bool timescaleMatters() const override { return false; } - AstClassPackage* classOrPackagep() const { return m_classOrPackagep; } + AstClassPackage* classOrPackagep() const VL_MT_SAFE { return m_classOrPackagep; } void classOrPackagep(AstClassPackage* classpackagep) { m_classOrPackagep = classpackagep; } AstNode* membersp() const { return stmtsp(); } void addMembersp(AstNode* nodep) { @@ -2523,7 +2525,7 @@ public: const char* broken() const override; void cloneRelink() override; bool timescaleMatters() const override { return false; } - AstClass* classp() const { return m_classp; } + AstClass* classp() const VL_MT_SAFE { return m_classp; } void classp(AstClass* classp) { m_classp = classp; } }; class AstIface final : public AstNodeModule { @@ -2716,19 +2718,19 @@ public: inline AstRange(FileLine* fl, int left, int right); inline AstRange(FileLine* fl, const VNumRange& range); ASTGEN_MEMBERS_AstRange; - inline int leftConst() const; - inline int rightConst() const; - int hiConst() const { + inline int leftConst() const VL_MT_SAFE; + inline int rightConst() const VL_MT_SAFE; + int hiConst() const VL_MT_SAFE { const int l = leftConst(); const int r = rightConst(); return l > r ? l : r; } - int loConst() const { + int loConst() const VL_MT_SAFE { const int l = leftConst(); const int r = rightConst(); return l > r ? r : l; } - int elementsConst() const { return hiConst() - loConst() + 1; } + int elementsConst() const VL_MT_SAFE { return hiConst() - loConst() + 1; } bool littleEndian() const { return leftConst() < rightConst(); } void dump(std::ostream& str) const override; virtual string emitC() { V3ERROR_NA_RETURN(""); } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 9758a9e58..10861568f 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -379,7 +379,7 @@ string AstVar::verilogKwd() const { } string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string& namespc, - bool asRef) const { + bool asRef) const VL_MT_SAFE { UASSERT_OBJ(!forReturn, this, "Internal data is never passed as return, but as first argument"); string ostatic; @@ -679,7 +679,7 @@ class AstNodeDType::CTypeRecursed final { public: string m_type; // The base type, e.g.: "Foo_t"s string m_dims; // Array dimensions, e.g.: "[3][2][1]" - string render(const string& name, bool isRef) const { + string render(const string& name, bool isRef) const VL_MT_SAFE { string out; out += m_type; if (!name.empty()) out += " "; @@ -696,7 +696,7 @@ public: } }; -string AstNodeDType::cType(const string& name, bool /*forFunc*/, bool isRef) const { +string AstNodeDType::cType(const string& name, bool /*forFunc*/, bool isRef) const VL_MT_SAFE { const CTypeRecursed info = cTypeRecurse(false); return info.render(name, isRef); } @@ -828,7 +828,7 @@ int AstNodeDType::widthPow2() const { return 1; } -bool AstNodeDType::isLiteralType() const { +bool AstNodeDType::isLiteralType() const VL_MT_SAFE { if (const auto* const dtypep = VN_CAST(skipRefp(), BasicDType)) { return dtypep->keyword().isLiteralType(); } else if (const auto* const dtypep = VN_CAST(skipRefp(), UnpackArrayDType)) { diff --git a/src/V3Broken.cpp b/src/V3Broken.cpp index d039e593b..c47463936 100644 --- a/src/V3Broken.cpp +++ b/src/V3Broken.cpp @@ -51,7 +51,7 @@ static class BrokenCntGlobal { uint8_t m_count = MIN_VALUE; public: - uint8_t get() const { + uint8_t get() const VL_MT_SAFE { UASSERT(MIN_VALUE <= m_count && m_count <= MAX_VALUE, "Invalid generation number"); return m_count; } diff --git a/src/V3EmitCBase.h b/src/V3EmitCBase.h index ca293eef1..8f7a97dd8 100644 --- a/src/V3EmitCBase.h +++ b/src/V3EmitCBase.h @@ -54,7 +54,7 @@ public: V3OutCFile* m_ofp = nullptr; bool m_trackText = false; // Always track AstText nodes // METHODS - V3OutCFile* ofp() const { return m_ofp; } + V3OutCFile* ofp() const VL_MT_SAFE { return m_ofp; } void puts(const string& str) { ofp()->puts(str); } void putbs(const string& str) { ofp()->putbs(str); } void putsDecoration(const string& str) { diff --git a/src/V3Error.h b/src/V3Error.h index 96244a46b..2aca831d9 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -160,8 +160,8 @@ public: explicit V3ErrorCode(const char* msgp); // Matching code or ERROR explicit V3ErrorCode(int _e) : m_e(static_cast(_e)) {} // Need () or GCC 4.8 false warning - constexpr operator en() const { return m_e; } - const char* ascii() const { + constexpr operator en() const VL_MT_SAFE { return m_e; } + const char* ascii() const VL_MT_SAFE { // clang-format off static const char* const names[] = { // Leading spaces indicate it can't be disabled. @@ -197,25 +197,25 @@ public: return names[m_e]; } // Warnings that default to off - bool defaultsOff() const { + bool defaultsOff() const VL_MT_SAFE { return (m_e == IMPERFECTSCH || m_e == I_CELLDEFINE || styleError()); } // Warnings that warn about nasty side effects - bool dangerous() const { return (m_e == COMBDLY); } + bool dangerous() const VL_MT_SAFE { return (m_e == COMBDLY); } // Warnings we'll present to the user as errors // Later -Werror- options may make more of these. - bool pretendError() const { + bool pretendError() const VL_MT_SAFE { return (m_e == ASSIGNIN || m_e == BADSTDPRAGMA || m_e == BLKANDNBLK || m_e == BLKLOOPINIT || m_e == CONTASSREG || m_e == IMPURE || m_e == PINNOTFOUND || m_e == PKGNODECL || m_e == PROCASSWIRE // Says IEEE || m_e == ZERODLY); } // Warnings to mention manual - bool mentionManual() const { + bool mentionManual() const VL_MT_SAFE { return (m_e == EC_FATALSRC || m_e == SYMRSVDWORD || pretendError()); } // Warnings that are lint only - bool lintError() const { + bool lintError() const VL_MT_SAFE { return (m_e == ALWCOMBORDER || m_e == BSSPACE || m_e == CASEINCOMPLETE || m_e == CASEOVERLAP || m_e == CASEWITHX || m_e == CASEX || m_e == CASTCONST || m_e == CMPCONST || m_e == COLONPLUS || m_e == ENDLABEL || m_e == IMPLICIT @@ -223,7 +223,7 @@ public: || m_e == UNSIGNED || m_e == WIDTH); } // Warnings that are style only - bool styleError() const { + bool styleError() const VL_MT_SAFE { return (m_e == ASSIGNDLY // More than style, but for backward compatibility || m_e == BLKSEQ || m_e == DEFPARAM || m_e == DECLFILENAME || m_e == EOFNEWLINE || m_e == IMPORTSTAR || m_e == INCABSPATH || m_e == PINCONNECTEMPTY @@ -232,7 +232,7 @@ public: || m_e == VARHIDDEN); } // Warnings that are unused only - bool unusedError() const { + bool unusedError() const VL_MT_SAFE { return (m_e == UNUSEDGENVAR || m_e == UNUSEDPARAM || m_e == UNUSEDSIGNAL); } static bool unusedMsg(const char* msgp) { return 0 == VL_STRCASECMP(msgp, "UNUSED"); } @@ -285,15 +285,15 @@ public: // CONSTRUCTORS // ACCESSORS static void debugDefault(int level) { s_debugDefault = level; } - static int debugDefault() { return s_debugDefault; } + static int debugDefault() VL_MT_SAFE { return s_debugDefault; } static void errorLimit(int level) { s_errorLimit = level; } - static int errorLimit() { return s_errorLimit; } + static int errorLimit() VL_MT_SAFE { return s_errorLimit; } static void warnFatal(bool flag) { s_warnFatal = flag; } static bool warnFatal() { return s_warnFatal; } static string msgPrefix(); // returns %Error/%Warn - static int errorCount() { return s_errCount; } + static int errorCount() VL_MT_SAFE { return s_errCount; } static int warnCount() { return s_warnCount; } - static bool errorContexted() { return s_errorContexted; } + static bool errorContexted() VL_MT_SAFE { return s_errorContexted; } static void errorContexted(bool flag) { s_errorContexted = flag; } // METHODS static void incErrors(); @@ -307,7 +307,7 @@ public: static void pretendError(V3ErrorCode code, bool flag) { s_pretendError[code] = flag; } static bool isError(V3ErrorCode code, bool supp); static string lineStr(const char* filename, int lineno); - static V3ErrorCode errorCode() { return s_errorCode; } + static V3ErrorCode errorCode() VL_MT_SAFE { return s_errorCode; } static void errorExitCb(ErrorExitCb cb) { s_errorExitCb = cb; } // When printing an error/warning, print prefix for multiline message diff --git a/src/V3FileLine.cpp b/src/V3FileLine.cpp index f2750e2a4..3704d4631 100644 --- a/src/V3FileLine.cpp +++ b/src/V3FileLine.cpp @@ -155,7 +155,7 @@ void VFileContent::pushText(const string& text) { m_lines.emplace_back(string(leftover, line_start)); // Might be "" } -string VFileContent::getLine(int lineno) const { +string VFileContent::getLine(int lineno) const VL_MT_SAFE { // Return error text rather than asserting so the user isn't left without a message // cppcheck-suppress negativeContainerIndex if (VL_UNCOVERABLE(lineno < 0 || lineno >= (int)m_lines.size())) { @@ -284,7 +284,7 @@ FileLine* FileLine::copyOrSameFileLine() { return newp; } -string FileLine::filebasename() const { +string FileLine::filebasename() const VL_MT_SAFE { string name = filename(); string::size_type pos; if ((pos = name.rfind('/')) != string::npos) name.erase(0, pos + 1); @@ -298,7 +298,7 @@ string FileLine::filebasenameNoExt() const { return name; } -string FileLine::firstColumnLetters() const { +string FileLine::firstColumnLetters() const VL_MT_SAFE { const char a = ((firstColumn() / 26) % 26) + 'a'; const char b = (firstColumn() % 26) + 'a'; return string(1, a) + string(1, b); @@ -322,7 +322,7 @@ string FileLine::asciiLineCol() const { + "-" + cvtToStr(lastColumn()) + "[" + (m_contentp ? m_contentp->ascii() : "ct0") + "+" + cvtToStr(m_contentLineno) + "]"); } -string FileLine::ascii() const { +string FileLine::ascii() const VL_MT_SAFE { // For most errors especially in the parser the lastLineno is more accurate than firstLineno return filename() + ":" + cvtToStr(lastLineno()) + ":" + cvtToStr(firstColumn()); } @@ -369,7 +369,7 @@ void FileLine::warnUnusedOff(bool flag) { warnOff(V3ErrorCode::UNUSEDSIGNAL, flag); } -bool FileLine::warnIsOff(V3ErrorCode code) const { +bool FileLine::warnIsOff(V3ErrorCode code) const VL_MT_SAFE { if (!msgEn().test(code)) return true; if (!defaultFileLine().msgEn().test(code)) return true; // Global overrides local if ((code.lintError() || code.styleError()) && !msgEn().test(V3ErrorCode::I_LINT)) { @@ -406,7 +406,7 @@ string FileLine::warnMore() const { return V3Error::warnMore(); } } -string FileLine::warnOther() const { +string FileLine::warnOther() const VL_MT_SAFE { if (lastLineno()) { return V3Error::warnMore() + ascii() + ": "; } else { @@ -414,7 +414,7 @@ string FileLine::warnOther() const { } } -string FileLine::source() const { +string FileLine::source() const VL_MT_SAFE { if (VL_UNCOVERABLE(!m_contentp)) { // LCOV_EXCL_START if (debug() || v3Global.opt.debugCheck()) { // The newline here is to work around the " | " @@ -425,7 +425,7 @@ string FileLine::source() const { } // LCOV_EXCL_STOP return m_contentp->getLine(m_contentLineno); } -string FileLine::prettySource() const { +string FileLine::prettySource() const VL_MT_SAFE { string out = source(); // Drop ignore trailing newline const string::size_type pos = out.find('\n'); @@ -434,7 +434,7 @@ string FileLine::prettySource() const { return VString::spaceUnprintable(out); } -string FileLine::warnContext(bool secondary) const { +string FileLine::warnContext(bool secondary) const VL_MT_SAFE { V3Error::errorContexted(true); if (!v3Global.opt.context()) return ""; string out; diff --git a/src/V3FileLine.h b/src/V3FileLine.h index b2cdb5823..b846bba22 100644 --- a/src/V3FileLine.h +++ b/src/V3FileLine.h @@ -86,7 +86,9 @@ class FileLineSingleton final { // Return index to intersection set msgEnSetIdx_t msgEnAnd(msgEnSetIdx_t lhsIdx, msgEnSetIdx_t rhsIdx); // Retrieve interned bitset at given interned index. The returned reference is not persistent. - const MsgEnBitSet& msgEn(msgEnSetIdx_t idx) const { return m_internedMsgEns.at(idx); } + const MsgEnBitSet& msgEn(msgEnSetIdx_t idx) const VL_MT_SAFE { + return m_internedMsgEns.at(idx); + } }; // All source lines from a file/stream, to enable errors to show sources @@ -110,7 +112,7 @@ class VFileContent final { public: void pushText(const string& text); // Add arbitrary text (need not be line-by-line) - string getLine(int lineno) const; + string getLine(int lineno) const VL_MT_SAFE; string ascii() const { return "ct" + cvtToStr(m_id); } }; std::ostream& operator<<(std::ostream& os, VFileContent* contentp); @@ -228,29 +230,31 @@ public: } // Advance last line/column based on given text void forwardToken(const char* textp, size_t size, bool trackLines = true); - int firstLineno() const { return m_firstLineno; } - int firstColumn() const { return m_firstColumn; } - int lastLineno() const { return m_lastLineno; } - int lastColumn() const { return m_lastColumn; } + int firstLineno() const VL_MT_SAFE { return m_firstLineno; } + int firstColumn() const VL_MT_SAFE { return m_firstColumn; } + int lastLineno() const VL_MT_SAFE { return m_lastLineno; } + int lastColumn() const VL_MT_SAFE { return m_lastColumn; } VFileContent* contentp() const { return m_contentp; } // If not otherwise more specific, use last lineno for errors etc, // as the parser errors etc generally make more sense pointing at the last parse point - int lineno() const { return m_lastLineno; } - string source() const; - string prettySource() const; // Source, w/stripped unprintables and newlines - FileLine* parent() const { return m_parent; } + int lineno() const VL_MT_SAFE { return m_lastLineno; } + string source() const VL_MT_SAFE; + string prettySource() const VL_MT_SAFE; // Source, w/stripped unprintables and newlines + FileLine* parent() const VL_MT_SAFE { return m_parent; } V3LangCode language() const { return singleton().numberToLang(filenameno()); } string ascii() const; string asciiLineCol() const; - int filenameno() const { return m_filenameno; } - string filename() const { return singleton().numberToName(filenameno()); } - bool filenameIsGlobal() const { + int filenameno() const VL_MT_SAFE { return m_filenameno; } + string filename() const VL_MT_SAFE { return singleton().numberToName(filenameno()); } + bool filenameIsGlobal() const VL_MT_SAFE { return (filename() == commandLineFilename() || filename() == builtInFilename()); } - string filenameLetters() const { return FileLineSingleton::filenameLetters(filenameno()); } - string filebasename() const; + string filenameLetters() const VL_MT_SAFE { + return FileLineSingleton::filenameLetters(filenameno()); + } + string filebasename() const VL_MT_SAFE; string filebasenameNoExt() const; - string firstColumnLetters() const; + string firstColumnLetters() const VL_MT_SAFE; string profileFuncname() const; string xmlDetailedLocation() const; string lineDirectiveStrg(int enterExit) const; @@ -281,8 +285,8 @@ public: // METHODS - Global // and match what GCC outputs - static string commandLineFilename() { return ""; } - static string builtInFilename() { return ""; } + static string commandLineFilename() VL_MT_SAFE { return ""; } + static string builtInFilename() VL_MT_SAFE { return ""; } static void globalWarnLintOff(bool flag) { defaultFileLine().warnLintOff(flag); } static void globalWarnStyleOff(bool flag) { defaultFileLine().warnStyleOff(flag); } static void globalWarnUnusedOff(bool flag) { defaultFileLine().warnUnusedOff(flag); } @@ -316,13 +320,13 @@ public: string warnMore() const; /// When building an error, prefix for printing secondary information /// from a different FileLine than the original error - string warnOther() const; + string warnOther() const VL_MT_SAFE; /// When building an error, current location in include etc /// If not used in a given error, automatically pasted at end of error - string warnContextPrimary() const { return warnContext(false); } + string warnContextPrimary() const VL_MT_SAFE { return warnContext(false); } /// When building an error, additional location for additional references /// Simplified information vs warnContextPrimary() to make dump clearer - string warnContextSecondary() const { return warnContext(true); } + string warnContextSecondary() const VL_MT_SAFE { return warnContext(true); } bool operator==(const FileLine& rhs) const { return (m_firstLineno == rhs.m_firstLineno && m_firstColumn == rhs.m_firstColumn && m_lastLineno == rhs.m_lastLineno && m_lastColumn == rhs.m_lastColumn @@ -345,8 +349,8 @@ public: } private: - string warnContext(bool secondary) const; - const MsgEnBitSet& msgEn() const { return singleton().msgEn(m_msgEnIdx); } + string warnContext(bool secondary) const VL_MT_SAFE; + const MsgEnBitSet& msgEn() const VL_MT_SAFE { return singleton().msgEn(m_msgEnIdx); } }; std::ostream& operator<<(std::ostream& os, FileLine* fileline); diff --git a/src/V3Global.h b/src/V3Global.h index 0f1baaa3b..7108f4148 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -127,7 +127,7 @@ public: void clear(); void shutdown(); // Release allocated resorces // ACCESSORS (general) - AstNetlist* rootp() const { return m_rootp; } + AstNetlist* rootp() const VL_MT_SAFE { return m_rootp; } VWidthMinUsage widthMinUsage() const { return m_widthMinUsage; } bool assertDTypesResolved() const { return m_assertDTypesResolved; } bool assertScoped() const { return m_assertScoped; } @@ -146,7 +146,7 @@ public: static string digitsFilename(int number); bool needTraceDumper() const { return m_needTraceDumper; } void needTraceDumper(bool flag) { m_needTraceDumper = flag; } - bool dpi() const { return m_dpi; } + bool dpi() const VL_MT_SAFE { return m_dpi; } void dpi(bool flag) { m_dpi = flag; } bool hasEvents() const { return m_hasEvents; } void setHasEvents() { m_hasEvents = true; } @@ -156,7 +156,7 @@ public: void setUsesTiming() { m_usesTiming = true; } bool hasForceableSignals() const { return m_hasForceableSignals; } void setHasForceableSignals() { m_hasForceableSignals = true; } - bool hasSCTextSections() const { return m_hasSCTextSections; } + bool hasSCTextSections() const VL_MT_SAFE { return m_hasSCTextSections; } void setHasSCTextSections() { m_hasSCTextSections = true; } V3HierBlockPlan* hierPlanp() const { return m_hierPlanp; } void hierPlanp(V3HierBlockPlan* plan) { diff --git a/src/V3Hash.cpp b/src/V3Hash.cpp index 38837e6c2..916e703a5 100644 --- a/src/V3Hash.cpp +++ b/src/V3Hash.cpp @@ -28,7 +28,7 @@ std::ostream& operator<<(std::ostream& os, const V3Hash& rhs) { return os << 'h' << std::hex << std::setw(8) << std::setfill('0') << rhs.value(); } -std::string V3Hash::toString() const { +std::string V3Hash::toString() const VL_MT_SAFE { std::ostringstream os; os << *this; return os.str(); diff --git a/src/V3Hash.h b/src/V3Hash.h index 3df97c079..0fda1b6c9 100644 --- a/src/V3Hash.h +++ b/src/V3Hash.h @@ -17,6 +17,8 @@ #ifndef VERILATOR_V3HASH_H_ #define VERILATOR_V3HASH_H_ +#include "verilatedos.h" + #include #include @@ -45,8 +47,8 @@ public: explicit V3Hash(const std::string& val); // METHODS - uint32_t value() const { return m_value; } - std::string toString() const; + uint32_t value() const VL_MT_SAFE { return m_value; } + std::string toString() const VL_MT_SAFE; // OPERATORS // Comparisons diff --git a/src/V3LangCode.h b/src/V3LangCode.h index 9deef638f..949767489 100644 --- a/src/V3LangCode.h +++ b/src/V3LangCode.h @@ -48,7 +48,7 @@ public: "1800-2005", "1800-2009", "1800-2012", "1800-2017"}; return names[m_e]; } - static V3LangCode mostRecent() { return V3LangCode{L1800_2017}; } + static V3LangCode mostRecent() VL_MT_SAFE { return V3LangCode{L1800_2017}; } bool systemVerilog() const { return m_e == L1800_2005 || m_e == L1800_2009 || m_e == L1800_2012 || m_e == L1800_2017; } diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 5916944a5..3b1b2ccc0 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -76,7 +76,7 @@ constexpr int MAX_SPRINTF_DOUBLE_SIZE //====================================================================== // Errors -void V3Number::v3errorEnd(const std::ostringstream& str) const { +void V3Number::v3errorEnd(const std::ostringstream& str) const VL_MT_SAFE { std::ostringstream nsstr; nsstr << str.str(); if (m_nodep) { @@ -88,7 +88,7 @@ void V3Number::v3errorEnd(const std::ostringstream& str) const { } } -void V3Number::v3errorEndFatal(const std::ostringstream& str) const { +void V3Number::v3errorEndFatal(const std::ostringstream& str) const VL_MT_SAFE { v3errorEnd(str); assert(0); // LCOV_EXCL_LINE VL_UNREACHABLE; @@ -874,7 +874,7 @@ string V3Number::toDecimalU() const { //====================================================================== // ACCESSORS - as numbers -uint32_t V3Number::toUInt() const { +uint32_t V3Number::toUInt() const VL_MT_SAFE { UASSERT(!isFourState(), "toUInt with 4-state " << *this); // We allow wide numbers that represent values <= 32 bits for (int i = 1; i < words(); ++i) { @@ -912,7 +912,7 @@ int32_t V3Number::toSInt() const { } } -uint64_t V3Number::toUQuad() const { +uint64_t V3Number::toUQuad() const VL_MT_SAFE { UASSERT(!isFourState(), "toUQuad with 4-state " << *this); // We allow wide numbers that represent values <= 64 bits if (isDouble()) return static_cast(toDouble()); diff --git a/src/V3Number.h b/src/V3Number.h index a419e9a0e..10032b68d 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -196,7 +196,7 @@ public: UASSERT(isNumber(), "`num` member accessed when data type is " << m_type); return isInlineNumber() ? m_inlineNumber.data() : m_dynamicNumber.data(); } - const ValueAndX* num() const { + const ValueAndX* num() const VL_MT_SAFE { UASSERT(isNumber(), "`num` member accessed when data type is " << m_type); return isInlineNumber() ? m_inlineNumber.data() : m_dynamicNumber.data(); } @@ -204,13 +204,13 @@ public: UASSERT(isString(), "`str` member accessed when data type is " << m_type); return m_string; } - const std::string& str() const { + const std::string& str() const VL_MT_SAFE { UASSERT(isString(), "`str` member accessed when data type is " << m_type); return m_string; } - int width() const { return m_width; } - V3NumberDataType type() const { return m_type; } + int width() const VL_MT_SAFE { return m_width; } + V3NumberDataType type() const VL_MT_SAFE { return m_type; } // METHODS void resize(int bitsCount) { @@ -275,19 +275,19 @@ public: } private: - static constexpr int bitsToWords(int bitsCount) { return (bitsCount + 31) / 32; } + static constexpr int bitsToWords(int bitsCount) VL_MT_SAFE { return (bitsCount + 31) / 32; } - bool isNumber() const { + bool isNumber() const VL_MT_SAFE { return m_type == V3NumberDataType::DOUBLE || m_type == V3NumberDataType::LOGIC; } - bool isInlineNumber() const { + bool isInlineNumber() const VL_MT_SAFE { return (m_width <= MAX_INLINE_WIDTH) && (m_type == V3NumberDataType::DOUBLE || m_type == V3NumberDataType::LOGIC); } - bool isDynamicNumber() const { + bool isDynamicNumber() const VL_MT_SAFE { return (m_width > MAX_INLINE_WIDTH) && (m_type == V3NumberDataType::LOGIC); } - bool isString() const { return m_type == V3NumberDataType::STRING; } + bool isString() const VL_MT_SAFE { return m_type == V3NumberDataType::STRING; } template void initInlineNumber(Args&&... args) { @@ -351,7 +351,7 @@ class V3Number final { public: void nodep(AstNode* nodep); - FileLine* fileline() const { return m_fileline; } + FileLine* fileline() const VL_MT_SAFE { return m_fileline; } V3Number& setZero(); V3Number& setQuad(uint64_t value); V3Number& setLong(uint32_t value); @@ -377,7 +377,7 @@ public: } private: - char bitIs(int bit) const { + char bitIs(int bit) const VL_MT_SAFE { if (bit >= m_data.width() || bit < 0) { // We never sign extend return '0'; @@ -403,14 +403,14 @@ private: } public: - bool bitIs0(int bit) const { + bool bitIs0(int bit) const VL_MT_SAFE { if (!isNumber()) return false; if (bit < 0) return false; if (bit >= m_data.width()) return !bitIsXZ(m_data.width() - 1); const ValueAndX v = m_data.num()[bit / 32]; return ((v.m_value & (1UL << (bit & 31))) == 0 && !(v.m_valueX & (1UL << (bit & 31)))); } - bool bitIs1(int bit) const { + bool bitIs1(int bit) const VL_MT_SAFE { if (!isNumber()) return false; if (bit < 0) return false; if (bit >= m_data.width()) return false; @@ -424,21 +424,21 @@ public: const ValueAndX v = m_data.num()[bit / 32]; return ((v.m_value & (1UL << (bit & 31))) && !(v.m_valueX & (1UL << (bit & 31)))); } - bool bitIsX(int bit) const { + bool bitIsX(int bit) const VL_MT_SAFE { if (!isNumber()) return false; if (bit < 0) return false; if (bit >= m_data.width()) return bitIsZ(m_data.width() - 1); const ValueAndX v = m_data.num()[bit / 32]; return ((v.m_value & (1UL << (bit & 31))) && (v.m_valueX & (1UL << (bit & 31)))); } - bool bitIsXZ(int bit) const { + bool bitIsXZ(int bit) const VL_MT_SAFE { if (!isNumber()) return false; if (bit < 0) return false; if (bit >= m_data.width()) return bitIsXZ(m_data.width() - 1); const ValueAndX v = m_data.num()[bit / 32]; return ((v.m_valueX & (1UL << (bit & 31)))); } - bool bitIsZ(int bit) const { + bool bitIsZ(int bit) const VL_MT_SAFE { if (!isNumber()) return false; if (bit < 0) return false; if (bit >= m_data.width()) return bitIsZ(m_data.width() - 1); @@ -447,17 +447,17 @@ public: } private: - uint32_t bitsValue(int lsb, int nbits) const { + uint32_t bitsValue(int lsb, int nbits) const VL_MT_SAFE { uint32_t v = 0; for (int bitn = 0; bitn < nbits; bitn++) { v |= (bitIs1(lsb + bitn) << bitn); } return v; } - int countX(int lsb, int nbits) const; - int countZ(int lsb, int nbits) const; + int countX(int lsb, int nbits) const VL_MT_SAFE; + int countZ(int lsb, int nbits) const VL_MT_SAFE; - int words() const { return ((width() + 31) / 32); } - uint32_t hiWordMask() const { return VL_MASK_I(width()); } + int words() const VL_MT_SAFE { return ((width() + 31) / 32); } + uint32_t hiWordMask() const VL_MT_SAFE { return VL_MASK_I(width()); } V3Number& opModDivGuts(const V3Number& lhs, const V3Number& rhs, bool is_modulus); @@ -549,8 +549,10 @@ private: } } static string displayPad(size_t fmtsize, char pad, bool left, const string& in); - string displayed(FileLine* fl, const string& vformat) const; - string displayed(const string& vformat) const { return displayed(m_fileline, vformat); } + string displayed(FileLine* fl, const string& vformat) const VL_MT_SAFE; + string displayed(const string& vformat) const VL_MT_SAFE { + return displayed(m_fileline, vformat); + } public: void v3errorEnd(const std::ostringstream& sstr) const; @@ -569,15 +571,15 @@ public: V3Number& setMask(int nbits); // IE if nbits=1, then 0b1, if 2->0b11, if 3->0b111 etc // ACCESSORS - string ascii(bool prefixed = true, bool cleanVerilog = false) const; + string ascii(bool prefixed = true, bool cleanVerilog = false) const VL_MT_SAFE; string displayed(AstNode* nodep, const string& vformat) const; static bool displayedFmtLegal(char format, bool isScan); // Is this a valid format letter? - int width() const { return m_data.width(); } + int width() const VL_MT_SAFE { return m_data.width(); } int widthMin() const; // Minimum width that can represent this number (~== log2(num)+1) - bool sized() const { return m_data.m_sized; } - bool autoExtend() const { return m_data.m_autoExtend; } + bool sized() const VL_MT_SAFE { return m_data.m_sized; } + bool autoExtend() const VL_MT_SAFE { return m_data.m_autoExtend; } bool isFromString() const { return m_data.m_fromString; } - V3NumberDataType dataType() const { return m_data.type(); } + V3NumberDataType dataType() const VL_MT_SAFE { return m_data.type(); } void dataType(V3NumberDataType newType) { if (dataType() == newType) return; UASSERT(newType != V3NumberDataType::UNINITIALIZED, "Can't set type to UNINITIALIZED."); @@ -590,17 +592,17 @@ public: } // Only correct for parsing of numbers from strings, otherwise not used // (use AstConst::isSigned()) - bool isSigned() const { return m_data.m_signed; } + bool isSigned() const VL_MT_SAFE { return m_data.m_signed; } void isSigned(bool ssigned) { m_data.m_signed = ssigned; } - bool isDouble() const { return dataType() == V3NumberDataType::DOUBLE; } - bool isString() const { return dataType() == V3NumberDataType::STRING; } - bool isNumber() const { + bool isDouble() const VL_MT_SAFE { return dataType() == V3NumberDataType::DOUBLE; } + bool isString() const VL_MT_SAFE { return dataType() == V3NumberDataType::STRING; } + bool isNumber() const VL_MT_SAFE { return m_data.type() == V3NumberDataType::LOGIC || m_data.type() == V3NumberDataType::DOUBLE; } - bool isNegative() const { return !isString() && bitIs1(width() - 1); } - bool isNull() const { return m_data.m_isNull; } - bool isFourState() const; + bool isNegative() const VL_MT_SAFE { return !isString() && bitIs1(width() - 1); } + bool isNull() const VL_MT_SAFE { return m_data.m_isNull; } + bool isFourState() const VL_MT_SAFE; bool hasZ() const { if (isString()) return false; for (int i = 0; i < words(); i++) { @@ -609,27 +611,27 @@ public: } return false; } - bool isAllZ() const; - bool isAllX() const; - bool isEqZero() const; + bool isAllZ() const VL_MT_SAFE; + bool isAllX() const VL_MT_SAFE; + bool isEqZero() const VL_MT_SAFE; bool isNeqZero() const; bool isBitsZero(int msb, int lsb) const; bool isEqOne() const; bool isEqAllOnes(int optwidth = 0) const; bool isCaseEq(const V3Number& rhs) const; // operator== bool isLtXZ(const V3Number& rhs) const; // operator< with XZ compared - bool isAnyX() const; + bool isAnyX() const VL_MT_SAFE; bool isAnyXZ() const; - bool isAnyZ() const; + bool isAnyZ() const VL_MT_SAFE; bool isMsbXZ() const { return bitIsXZ(m_data.width() - 1); } uint32_t toUInt() const; - int32_t toSInt() const; + int32_t toSInt() const VL_MT_SAFE; uint64_t toUQuad() const; - int64_t toSQuad() const; - string toString() const; - string toDecimalS() const; // return ASCII signed decimal number - string toDecimalU() const; // return ASCII unsigned decimal number - double toDouble() const; + int64_t toSQuad() const VL_MT_SAFE; + string toString() const VL_MT_SAFE; + string toDecimalS() const VL_MT_SAFE; // return ASCII signed decimal number + string toDecimalU() const VL_MT_SAFE; // return ASCII unsigned decimal number + double toDouble() const VL_MT_SAFE; V3Hash toHash() const; uint32_t edataWord(int eword) const; uint8_t dataByte(int byte) const; diff --git a/src/V3Options.cpp b/src/V3Options.cpp index c89d06975..cd556a21f 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -395,7 +395,7 @@ void V3Options::addForceInc(const string& filename) { m_forceIncs.push_back(file void V3Options::addArg(const string& arg) { m_impp->m_allArgs.push_back(arg); } -string V3Options::allArgsString() const { +string V3Options::allArgsString() const VL_MT_SAFE { string out; for (const string& i : m_impp->m_allArgs) { if (out != "") out += " "; @@ -1852,29 +1852,29 @@ void V3Options::setDebugMode(int level) { cout << "Starting " << version() << endl; } -unsigned V3Options::debugLevel(const string& tag) const { +unsigned V3Options::debugLevel(const string& tag) const VL_MT_SAFE { const auto iter = m_debugLevel.find(tag); return iter != m_debugLevel.end() ? iter->second : V3Error::debugDefault(); } -unsigned V3Options::debugSrcLevel(const string& srcfile_path) const { +unsigned V3Options::debugSrcLevel(const string& srcfile_path) const VL_MT_SAFE { // For simplicity, calling functions can just use __FILE__ for srcfile. // That means we need to strip the filenames: ../Foo.cpp -> Foo return debugLevel(V3Os::filenameNonDirExt(srcfile_path)); } -unsigned V3Options::dumpLevel(const string& tag) const { +unsigned V3Options::dumpLevel(const string& tag) const VL_MT_SAFE { const auto iter = m_dumpLevel.find(tag); return iter != m_dumpLevel.end() ? iter->second : 0; } -unsigned V3Options::dumpSrcLevel(const string& srcfile_path) const { +unsigned V3Options::dumpSrcLevel(const string& srcfile_path) const VL_MT_SAFE { // For simplicity, calling functions can just use __FILE__ for srcfile. // That means we need to strip the filenames: ../Foo.cpp -> Foo return dumpLevel(V3Os::filenameNonDirExt(srcfile_path)); } -bool V3Options::dumpTreeAddrids() const { +bool V3Options::dumpTreeAddrids() const VL_MT_SAFE { static int level = -1; if (VL_UNLIKELY(level < 0)) { const unsigned value = dumpLevel("tree-addrids"); diff --git a/src/V3Options.h b/src/V3Options.h index 7503408ad..a19c85269 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -146,7 +146,7 @@ public: static const char* const names[] = {"VerilatedVcd", "VerilatedFst"}; return names[m_e]; } - string sourceName() const { + string sourceName() const VL_MT_SAFE { static const char* const names[] = {"verilated_vcd", "verilated_fst"}; return names[m_e]; } @@ -401,10 +401,10 @@ public: V3Options(); ~V3Options(); void setDebugMode(int level); - unsigned debugLevel(const string& tag) const; - unsigned debugSrcLevel(const string& srcfile_path) const; - unsigned dumpLevel(const string& tag) const; - unsigned dumpSrcLevel(const string& srcfile_path) const; + unsigned debugLevel(const string& tag) const VL_MT_SAFE; + unsigned debugSrcLevel(const string& srcfile_path) const VL_MT_SAFE; + unsigned dumpLevel(const string& tag) const VL_MT_SAFE; + unsigned dumpSrcLevel(const string& srcfile_path) const VL_MT_SAFE; // METHODS void addCppFile(const string& filename); @@ -416,7 +416,7 @@ public: void addNoClocker(const string& signame); void addVFile(const string& filename); void addForceInc(const string& filename); - bool available() const { return m_available; } + bool available() const VL_MT_SAFE { return m_available; } void ccSet(); void notify(); @@ -426,8 +426,8 @@ public: bool preprocNoLine() const { return m_preprocNoLine; } bool underlineZero() const { return m_underlineZero; } string flags() const { return m_flags; } - bool systemC() const { return m_systemC; } - bool savable() const { return m_savable; } + bool systemC() const VL_MT_SAFE { return m_systemC; } + bool savable() const VL_MT_SAFE { return m_savable; } bool stats() const { return m_stats; } bool statsVars() const { return m_statsVars; } bool structsPacked() const { return m_structsPacked; } @@ -440,23 +440,25 @@ public: void buildDepBin(const string& flag) { m_buildDepBin = flag; } bool cdc() const { return m_cdc; } bool cmake() const { return m_cmake; } - bool context() const { return m_context; } - bool coverage() const { return m_coverageLine || m_coverageToggle || m_coverageUser; } + bool context() const VL_MT_SAFE { return m_context; } + bool coverage() const VL_MT_SAFE { + return m_coverageLine || m_coverageToggle || m_coverageUser; + } bool coverageLine() const { return m_coverageLine; } bool coverageToggle() const { return m_coverageToggle; } bool coverageUnderscore() const { return m_coverageUnderscore; } bool coverageUser() const { return m_coverageUser; } - bool debugCheck() const { return m_debugCheck; } + bool debugCheck() const VL_MT_SAFE { return m_debugCheck; } bool debugCollision() const { return m_debugCollision; } - bool debugEmitV() const { return m_debugEmitV; } + bool debugEmitV() const VL_MT_SAFE { return m_debugEmitV; } bool debugExitParse() const { return m_debugExitParse; } bool debugExitUvm() const { return m_debugExitUvm; } bool debugLeak() const { return m_debugLeak; } bool debugNondeterminism() const { return m_debugNondeterminism; } bool debugPartition() const { return m_debugPartition; } - bool debugProtect() const { return m_debugProtect; } + bool debugProtect() const VL_MT_SAFE { return m_debugProtect; } bool debugSelfTest() const { return m_debugSelfTest; } - bool decoration() const { return m_decoration; } + bool decoration() const VL_MT_SAFE { return m_decoration; } bool dpiHdrOnly() const { return m_dpiHdrOnly; } bool dumpDefines() const { return m_dumpLevel.count("defines") && m_dumpLevel.at("defines"); } bool dumpTreeDot() const { @@ -487,12 +489,12 @@ public: bool profExec() const { return m_profExec; } bool profPgo() const { return m_profPgo; } bool usesProfiler() const { return profExec() || profPgo(); } - bool protectIds() const { return m_protectIds; } + bool protectIds() const VL_MT_SAFE { return m_protectIds; } bool allPublic() const { return m_public; } bool publicFlatRW() const { return m_publicFlatRW; } - bool lintOnly() const { return m_lintOnly; } + bool lintOnly() const VL_MT_SAFE { return m_lintOnly; } bool ignc() const { return m_ignc; } - bool quietExit() const { return m_quietExit; } + bool quietExit() const VL_MT_SAFE { return m_quietExit; } bool reportUnoptflat() const { return m_reportUnoptflat; } bool verilate() const { return m_verilate; } bool vpi() const { return m_vpi; } @@ -500,10 +502,10 @@ public: bool xmlOnly() const { return m_xmlOnly; } bool topIfacesSupported() const { return lintOnly() && !hierarchical(); } - int buildJobs() const { return m_buildJobs; } + int buildJobs() const VL_MT_SAFE { return m_buildJobs; } int convergeLimit() const { return m_convergeLimit; } int coverageMaxWidth() const { return m_coverageMaxWidth; } - bool dumpTreeAddrids() const; + bool dumpTreeAddrids() const VL_MT_SAFE; int expandLimit() const { return m_expandLimit; } int gateStmts() const { return m_gateStmts; } int ifDepth() const { return m_ifDepth; } @@ -518,7 +520,7 @@ public: int pinsBv() const { return m_pinsBv; } int reloopLimit() const { return m_reloopLimit; } VOptionBool skipIdentical() const { return m_skipIdentical; } - int threads() const { return m_threads; } + int threads() const VL_MT_SAFE { return m_threads; } int threadsMaxMTasks() const { return m_threadsMaxMTasks; } bool mtasks() const { return (m_threads > 1); } VTimescale timeDefaultPrec() const { return m_timeDefaultPrec; } @@ -559,10 +561,10 @@ public: } return libName; } - string makeDir() const { return m_makeDir; } - string modPrefix() const { return m_modPrefix; } + string makeDir() const VL_MT_SAFE { return m_makeDir; } + string modPrefix() const VL_MT_SAFE { return m_modPrefix; } string pipeFilter() const { return m_pipeFilter; } - string prefix() const { return m_prefix; } + string prefix() const VL_MT_SAFE { return m_prefix; } // Not just called protectKey() to avoid bugs of not using protectKeyDefaulted() bool protectKeyProvided() const { return !m_protectKey.empty(); } string protectKeyDefaulted(); // Set default key if not set by user @@ -627,24 +629,24 @@ public: string traceClassBase() const { return m_traceFormat.classBase(); } string traceClassLang() const { return m_traceFormat.classBase() + (systemC() ? "Sc" : "C"); } string traceSourceBase() const { return m_traceFormat.sourceName(); } - string traceSourceLang() const { + string traceSourceLang() const VL_MT_SAFE { return m_traceFormat.sourceName() + (systemC() ? "_sc" : "_c"); } bool hierarchical() const { return m_hierarchical; } int hierChild() const { return m_hierChild; } - bool hierTop() const { return !m_hierChild && !m_hierBlocks.empty(); } + bool hierTop() const VL_MT_SAFE { return !m_hierChild && !m_hierBlocks.empty(); } const V3HierBlockOptSet& hierBlocks() const { return m_hierBlocks; } // Directory to save .tree, .dot, .dat, .vpp for hierarchical block top // Returns makeDir() unless top module of hierarchical verilation. - string hierTopDataDir() const { + string hierTopDataDir() const VL_MT_SAFE { return hierTop() ? (makeDir() + '/' + prefix() + "__hier.dir") : makeDir(); } // METHODS (from main) static string version(); static string argString(int argc, char** argv); ///< Return list of arguments as simple string - string allArgsString() const; ///< Return all passed arguments as simple string + string allArgsString() const VL_MT_SAFE; ///< Return all passed arguments as simple string // Return options for child hierarchical blocks when forTop==false, otherwise returns args for // the top module. string allArgsStringForHierBlock(bool forTop) const; diff --git a/src/V3Stats.h b/src/V3Stats.h index 7fb43db33..1a51619c6 100644 --- a/src/V3Stats.h +++ b/src/V3Stats.h @@ -75,13 +75,13 @@ class V3Statistic final { bool m_printit = true; ///< Print the results public: // METHODS - string stage() const { return m_stage; } - string name() const { return m_name; } - double count() const { return m_count; } - bool sumit() const { return m_sumit; } - bool perf() const { return m_perf; } - bool printit() const { return m_printit; } - virtual void dump(std::ofstream& os) const; + string stage() const VL_MT_SAFE { return m_stage; } + string name() const VL_MT_SAFE { return m_name; } + double count() const VL_MT_SAFE { return m_count; } + bool sumit() const VL_MT_SAFE { return m_sumit; } + bool perf() const VL_MT_SAFE { return m_perf; } + bool printit() const VL_MT_SAFE { return m_printit; } + virtual void dump(std::ofstream& os) const VL_MT_SAFE; void combineWith(V3Statistic* otherp) { m_count += otherp->count(); otherp->m_printit = false; diff --git a/src/astgen b/src/astgen index 3348d0e44..2c7f847aa 100755 --- a/src/astgen +++ b/src/astgen @@ -924,7 +924,7 @@ def write_ast_macros(filename): Ast{t}* cloneTree(bool cloneNext) {{ return static_cast(AstNode::cloneTree(cloneNext)); }} - Ast{t}* clonep() const {{ return static_cast(AstNode::clonep()); }} + Ast{t}* clonep() const VL_MT_SAFE {{ return static_cast(AstNode::clonep()); }} Ast{t}* addNext(Ast{t}* nodep) {{ return static_cast(AstNode::addNext(this, nodep)); }} ''', t=node.name) @@ -945,7 +945,7 @@ def write_ast_macros(filename): "op{n}p()").format(n=n, kind=kind) if monad == "List": emitBlock('''\ - Ast{kind}* {name}() const {{ return {retrieve}; }} + Ast{kind}* {name}() const VL_MT_SAFE {{ return {retrieve}; }} void add{Name}(Ast{kind}* nodep) {{ addNOp{n}p(reinterpret_cast(nodep)); }} ''', kind=kind, @@ -955,7 +955,7 @@ def write_ast_macros(filename): retrieve=retrieve) elif monad == "Optional": emitBlock('''\ - Ast{kind}* {name}() const {{ return {retrieve}; }} + Ast{kind}* {name}() const VL_MT_SAFE {{ return {retrieve}; }} void {name}(Ast{kind}* nodep) {{ setNOp{n}p(reinterpret_cast(nodep)); }} ''', kind=kind, @@ -964,7 +964,7 @@ def write_ast_macros(filename): retrieve=retrieve) else: emitBlock('''\ - Ast{kind}* {name}() const {{ return {retrieve}; }} + Ast{kind}* {name}() const VL_MT_SAFE {{ return {retrieve}; }} void {name}(Ast{kind}* nodep) {{ setOp{n}p(reinterpret_cast(nodep)); }} ''', kind=kind, From 27232238848d836e6d92815767c875ca8de83084 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Tue, 18 Oct 2022 17:29:51 -0400 Subject: [PATCH 128/177] Fix LSB error on --hierarchical submodules (#3539). --- Changes | 1 + src/V3EmitV.cpp | 4 +- test_regress/t/t_hier_block.v | 4 +- test_regress/t/t_hier_block_sc_trace_fst.out | 8 +- test_regress/t/t_hier_block_sc_trace_vcd.out | 220 +++++++++---------- test_regress/t/t_hier_block_trace_fst.out | 8 +- test_regress/t/t_hier_block_trace_vcd.out | 192 ++++++++-------- 7 files changed, 220 insertions(+), 217 deletions(-) diff --git a/Changes b/Changes index 1d06c5041..f4ca620a8 100644 --- a/Changes +++ b/Changes @@ -38,6 +38,7 @@ Verilator 5.001 devel * Add error on real edge event control. * Fix linker errors in user-facing timing functions (#3657). [Krzysztof Bieganski, Antmicro Ltd] * Fix null access on optimized-out fork statements (#3658). [Krzysztof Bieganski, Antmicro Ltd] +* Fix LSB error on --hierarchical submodules (#3539). [danbone] Verilator 4.228 2022-10-01 diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index ce76d6135..3b2d6a3fd 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -597,7 +597,9 @@ class EmitVBaseVisitor VL_NOT_FINAL : public EmitCBaseVisitor { } else if (nodep->isRanged()) { puts(" ["); puts(cvtToStr(nodep->hi())); - puts(":0] "); + puts(":"); + puts(cvtToStr(nodep->lo())); + puts("] "); } } void visit(AstConstDType* nodep) override { diff --git a/test_regress/t/t_hier_block.v b/test_regress/t/t_hier_block.v index 6e1d37098..e7c67c615 100644 --- a/test_regress/t/t_hier_block.v +++ b/test_regress/t/t_hier_block.v @@ -115,7 +115,7 @@ endmodule module sub1( input wire clk, - input wire [7:0] in, + input wire [11:4] in, // Uses higher LSB to cover bug3539 output wire [7:0] out); `HIER_BLOCK logic [7:0] ff; @@ -131,7 +131,7 @@ module sub2( logic [7:0] ff; - // dpi_import_func returns (dpi_eport_func(v) -1) + // dpi_import_func returns (dpi_eport_func(v) -1) import "DPI-C" context function int dpi_import_func(int v); export "DPI-C" function dpi_export_func; diff --git a/test_regress/t/t_hier_block_sc_trace_fst.out b/test_regress/t/t_hier_block_sc_trace_fst.out index 0594e8107..325da4741 100644 --- a/test_regress/t/t_hier_block_sc_trace_fst.out +++ b/test_regress/t/t_hier_block_sc_trace_fst.out @@ -1,5 +1,5 @@ $date - Tue Feb 22 23:52:17 2022 + Tue Oct 18 17:13:03 2022 $end $version @@ -41,7 +41,7 @@ $upscope $end $upscope $end $scope module i_sub1 $end $var wire 1 ! clk $end -$var wire 8 " in [7:0] $end +$var wire 8 " in [11:4] $end $var wire 8 # out [7:0] $end $upscope $end $scope module i_sub2 $end @@ -128,11 +128,11 @@ $upscope $end $upscope $end $scope module top.t.i_sub1 $end $var wire 1 > clk $end -$var wire 8 ? in [7:0] $end +$var wire 8 ? in [11:4] $end $var wire 8 @ out [7:0] $end $scope module sub1 $end $var wire 1 > clk $end -$var wire 8 ? in [7:0] $end +$var wire 8 ? in [11:4] $end $var wire 8 @ out [7:0] $end $var logic 8 A ff [7:0] $end $upscope $end diff --git a/test_regress/t/t_hier_block_sc_trace_vcd.out b/test_regress/t/t_hier_block_sc_trace_vcd.out index 6e0c6affa..cde885b1e 100644 --- a/test_regress/t/t_hier_block_sc_trace_vcd.out +++ b/test_regress/t/t_hier_block_sc_trace_vcd.out @@ -1,57 +1,57 @@ $version Generated by VerilatedVcd $end -$date Sun Dec 19 12:27:52 2021 $end +$date Tue Oct 18 17:12:31 2022 $end $timescale 1ps $end $scope module top $end $scope module t $end - $var wire 1 # clk $end + $var wire 1 * clk $end $var wire 32 + count [31:0] $end - $var wire 8 $ out0 [7:0] $end - $var wire 8 % out1 [7:0] $end - $var wire 8 & out2 [7:0] $end - $var wire 8 ' out3 [7:0] $end - $var wire 8 * out3_2 [7:0] $end + $var wire 8 # out0 [7:0] $end + $var wire 8 $ out1 [7:0] $end + $var wire 8 % out2 [7:0] $end + $var wire 8 & out3 [7:0] $end + $var wire 8 ' out3_2 [7:0] $end $var wire 8 ( out5 [7:0] $end $var wire 8 ) out6 [7:0] $end $scope module i_delay0 $end - $var wire 1 # clk $end - $var wire 8 ' in [7:0] $end + $var wire 1 * clk $end + $var wire 8 & in [7:0] $end $var wire 8 ( out [7:0] $end $upscope $end $scope module i_delay1 $end - $var wire 1 # clk $end + $var wire 1 * clk $end $var wire 8 ( in [7:0] $end $var wire 8 ) out [7:0] $end $upscope $end $scope module i_sub0 $end - $var wire 1 # clk $end - $var wire 8 ' in [7:0] $end - $var wire 8 $ out [7:0] $end + $var wire 1 * clk $end + $var wire 8 & in [7:0] $end + $var wire 8 # out [7:0] $end $scope module i_sub0 $end - $var wire 1 # clk $end - $var wire 8 ' in [7:0] $end - $var wire 8 $ out [7:0] $end + $var wire 1 * clk $end + $var wire 8 & in [7:0] $end + $var wire 8 # out [7:0] $end $upscope $end $upscope $end $scope module i_sub1 $end - $var wire 1 # clk $end + $var wire 1 * clk $end + $var wire 8 # in [11:4] $end + $var wire 8 $ out [7:0] $end + $upscope $end + $scope module i_sub2 $end + $var wire 1 * clk $end $var wire 8 $ in [7:0] $end $var wire 8 % out [7:0] $end $upscope $end - $scope module i_sub2 $end - $var wire 1 # clk $end + $scope module i_sub3 $end + $var wire 1 * clk $end $var wire 8 % in [7:0] $end $var wire 8 & out [7:0] $end $upscope $end - $scope module i_sub3 $end - $var wire 1 # clk $end - $var wire 8 & in [7:0] $end - $var wire 8 ' out [7:0] $end - $upscope $end $scope module i_sub3_2 $end - $var wire 1 # clk $end - $var wire 8 & in [7:0] $end - $var wire 8 * out [7:0] $end + $var wire 1 * clk $end + $var wire 8 % in [7:0] $end + $var wire 8 ' out [7:0] $end $upscope $end $upscope $end $upscope $end @@ -122,12 +122,12 @@ $timescale 1ps $end $upscope $end $scope module top.t.i_sub1 $end $var wire 1 D clk $end - $var wire 8 E in [7:0] $end + $var wire 8 E in [11:4] $end $var wire 8 F out [7:0] $end $scope module sub1 $end $var wire 1 D clk $end $var wire 8 G ff [7:0] $end - $var wire 8 E in [7:0] $end + $var wire 8 E in [11:4] $end $var wire 8 F out [7:0] $end $upscope $end $upscope $end @@ -1016,14 +1016,14 @@ $enddefinitions $end #0 -0# +b00000000 # b00000000 $ b00000000 % b00000000 & b00000000 ' b00000000 ( b00000000 ) -b00000000 * +0* b00000000000000000000000000000000 + 0- b00000000 . @@ -1502,11 +1502,11 @@ b00000001 ') b00000011 () b00000000000000000000000000000001 )) #10 -1# -b00000001 % +b00000001 $ +b00000010 % b00000010 & b00000010 ' -b00000010 * +1* b00000000000000000000000000000001 + 1- b00000010 . @@ -1677,7 +1677,7 @@ b0000000000000000000000000000000000000000000000000000000000000000000000000000000 #13 #14 #15 -0# +0* 0- 08 0? @@ -1703,11 +1703,11 @@ b0000000000000000000000000000000000000000000000000000000000000000000000000000000 #18 #19 #20 -1# -b00000010 $ -b00000101 & +b00000010 # +b00000101 % +b00000011 & b00000011 ' -b00000011 * +1* b00000000000000000000000000000010 + 1- b00000011 . @@ -2017,7 +2017,7 @@ b00000001 #) #23 #24 #25 -0# +0* 0- 08 0? @@ -2043,13 +2043,13 @@ b00000001 #) #28 #29 #30 -1# +b00000011 # b00000011 $ -b00000011 % -b00000111 & +b00000111 % +b00000101 & b00000101 ' b00000010 ( -b00000101 * +1* b00000000000000000000000000000011 + 1- b00000101 . @@ -2345,7 +2345,7 @@ b00000000 #) #33 #34 #35 -0# +0* 0- 08 0? @@ -2371,13 +2371,13 @@ b00000000 #) #38 #39 #40 -1# -b00000101 $ -b00000100 % +b00000101 # +b00000100 $ +b00001000 % b00001000 & b00001000 ' b00000011 ( -b00001000 * +1* b00000000000000000000000000000100 + 1- b00001000 . @@ -2673,7 +2673,7 @@ b00000001 #) #43 #44 #45 -0# +0* 0- 08 0? @@ -2699,12 +2699,12 @@ b00000001 #) #48 #49 #50 -1# -b00001000 $ -b00000110 % +b00001000 # +b00000110 $ +b00001010 & b00001010 ' b00000101 ( -b00001010 * +1* b00000000000000000000000000000101 + 1- b00001010 . @@ -2992,7 +2992,7 @@ b00000000 #) #53 #54 #55 -0# +0* 0- 08 0? @@ -3018,14 +3018,14 @@ b00000000 #) #58 #59 #60 -1# -b00001010 $ -b00001001 % -b00001010 & +b00001010 # +b00001001 $ +b00001010 % +b00001011 & b00001011 ' b00001000 ( b00000010 ) -b00001011 * +1* b00000000000000000000000000000110 + 1- b00001011 . @@ -3321,7 +3321,7 @@ b00000001 #) #63 #64 #65 -0# +0* 0- 08 0? @@ -3347,12 +3347,12 @@ b00000001 #) #68 #69 #70 -1# +b00001011 # b00001011 $ b00001011 % -b00001011 & b00001010 ( b00000011 ) +1* b00000000000000000000000000000111 + 1- b00001010 / @@ -3638,7 +3638,7 @@ b00000000 #) #73 #74 #75 -0# +0* 0- 08 0? @@ -3664,13 +3664,13 @@ b00000000 #) #78 #79 #80 -1# -b00001100 % +b00001100 $ +b00001101 % b00001101 & b00001101 ' b00001011 ( b00000101 ) -b00001101 * +1* b00000000000000000000000000001000 + 1- b00001101 . @@ -3968,7 +3968,7 @@ b00000001 #) #83 #84 #85 -0# +0* 0- 08 0? @@ -3994,12 +3994,12 @@ b00000001 #) #88 #89 #90 -1# -b00001101 $ -b00010000 & +b00001101 # +b00010000 % +b00001110 & b00001110 ' b00001000 ) -b00001110 * +1* b00000000000000000000000000001001 + 1- b00001110 . @@ -4295,7 +4295,7 @@ b00000000 #) #93 #94 #95 -0# +0* 0- 08 0? @@ -4321,14 +4321,14 @@ b00000000 #) #98 #99 #100 -1# +b00001110 # b00001110 $ -b00001110 % -b00010010 & +b00010010 % +b00010000 & b00010000 ' b00001101 ( b00001010 ) -b00010000 * +1* b00000000000000000000000000001010 + 1- b00010000 . @@ -4627,7 +4627,7 @@ b00000001 #) #103 #104 #105 -0# +0* 0- 08 0? @@ -4653,14 +4653,14 @@ b00000001 #) #108 #109 #110 -1# -b00010000 $ -b00001111 % +b00010000 # +b00001111 $ +b00010011 % b00010011 & b00010011 ' b00001110 ( b00001011 ) -b00010011 * +1* b00000000000000000000000000001011 + 1- b00010011 . @@ -4958,7 +4958,7 @@ b00000000 #) #113 #114 #115 -0# +0* 0- 08 0? @@ -4984,12 +4984,12 @@ b00000000 #) #118 #119 #120 -1# -b00010011 $ -b00010001 % +b00010011 # +b00010001 $ +b00010101 & b00010101 ' b00010000 ( -b00010101 * +1* b00000000000000000000000000001100 + 1- b00010101 . @@ -5277,7 +5277,7 @@ b00000001 #) #123 #124 #125 -0# +0* 0- 08 0? @@ -5303,14 +5303,14 @@ b00000001 #) #128 #129 #130 -1# -b00010101 $ -b00010100 % -b00010101 & +b00010101 # +b00010100 $ +b00010101 % +b00010110 & b00010110 ' b00010011 ( b00001101 ) -b00010110 * +1* b00000000000000000000000000001101 + 1- b00010110 . @@ -5606,7 +5606,7 @@ b00000000 #) #133 #134 #135 -0# +0* 0- 08 0? @@ -5632,12 +5632,12 @@ b00000000 #) #138 #139 #140 -1# +b00010110 # b00010110 $ b00010110 % -b00010110 & b00010101 ( b00001110 ) +1* b00000000000000000000000000001110 + 1- b00010101 / @@ -5923,7 +5923,7 @@ b00000001 #) #143 #144 #145 -0# +0* 0- 08 0? @@ -5949,13 +5949,13 @@ b00000001 #) #148 #149 #150 -1# -b00010111 % +b00010111 $ +b00011000 % b00011000 & b00011000 ' b00010110 ( b00010000 ) -b00011000 * +1* b00000000000000000000000000001111 + 1- b00011000 . @@ -6253,7 +6253,7 @@ b00000000 #) #153 #154 #155 -0# +0* 0- 08 0? @@ -6279,12 +6279,12 @@ b00000000 #) #158 #159 #160 -1# -b00011000 $ -b00011011 & +b00011000 # +b00011011 % +b00011001 & b00011001 ' b00010011 ) -b00011001 * +1* b00000000000000000000000000010000 + 1- b00011001 . @@ -6580,7 +6580,7 @@ b00000001 #) #163 #164 #165 -0# +0* 0- 08 0? @@ -6606,14 +6606,14 @@ b00000001 #) #168 #169 #170 -1# +b00011001 # b00011001 $ -b00011001 % -b00011101 & +b00011101 % +b00011011 & b00011011 ' b00011000 ( b00010101 ) -b00011011 * +1* b00000000000000000000000000010001 + 1- b00011011 . diff --git a/test_regress/t/t_hier_block_trace_fst.out b/test_regress/t/t_hier_block_trace_fst.out index 366eef0b4..1ae808652 100644 --- a/test_regress/t/t_hier_block_trace_fst.out +++ b/test_regress/t/t_hier_block_trace_fst.out @@ -1,5 +1,5 @@ $date - Tue Feb 22 23:53:34 2022 + Tue Oct 18 17:19:55 2022 $end $version @@ -42,7 +42,7 @@ $upscope $end $upscope $end $scope module i_sub1 $end $var wire 1 ! clk $end -$var wire 8 " in [7:0] $end +$var wire 8 " in [11:4] $end $var wire 8 # out [7:0] $end $upscope $end $scope module i_sub2 $end @@ -129,11 +129,11 @@ $upscope $end $upscope $end $scope module top.t.i_sub1 $end $var wire 1 > clk $end -$var wire 8 ? in [7:0] $end +$var wire 8 ? in [11:4] $end $var wire 8 @ out [7:0] $end $scope module sub1 $end $var wire 1 > clk $end -$var wire 8 ? in [7:0] $end +$var wire 8 ? in [11:4] $end $var wire 8 @ out [7:0] $end $var logic 8 A ff [7:0] $end $upscope $end diff --git a/test_regress/t/t_hier_block_trace_vcd.out b/test_regress/t/t_hier_block_trace_vcd.out index cd600aaa9..993b40bbe 100644 --- a/test_regress/t/t_hier_block_trace_vcd.out +++ b/test_regress/t/t_hier_block_trace_vcd.out @@ -1,58 +1,58 @@ $version Generated by VerilatedVcd $end -$date Sat Nov 27 16:51:43 2021 $end +$date Tue Oct 18 17:20:50 2022 $end $timescale 1ps $end $scope module top $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $scope module t $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 32 + count [31:0] $end $var wire 8 # out0 [7:0] $end $var wire 8 $ out1 [7:0] $end $var wire 8 % out2 [7:0] $end $var wire 8 & out3 [7:0] $end - $var wire 8 * out3_2 [7:0] $end - $var wire 8 ' out5 [7:0] $end - $var wire 8 ( out6 [7:0] $end + $var wire 8 ' out3_2 [7:0] $end + $var wire 8 ( out5 [7:0] $end + $var wire 8 ) out6 [7:0] $end $scope module i_delay0 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 & in [7:0] $end - $var wire 8 ' out [7:0] $end - $upscope $end - $scope module i_delay1 $end - $var wire 1 ) clk $end - $var wire 8 ' in [7:0] $end $var wire 8 ( out [7:0] $end $upscope $end + $scope module i_delay1 $end + $var wire 1 * clk $end + $var wire 8 ( in [7:0] $end + $var wire 8 ) out [7:0] $end + $upscope $end $scope module i_sub0 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 & in [7:0] $end $var wire 8 # out [7:0] $end $scope module i_sub0 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 & in [7:0] $end $var wire 8 # out [7:0] $end $upscope $end $upscope $end $scope module i_sub1 $end - $var wire 1 ) clk $end - $var wire 8 # in [7:0] $end + $var wire 1 * clk $end + $var wire 8 # in [11:4] $end $var wire 8 $ out [7:0] $end $upscope $end $scope module i_sub2 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 $ in [7:0] $end $var wire 8 % out [7:0] $end $upscope $end $scope module i_sub3 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 % in [7:0] $end $var wire 8 & out [7:0] $end $upscope $end $scope module i_sub3_2 $end - $var wire 1 ) clk $end + $var wire 1 * clk $end $var wire 8 % in [7:0] $end - $var wire 8 * out [7:0] $end + $var wire 8 ' out [7:0] $end $upscope $end $upscope $end $upscope $end @@ -123,12 +123,12 @@ $timescale 1ps $end $upscope $end $scope module top.t.i_sub1 $end $var wire 1 D clk $end - $var wire 8 E in [7:0] $end + $var wire 8 E in [11:4] $end $var wire 8 F out [7:0] $end $scope module sub1 $end $var wire 1 D clk $end $var wire 8 G ff [7:0] $end - $var wire 8 E in [7:0] $end + $var wire 8 E in [11:4] $end $var wire 8 F out [7:0] $end $upscope $end $upscope $end @@ -1023,8 +1023,8 @@ b00000000 % b00000000 & b00000000 ' b00000000 ( -0) -b00000000 * +b00000000 ) +0* b00000000000000000000000000000000 + 0- b00000000 . @@ -1506,8 +1506,8 @@ b00000000000000000000000000000001 )) b00000001 $ b00000010 % b00000010 & -1) -b00000010 * +b00000010 ' +1* b00000000000000000000000000000001 + 1- b00000010 . @@ -1674,7 +1674,7 @@ b0000000000000000000000000000000000000000000000000000000000000000000000000000000 b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100 t( b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000101 x( #15 -0) +0* 0- 08 0? @@ -1699,8 +1699,8 @@ b0000000000000000000000000000000000000000000000000000000000000000000000000000000 b00000010 # b00000101 % b00000011 & -1) -b00000011 * +b00000011 ' +1* b00000000000000000000000000000010 + 1- b00000011 . @@ -2006,7 +2006,7 @@ b00000011 !) b00000010 ") b00000001 #) #25 -0) +0* 0- 08 0? @@ -2032,9 +2032,9 @@ b00000011 # b00000011 $ b00000111 % b00000101 & -b00000010 ' -1) -b00000101 * +b00000101 ' +b00000010 ( +1* b00000000000000000000000000000011 + 1- b00000101 . @@ -2326,7 +2326,7 @@ b00000000 !) b00000000 ") b00000000 #) #35 -0) +0* 0- 08 0? @@ -2352,9 +2352,9 @@ b00000101 # b00000100 $ b00001000 % b00001000 & -b00000011 ' -1) -b00001000 * +b00001000 ' +b00000011 ( +1* b00000000000000000000000000000100 + 1- b00001000 . @@ -2646,7 +2646,7 @@ b00000011 !) b00000010 ") b00000001 #) #45 -0) +0* 0- 08 0? @@ -2671,9 +2671,9 @@ b00000001 #) b00001000 # b00000110 $ b00001010 & -b00000101 ' -1) -b00001010 * +b00001010 ' +b00000101 ( +1* b00000000000000000000000000000101 + 1- b00001010 . @@ -2957,7 +2957,7 @@ b00000000 !) b00000000 ") b00000000 #) #55 -0) +0* 0- 08 0? @@ -2983,10 +2983,10 @@ b00001010 # b00001001 $ b00001010 % b00001011 & -b00001000 ' -b00000010 ( -1) -b00001011 * +b00001011 ' +b00001000 ( +b00000010 ) +1* b00000000000000000000000000000110 + 1- b00001011 . @@ -3278,7 +3278,7 @@ b00000011 !) b00000010 ") b00000001 #) #65 -0) +0* 0- 08 0? @@ -3303,9 +3303,9 @@ b00000001 #) b00001011 # b00001011 $ b00001011 % -b00001010 ' -b00000011 ( -1) +b00001010 ( +b00000011 ) +1* b00000000000000000000000000000111 + 1- b00001010 / @@ -3587,7 +3587,7 @@ b00000000 !) b00000000 ") b00000000 #) #75 -0) +0* 0- 08 0? @@ -3612,10 +3612,10 @@ b00000000 #) b00001100 $ b00001101 % b00001101 & -b00001011 ' -b00000101 ( -1) -b00001101 * +b00001101 ' +b00001011 ( +b00000101 ) +1* b00000000000000000000000000001000 + 1- b00001101 . @@ -3909,7 +3909,7 @@ b00000011 !) b00000010 ") b00000001 #) #85 -0) +0* 0- 08 0? @@ -3934,9 +3934,9 @@ b00000001 #) b00001101 # b00010000 % b00001110 & -b00001000 ( -1) -b00001110 * +b00001110 ' +b00001000 ) +1* b00000000000000000000000000001001 + 1- b00001110 . @@ -4228,7 +4228,7 @@ b00000000 !) b00000000 ") b00000000 #) #95 -0) +0* 0- 08 0? @@ -4254,10 +4254,10 @@ b00001110 # b00001110 $ b00010010 % b00010000 & -b00001101 ' -b00001010 ( -1) -b00010000 * +b00010000 ' +b00001101 ( +b00001010 ) +1* b00000000000000000000000000001010 + 1- b00010000 . @@ -4552,7 +4552,7 @@ b00000011 !) b00000010 ") b00000001 #) #105 -0) +0* 0- 08 0? @@ -4578,10 +4578,10 @@ b00010000 # b00001111 $ b00010011 % b00010011 & -b00001110 ' -b00001011 ( -1) -b00010011 * +b00010011 ' +b00001110 ( +b00001011 ) +1* b00000000000000000000000000001011 + 1- b00010011 . @@ -4875,7 +4875,7 @@ b00000000 !) b00000000 ") b00000000 #) #115 -0) +0* 0- 08 0? @@ -4900,9 +4900,9 @@ b00000000 #) b00010011 # b00010001 $ b00010101 & -b00010000 ' -1) -b00010101 * +b00010101 ' +b00010000 ( +1* b00000000000000000000000000001100 + 1- b00010101 . @@ -5186,7 +5186,7 @@ b00000011 !) b00000010 ") b00000001 #) #125 -0) +0* 0- 08 0? @@ -5212,10 +5212,10 @@ b00010101 # b00010100 $ b00010101 % b00010110 & -b00010011 ' -b00001101 ( -1) -b00010110 * +b00010110 ' +b00010011 ( +b00001101 ) +1* b00000000000000000000000000001101 + 1- b00010110 . @@ -5507,7 +5507,7 @@ b00000000 !) b00000000 ") b00000000 #) #135 -0) +0* 0- 08 0? @@ -5532,9 +5532,9 @@ b00000000 #) b00010110 # b00010110 $ b00010110 % -b00010101 ' -b00001110 ( -1) +b00010101 ( +b00001110 ) +1* b00000000000000000000000000001110 + 1- b00010101 / @@ -5816,7 +5816,7 @@ b00000011 !) b00000010 ") b00000001 #) #145 -0) +0* 0- 08 0? @@ -5841,10 +5841,10 @@ b00000001 #) b00010111 $ b00011000 % b00011000 & -b00010110 ' -b00010000 ( -1) -b00011000 * +b00011000 ' +b00010110 ( +b00010000 ) +1* b00000000000000000000000000001111 + 1- b00011000 . @@ -6138,7 +6138,7 @@ b00000000 !) b00000000 ") b00000000 #) #155 -0) +0* 0- 08 0? @@ -6163,9 +6163,9 @@ b00000000 #) b00011000 # b00011011 % b00011001 & -b00010011 ( -1) -b00011001 * +b00011001 ' +b00010011 ) +1* b00000000000000000000000000010000 + 1- b00011001 . @@ -6457,7 +6457,7 @@ b00000011 !) b00000010 ") b00000001 #) #165 -0) +0* 0- 08 0? @@ -6483,10 +6483,10 @@ b00011001 # b00011001 $ b00011101 % b00011011 & -b00011000 ' -b00010101 ( -1) -b00011011 * +b00011011 ' +b00011000 ( +b00010101 ) +1* b00000000000000000000000000010001 + 1- b00011011 . From b930d0731a35e4b5a1f0079e08d18d0ba3d2023a Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Tue, 18 Oct 2022 20:04:09 -0400 Subject: [PATCH 129/177] Fix foreach and pre/post increment in functions (#3613). --- Changes | 3 ++- src/V3LinkInc.cpp | 7 +++++++ src/V3LinkLValue.cpp | 2 +- src/V3Task.cpp | 2 +- test_regress/t/t_assoc2.v | 12 ++++++++++++ 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index f4ca620a8..263c68d37 100644 --- a/Changes +++ b/Changes @@ -36,9 +36,10 @@ Verilator 5.001 devel * Support linting for top module interfaces (#3635). [Kanad Kanhere] * Add --dump-tree-dot to enable dumping Ast Tree .dot files (#3636). [Marcel Chang] * Add error on real edge event control. +* Fix LSB error on --hierarchical submodules (#3539). [danbone] +* Fix foreach and pre/post increment in functions (#3613). [Nandu Raj] * Fix linker errors in user-facing timing functions (#3657). [Krzysztof Bieganski, Antmicro Ltd] * Fix null access on optimized-out fork statements (#3658). [Krzysztof Bieganski, Antmicro Ltd] -* Fix LSB error on --hierarchical submodules (#3539). [danbone] Verilator 4.228 2022-10-01 diff --git a/src/V3LinkInc.cpp b/src/V3LinkInc.cpp index 7b16f8722..7c12c4e28 100644 --- a/src/V3LinkInc.cpp +++ b/src/V3LinkInc.cpp @@ -60,6 +60,7 @@ private: }; // STATE + AstNodeFTask* m_ftaskp = nullptr; // Function or task we're inside int m_modIncrementsNum = 0; // Var name counter InsertMode m_insMode = IM_BEFORE; // How to insert AstNode* m_insStmtp = nullptr; // Where to insert statement @@ -94,6 +95,11 @@ private: m_modIncrementsNum = 0; iterateChildren(nodep); } + void visit(AstNodeFTask* nodep) override { + VL_RESTORER(m_ftaskp); + m_ftaskp = nodep; + iterateChildren(nodep); + } void visit(AstWhile* nodep) override { // Special, as statements need to be put in different places // Preconditions insert first just before themselves (the normal @@ -241,6 +247,7 @@ private: const string name = string("__Vincrement") + cvtToStr(++m_modIncrementsNum); AstVar* const varp = new AstVar(fl, VVarType::BLOCKTEMP, name, VFlagChildDType(), varrefp->varp()->subDTypep()->cloneTree(true)); + if (m_ftaskp) varp->funcLocal(true); // Declare the variable insertBeforeStmt(nodep, varp); diff --git a/src/V3LinkLValue.cpp b/src/V3LinkLValue.cpp index b58b6f476..903f0a90c 100644 --- a/src/V3LinkLValue.cpp +++ b/src/V3LinkLValue.cpp @@ -301,9 +301,9 @@ private: } } void visit(AstNodeFTask* nodep) override { + VL_RESTORER(m_ftaskp); m_ftaskp = nodep; iterateChildren(nodep); - m_ftaskp = nullptr; } void visit(AstNodeFTaskRef* nodep) override { AstNode* pinp = nodep->pinsp(); diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 9a2bd2b53..528d7a32b 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -175,7 +175,7 @@ private: // Find all var->varscope mappings, for later cleanup for (AstNode* stmtp = nodep->varsp(); stmtp; stmtp = stmtp->nextp()) { if (AstVarScope* const vscp = VN_CAST(stmtp, VarScope)) { - if (vscp->varp()->isFuncLocal()) { + if (vscp->varp()->isFuncLocal() || vscp->varp()->isUsedLoopIdx()) { UINFO(9, " funcvsc " << vscp << endl); m_varToScopeMap.insert( std::make_pair(std::make_pair(nodep, vscp->varp()), vscp)); diff --git a/test_regress/t/t_assoc2.v b/test_regress/t/t_assoc2.v index 8cea07e6b..5b94ca3b9 100644 --- a/test_regress/t/t_assoc2.v +++ b/test_regress/t/t_assoc2.v @@ -16,18 +16,30 @@ module t (/*AUTOARG*/ integer cyc = 0; + int imap[int]; + // associative array of an associative array logic [31:0] a [logic [31:0]][logic [63:0]]; + task disp(); + int i = 60; + imap[i++] = 600; + imap[i++] = 601; + foreach (imap[k]) $display("imap[%0d] = %0d", k, imap[k]); + endtask + always @ (posedge clk) begin cyc <= cyc + 1; if (cyc == 1) begin a[5][8] = 8; a[5][9] = 9; + imap[10] = 100; + imap[11] = 101; end else if (cyc == 2) begin `checkh(a[5][8], 8); `checkh(a[5][9], 9); + disp(); $write("*-* All Finished *-*\n"); $finish; end From e7068369fe3e7f61036ed5468cda180d86b1ec4e Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Tue, 18 Oct 2022 21:10:35 -0400 Subject: [PATCH 130/177] Fix $display of fixed-width numbers (#3565). --- Changes | 1 + include/verilated.cpp | 108 ++++++++---- src/V3Number.cpp | 154 +++++++++--------- test_regress/t/t_display.out | 5 +- test_regress/t/t_display.v | 10 +- test_regress/t/t_extract_static_const.v | 16 +- .../t/t_extract_static_const_multimodule.v | 20 +-- 7 files changed, 188 insertions(+), 126 deletions(-) diff --git a/Changes b/Changes index 263c68d37..f9a79bec6 100644 --- a/Changes +++ b/Changes @@ -37,6 +37,7 @@ Verilator 5.001 devel * Add --dump-tree-dot to enable dumping Ast Tree .dot files (#3636). [Marcel Chang] * Add error on real edge event control. * Fix LSB error on --hierarchical submodules (#3539). [danbone] +* Fix $display of fixed-width numbers (#3565). [Iztok Jeras] * Fix foreach and pre/post increment in functions (#3613). [Nandu Raj] * Fix linker errors in user-facing timing functions (#3657). [Krzysztof Bieganski, Antmicro Ltd] * Fix null access on optimized-out fork statements (#3658). [Krzysztof Bieganski, Antmicro Ltd] diff --git a/include/verilated.cpp b/include/verilated.cpp index 46192052f..506c1e5f7 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -905,15 +905,22 @@ void _vl_vsformat(std::string& output, const char* formatp, va_list ap) VL_MT_SA digits = append.length(); } const int needmore = width - digits; - std::string padding; if (needmore > 0) { - if (pctp && pctp[0] && pctp[1] == '0') { // %0 - padding.append(needmore, '0'); // Pre-pad zero - } else { + std::string padding; + if (left) { padding.append(needmore, ' '); // Pre-pad spaces + output += append + padding; + } else { + if (pctp && pctp[0] && pctp[1] == '0') { // %0 + padding.append(needmore, '0'); // Pre-pad zero + } else { + padding.append(needmore, ' '); // Pre-pad spaces + } + output += padding + append; } + } else { + output += append; } - output += left ? (append + padding) : (padding + append); break; } case '#': { // Unsigned decimal @@ -927,15 +934,22 @@ void _vl_vsformat(std::string& output, const char* formatp, va_list ap) VL_MT_SA digits = append.length(); } const int needmore = width - digits; - std::string padding; if (needmore > 0) { - if (pctp && pctp[0] && pctp[1] == '0') { // %0 - padding.append(needmore, '0'); // Pre-pad zero - } else { + std::string padding; + if (left) { padding.append(needmore, ' '); // Pre-pad spaces + output += append + padding; + } else { + if (pctp && pctp[0] && pctp[1] == '0') { // %0 + padding.append(needmore, '0'); // Pre-pad zero + } else { + padding.append(needmore, ' '); // Pre-pad spaces + } + output += padding + append; } + } else { + output += append; } - output += left ? (append + padding) : (padding + append); break; } case 't': { // Time @@ -944,21 +958,62 @@ void _vl_vsformat(std::string& output, const char* formatp, va_list ap) VL_MT_SA output += _vl_vsformat_time(t_tmp, ld, timeunit, left, width); break; } - case 'b': - for (; lsb >= 0; --lsb) output += (VL_BITRSHIFT_W(lwp, lsb) & 1) + '0'; - break; - case 'o': - for (; lsb >= 0; --lsb) { - lsb = (lsb / 3) * 3; // Next digit - // Octal numbers may span more than one wide word, - // so we need to grab each bit separately and check for overrun - // Octal is rare, so we'll do it a slow simple way - output += static_cast( - '0' + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 0)) ? 1 : 0) - + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 1)) ? 2 : 0) - + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 2)) ? 4 : 0)); + case 'b': // FALLTHRU + case 'o': // FALLTHRU + case 'x': { + if (widthSet || left) { + lsb = VL_MOSTSETBITP1_W(VL_WORDS_I(lbits), lwp); + lsb = (lsb < 1) ? 0 : (lsb - 1); + } + + std::string append; + int digits; + switch (fmt) { + case 'b': { + digits = lsb + 1; + for (; lsb >= 0; --lsb) append += (VL_BITRSHIFT_W(lwp, lsb) & 1) + '0'; + break; + } + case 'o': { + digits = (lsb + 1 + 2) / 3; + for (; lsb >= 0; --lsb) { + lsb = (lsb / 3) * 3; // Next digit + // Octal numbers may span more than one wide word, + // so we need to grab each bit separately and check for overrun + // Octal is rare, so we'll do it a slow simple way + append += static_cast( + '0' + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 0)) ? 1 : 0) + + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 1)) ? 2 : 0) + + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 2)) ? 4 : 0)); + } + break; + } + default: { // 'x' + digits = (lsb + 1 + 3) / 4; + for (; lsb >= 0; --lsb) { + lsb = (lsb / 4) * 4; // Next digit + const IData charval = VL_BITRSHIFT_W(lwp, lsb) & 0xf; + append += "0123456789abcdef"[charval]; + } + break; + } + } // switch + + const int needmore = width - digits; + if (needmore > 0) { + std::string padding; + if (left) { + padding.append(needmore, ' '); // Pre-pad spaces + output += append + padding; + } else { + padding.append(needmore, '0'); // Pre-pad zero + output += padding + append; + } + } else { + output += append; } break; + } // b / o / x case 'u': case 'z': { // Packed 4-state const bool is_4_state = (fmt == 'z'); @@ -984,13 +1039,6 @@ void _vl_vsformat(std::string& output, const char* formatp, va_list ap) VL_MT_SA } } break; - case 'x': - for (; lsb >= 0; --lsb) { - lsb = (lsb / 4) * 4; // Next digit - const IData charval = VL_BITRSHIFT_W(lwp, lsb) & 0xf; - output += "0123456789abcdef"[charval]; - } - break; default: { // LCOV_EXCL_START const std::string msg = std::string{"Unknown _vl_vsformat code: "} + pos[0]; VL_FATAL_MT(__FILE__, __LINE__, "", msg.c_str()); diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 3b1b2ccc0..4ce1c1423 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -603,86 +603,90 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const { string str; const char code = tolower(pos[0]); switch (code) { - case 'b': { - int bit = width() - 1; - if (fmtsize == "0") - while (bit && bitIs0(bit)) bit--; - for (; bit >= 0; bit--) { - if (bitIs0(bit)) { - str += '0'; - } else if (bitIs1(bit)) { - str += '1'; - } else if (bitIsZ(bit)) { - str += 'z'; - } else { - str += 'x'; - } - } - return str; - } - case 'o': { - int bit = width() - 1; - if (fmtsize == "0") - while (bit && bitIs0(bit)) bit--; - while ((bit % 3) != 2) bit++; - for (; bit > 0; bit -= 3) { - const int numX = countX(bit - 2, 3); - const int numZ = countZ(bit - 2, 3); - if (numX == 3 || numX == width() - (bit - 2)) { - str += 'x'; - continue; - } - if (numZ == 3 || numZ == width() - (bit - 2)) { - str += 'z'; - continue; - } - if (numX > 0) { - str += 'X'; - continue; - } - if (numZ > 0) { - str += 'Z'; - continue; - } - const int v = bitsValue(bit - 2, 3); - str += static_cast('0' + v); - } - return str; - } - case 'h': + case 'b': // FALLTHRU + case 'o': // FALLTHRU + case 'h': // FALLTHRU case 'x': { int bit = width() - 1; - if (fmtsize == "0") - while (bit && bitIs0(bit)) bit--; - while ((bit % 4) != 3) bit++; - for (; bit > 0; bit -= 4) { - const int numX = countX(bit - 3, 4); - const int numZ = countZ(bit - 3, 4); - if (numX == 4 || numX == width() - (bit - 3)) { - str += 'x'; - continue; + if (left || !fmtsize.empty()) { + while (bit && bitIs0(bit)) --bit; + } + switch (code) { + case 'b': { + for (; bit >= 0; --bit) { + if (bitIs0(bit)) { + str += '0'; + } else if (bitIs1(bit)) { + str += '1'; + } else if (bitIsZ(bit)) { + str += 'z'; + } else { + str += 'x'; + } } - if (numZ == 4 || numZ == width() - (bit - 3)) { - str += 'z'; - continue; - } - if (numX > 0) { - str += 'X'; - continue; - } - if (numZ > 0) { - str += 'Z'; - continue; - } - const int v = bitsValue(bit - 3, 4); - if (v >= 10) { - str += static_cast('a' + v - 10); - } else { + break; + } + case 'o': { + while ((bit % 3) != 2) ++bit; + for (; bit > 0; bit -= 3) { + const int numX = countX(bit - 2, 3); + const int numZ = countZ(bit - 2, 3); + if (numX == 3 || numX == width() - (bit - 2)) { + str += 'x'; + continue; + } + if (numZ == 3 || numZ == width() - (bit - 2)) { + str += 'z'; + continue; + } + if (numX > 0) { + str += 'X'; + continue; + } + if (numZ > 0) { + str += 'Z'; + continue; + } + const int v = bitsValue(bit - 2, 3); str += static_cast('0' + v); } + break; } + default: { // h/x + while ((bit % 4) != 3) ++bit; + for (; bit > 0; bit -= 4) { + const int numX = countX(bit - 3, 4); + const int numZ = countZ(bit - 3, 4); + if (numX == 4 || numX == width() - (bit - 3)) { + str += 'x'; + continue; + } + if (numZ == 4 || numZ == width() - (bit - 3)) { + str += 'z'; + continue; + } + if (numX > 0) { + str += 'X'; + continue; + } + if (numZ > 0) { + str += 'Z'; + continue; + } + const int v = bitsValue(bit - 3, 4); + if (v >= 10) { + str += static_cast('a' + v - 10); + } else { + str += static_cast('0' + v); + } + } + break; + } + } // switch + const size_t fmtsizen = static_cast(std::atoi(fmtsize.c_str())); + str = displayPad(fmtsizen, (left ? ' ' : '0'), left, str); return str; - } + } // case b/d/x/o case 'c': { if (width() > 8) fl->v3warn(WIDTH, "$display-like format of %c format of > 8 bit value"); const unsigned int v = bitsValue(0, 8); @@ -714,7 +718,7 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const { case 't': // Time case 'd': { // Unsigned decimal const bool issigned = (code == '~'); - if (fmtsize == "") { + if (fmtsize == "" && !left) { const double mantissabits = width() - (issigned ? 1 : 0); // To get the number of digits required, we want to compute // log10(2**mantissabits) and round it up. To be able to handle @@ -753,7 +757,7 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const { } } } - const bool zeropad = fmtsize.length() > 0 && fmtsize[0] == '0'; + const bool zeropad = fmtsize.length() > 0 && fmtsize[0] == '0' && !left; // fmtsize might have changed since we parsed the %fmtsize const size_t fmtsizen = static_cast(std::atoi(fmtsize.c_str())); str = displayPad(fmtsizen, (zeropad ? '0' : ' '), left, str); diff --git a/test_regress/t/t_display.out b/test_regress/t/t_display.out index 375c5dfc9..0ecc886a1 100644 --- a/test_regress/t/t_display.out +++ b/test_regress/t/t_display.out @@ -37,7 +37,10 @@ [0] %P="sv-str" [0] %u=dcba %0u=dcba [0] %U=dcba %0U=dcba -[0] %D= 12 %d= 12 %01d=12 %06d=000012 %6d= 12 +[0] %D= 12 %d= 12 %01d=12 %06d=000012 %6d= 12 %-06d=12 %-6d=12 +[0] %X=00c %x=00c %01x=c %06x=00000c %6x=00000c %-06x=c %-6x=c +[0] %O=014 %o=014 %01o=14 %06o=000014 %6o=000014 %-06o=14 %-6o=14 +[0] %B=000001100 %b=000001100 %01b=1100 %06b=001100 %6b=001100 %-06b=1100 %-6b=1100 [0] %t= 0 %03t= 0 %0t=0 [0] %s=! %s= what! %s= hmmm!1234 diff --git a/test_regress/t/t_display.v b/test_regress/t/t_display.v index 0da4596e2..24aa8f428 100644 --- a/test_regress/t/t_display.v +++ b/test_regress/t/t_display.v @@ -120,8 +120,14 @@ module t; {"a","b","c","d"}, {"a","b","c","d"}); // Avoid binary output // %z is tested in t_sys_sformat.v - $display("[%0t] %%D=%D %%d=%d %%01d=%01d %%06d=%06d %%6d=%6d", $time, - nine, nine, nine, nine, nine); + $display("[%0t] %%D=%D %%d=%d %%01d=%01d %%06d=%06d %%6d=%6d %%-06d=%-06d %%-6d=%-6d", $time, + nine, nine, nine, nine, nine, nine, nine); + $display("[%0t] %%X=%X %%x=%x %%01x=%01x %%06x=%06x %%6x=%6x %%-06x=%-06x %%-6x=%-6x", $time, + nine, nine, nine, nine, nine, nine, nine); + $display("[%0t] %%O=%O %%o=%o %%01o=%01o %%06o=%06o %%6o=%6o %%-06o=%-06o %%-6o=%-6o", $time, + nine, nine, nine, nine, nine, nine, nine); + $display("[%0t] %%B=%B %%b=%b %%01b=%01b %%06b=%06b %%6b=%6b %%-06b=%-06b %%-6b=%-6b", $time, + nine, nine, nine, nine, nine, nine, nine); $display("[%0t] %%t=%t %%03t=%03t %%0t=%0t", $time, $time, $time, $time); $display; diff --git a/test_regress/t/t_extract_static_const.v b/test_regress/t/t_extract_static_const.v index 2a49caced..5e258dbe9 100755 --- a/test_regress/t/t_extract_static_const.v +++ b/test_regress/t/t_extract_static_const.v @@ -27,14 +27,14 @@ module t (/*AUTOARG*/); initial begin // Note: Base index via $c to prevent optimizatoin by Verilator - $display("0x%32x", C[$c(0*32)+:32]); - $display("0x%32x", D[$c(1*32)+:32]); - $display("0x%32x", C[$c(2*32)+:32]); - $display("0x%32x", D[$c(3*32)+:32]); - $display("0x%32x", C[$c(4*32)+:32]); - $display("0x%32x", D[$c(5*32)+:32]); - $display("0x%32x", C[$c(6*32)+:32]); - $display("0x%32x", D[$c(7*32)+:32]); + $display("0x%8x", C[$c(0*32)+:32]); + $display("0x%8x", D[$c(1*32)+:32]); + $display("0x%8x", C[$c(2*32)+:32]); + $display("0x%8x", D[$c(3*32)+:32]); + $display("0x%8x", C[$c(4*32)+:32]); + $display("0x%8x", D[$c(5*32)+:32]); + $display("0x%8x", C[$c(6*32)+:32]); + $display("0x%8x", D[$c(7*32)+:32]); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_extract_static_const_multimodule.v b/test_regress/t/t_extract_static_const_multimodule.v index 0f959991a..7885ecb5b 100755 --- a/test_regress/t/t_extract_static_const_multimodule.v +++ b/test_regress/t/t_extract_static_const_multimodule.v @@ -29,11 +29,11 @@ module a( trig_o <= 1'd0; if (trig_i) begin // Note: Base index via $c to prevent optimizatoin by Verilator - $display("0x%32x", C[$c(0*32)+:32]); - $display("0x%32x", C[$c(2*32)+:32]); - $display("0x%32x", C[$c(4*32)+:32]); - $display("0x%32x", C[$c(6*32)+:32]); - $display("0x%256x", C); + $display("0x%8x", C[$c(0*32)+:32]); + $display("0x%8x", C[$c(2*32)+:32]); + $display("0x%8x", C[$c(4*32)+:32]); + $display("0x%8x", C[$c(6*32)+:32]); + $display("0x%32x", C); trig_o <= 1'd1; end end @@ -61,11 +61,11 @@ module b( trig_o <= 1'd0; if (trig_i) begin // Note: Base index via $c to prevent optimizatoin by Verilator - $display("0x%32x", C[$c(1*32)+:32]); - $display("0x%32x", C[$c(3*32)+:32]); - $display("0x%32x", C[$c(5*32)+:32]); - $display("0x%32x", C[$c(7*32)+:32]); - $display("0x%256x", C); + $display("0x%8x", C[$c(1*32)+:32]); + $display("0x%8x", C[$c(3*32)+:32]); + $display("0x%8x", C[$c(5*32)+:32]); + $display("0x%8x", C[$c(7*32)+:32]); + $display("0x%32x", C); trig_o <= 1'd1; end end From f6f13c7fda71cacf1ae61c8901bcd1a395b58a38 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Tue, 18 Oct 2022 21:17:52 -0400 Subject: [PATCH 131/177] Internals: Comment out debug that may flag ASAN problem (#3574) --- src/V3Ast.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 0c9f0442b..20652b369 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -1075,9 +1075,10 @@ void AstNode::dumpPtrs(std::ostream& os) const { if (user5p()) os << " user5p=" << cvtToHex(user5p()); if (m_iterpp) { os << " iterpp=" << cvtToHex(m_iterpp); - os << "*=" << cvtToHex(*m_iterpp); + // This may cause address sanitizer failures as iterpp can be stale + // os << "*=" << cvtToHex(*m_iterpp); } - os << endl; + os << std::endl; } void AstNode::dumpTree(std::ostream& os, const string& indent, int maxDepth) const { From bec0b7d4d079843b3adb332a767021c7f176bcf2 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Thu, 20 Oct 2022 02:52:29 +0200 Subject: [PATCH 132/177] Disallow delays with `--lib-create` (#3691) --- docs/guide/exe_verilator.rst | 4 ++++ docs/guide/verilating.rst | 2 ++ src/Verilator.cpp | 3 +++ 3 files changed, 9 insertions(+) diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index e4ece604e..727371a69 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -741,6 +741,8 @@ Summary: model has a time resolution that is always compatible with the time precision of the upper instantiating module. + Designs compiled using this option cannot use :vlopt:`--timing` with delays. + See also :vlopt:`--protect-lib`. .. option:: +libext+[+][...] @@ -1068,6 +1070,8 @@ Summary: in the distribution for a demonstration of how to build and use the DPI library. + Designs compiled using this option cannot use :vlopt:`--timing` with delays. + .. option:: --public This is only for historical debug use. Using it may result in diff --git a/docs/guide/verilating.rst b/docs/guide/verilating.rst index 2af18c1f0..3921a1964 100644 --- a/docs/guide/verilating.rst +++ b/docs/guide/verilating.rst @@ -113,6 +113,8 @@ Hierarchy blocks have some limitations including: hierarchical model and pass up into another hierarchical model or the top module. +* Delays are not allowed in hierarchy blocks. + But, the following usage is supported: * Nested hierarchy blocks. A hierarchy block may instantiate other diff --git a/src/Verilator.cpp b/src/Verilator.cpp index aaf666ea0..9604eb151 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -559,6 +559,9 @@ static void process() { // Output DPI protected library files if (!v3Global.opt.libCreate().empty()) { + if (v3Global.rootp()->delaySchedulerp()) { + v3warn(E_UNSUPPORTED, "Unsupported: --lib-create with --timing and delays"); + } V3ProtectLib::protect(); V3EmitV::emitvFiles(); V3EmitC::emitcFiles(); From 22243d1e492d759e461fe53dd81da761bbaa09d1 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Thu, 20 Oct 2022 03:59:26 +0200 Subject: [PATCH 133/177] Support class type params without defaults (#3693) --- src/V3LinkDot.cpp | 15 +++++++++++++++ src/V3LinkParse.cpp | 18 ++++++++---------- src/V3Param.cpp | 16 ++++++++++++++-- src/verilog.y | 4 +++- test_regress/t/t_class_param_bad.out | 9 --------- test_regress/t/t_class_param_bad1.out | 9 +++++++++ ...ass_param_bad.pl => t_class_param_bad1.pl} | 0 ...class_param_bad.v => t_class_param_bad1.v} | 0 test_regress/t/t_class_param_bad2.out | 5 +++++ test_regress/t/t_class_param_bad2.pl | 19 +++++++++++++++++++ test_regress/t/t_class_param_bad2.v | 14 ++++++++++++++ test_regress/t/t_class_vparam.v | 2 +- 12 files changed, 88 insertions(+), 23 deletions(-) delete mode 100644 test_regress/t/t_class_param_bad.out create mode 100644 test_regress/t/t_class_param_bad1.out rename test_regress/t/{t_class_param_bad.pl => t_class_param_bad1.pl} (100%) rename test_regress/t/{t_class_param_bad.v => t_class_param_bad1.v} (100%) create mode 100644 test_regress/t/t_class_param_bad2.out create mode 100755 test_regress/t/t_class_param_bad2.pl create mode 100644 test_regress/t/t_class_param_bad2.v diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 1f4757e89..6c3885ff1 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -161,6 +161,7 @@ private: bool m_forPrimary; // First link bool m_forPrearray; // Compress cell__[array] refs bool m_forScopeCreation; // Remove VarXRefs for V3Scope + bool m_removeVoidParamedClasses; // Remove classes with void params public: // METHODS @@ -207,6 +208,7 @@ public: m_forPrimary = (step == LDS_PRIMARY); m_forPrearray = (step == LDS_PARAMED || step == LDS_PRIMARY); m_forScopeCreation = (step == LDS_SCOPED); + m_removeVoidParamedClasses = (step == LDS_PARAMED); s_errorThisp = this; V3Error::errorExitCb(preErrorDumpHandler); // If get error, dump self } @@ -220,6 +222,7 @@ public: bool forPrimary() const { return m_forPrimary; } bool forPrearray() const { return m_forPrearray; } bool forScopeCreation() const { return m_forScopeCreation; } + bool removeVoidParamedClasses() const { return m_removeVoidParamedClasses; } // METHODS static string nodeTextType(AstNode* nodep) { @@ -902,6 +905,18 @@ class LinkDotFindVisitor final : public VNVisitor { void visit(AstClass* nodep) override { UASSERT_OBJ(m_curSymp, nodep, "Class not under module/package/$unit"); UINFO(8, " " << nodep << endl); + // Remove classes that have void params, as they were only used for the parametrization + // step and will not be instantiated + if (m_statep->removeVoidParamedClasses()) { + for (auto* stmtp = nodep->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + if (auto* dtypep = VN_CAST(stmtp, ParamTypeDType)) { + if (VN_IS(dtypep->subDTypep(), VoidDType)) { + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + return; + } + } + } + } VL_RESTORER(m_scope); VL_RESTORER(m_classOrPackagep); VL_RESTORER(m_modSymp); diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 32e656dc4..7dd81fb2a 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -209,18 +209,16 @@ private: } if (VN_IS(nodep->subDTypep(), ParseTypeDType)) { // It's a parameter type. Use a different node type for this. - AstNodeDType* const dtypep = VN_CAST(nodep->valuep(), NodeDType); - if (!dtypep) { - nodep->v3error( - "Parameter type's initial value isn't a type: " << nodep->prettyNameQ()); - nodep->unlinkFrBack(); - } else { + AstNodeDType* dtypep = VN_CAST(nodep->valuep(), NodeDType); + if (dtypep) { dtypep->unlinkFrBack(); - AstNode* const newp = new AstParamTypeDType( - nodep->fileline(), nodep->varType(), nodep->name(), VFlagChildDType(), dtypep); - nodep->replaceWith(newp); - VL_DO_DANGLING(nodep->deleteTree(), nodep); + } else { + dtypep = new AstVoidDType{nodep->fileline()}; } + AstNode* const newp = new AstParamTypeDType{nodep->fileline(), nodep->varType(), + nodep->name(), VFlagChildDType{}, dtypep}; + nodep->replaceWith(newp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); return; } diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 3ff5e8b56..baef76ddf 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -801,6 +801,15 @@ class ParamProcessor final { srcModpr = modInfop->m_modp; } + for (auto* stmtp = srcModpr->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + if (auto* dtypep = VN_CAST(stmtp, ParamTypeDType)) { + if (VN_IS(dtypep->subDTypep(), VoidDType)) { + nodep->v3error("Missing type parameter: " << dtypep->prettyNameQ()); + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + } + } + } + // Delete the parameters from the cell; they're not relevant any longer. if (paramsp) paramsp->unlinkFrBackWithNext()->deleteTree(); return any_overrides; @@ -822,8 +831,11 @@ class ParamProcessor final { } void classRefDeparam(AstClassRefDType* nodep, AstNodeModule*& srcModpr) { - if (nodeDeparamCommon(nodep, srcModpr, nodep->paramsp(), nullptr, false)) - nodep->classp(VN_AS(srcModpr, Class)); + if (nodeDeparamCommon(nodep, srcModpr, nodep->paramsp(), nullptr, false)) { + AstClass* const classp = VN_AS(srcModpr, Class); + nodep->classp(classp); + nodep->classOrPackagep(classp); + } } public: diff --git a/src/verilog.y b/src/verilog.y index 86f8edc58..f366c5b61 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -2882,7 +2882,9 @@ list_of_param_assignments: // ==IEEE: list_of_param_assignments type_assignment: // ==IEEE: type_assignment // // note exptOrDataType being a data_type is only for yPARAMETER yTYPE - idAny/*new-parameter*/ sigAttrListE '=' data_type + idAny/*new-parameter*/ sigAttrListE + { $$ = VARDONEA($1, *$1, nullptr, $2); } + | idAny/*new-parameter*/ sigAttrListE '=' data_type { $$ = VARDONEA($1, *$1, nullptr, $2); $$->valuep($4); } ; diff --git a/test_regress/t/t_class_param_bad.out b/test_regress/t/t_class_param_bad.out deleted file mode 100644 index ef96bc2f3..000000000 --- a/test_regress/t/t_class_param_bad.out +++ /dev/null @@ -1,9 +0,0 @@ -%Error-PINNOTFOUND: t/t_class_param_bad.v:12:11: Parameter pin not found: 'PARAMBAD' - : ... Suggested alternative: 'PARAMB' - 12 | Cls #(.PARAMBAD(1)) c; - | ^~~~~~~~ - ... For error description see https://verilator.org/warn/PINNOTFOUND?v=latest -%Error-PINNOTFOUND: t/t_class_param_bad.v:13:14: Parameter pin not found: '__paramNumber2' - 13 | Cls #(13, 1) cd; - | ^ -%Error: Exiting due to diff --git a/test_regress/t/t_class_param_bad1.out b/test_regress/t/t_class_param_bad1.out new file mode 100644 index 000000000..756f6e838 --- /dev/null +++ b/test_regress/t/t_class_param_bad1.out @@ -0,0 +1,9 @@ +%Error-PINNOTFOUND: t/t_class_param_bad1.v:12:11: Parameter pin not found: 'PARAMBAD' + : ... Suggested alternative: 'PARAMB' + 12 | Cls #(.PARAMBAD(1)) c; + | ^~~~~~~~ + ... For error description see https://verilator.org/warn/PINNOTFOUND?v=latest +%Error-PINNOTFOUND: t/t_class_param_bad1.v:13:14: Parameter pin not found: '__paramNumber2' + 13 | Cls #(13, 1) cd; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_class_param_bad.pl b/test_regress/t/t_class_param_bad1.pl similarity index 100% rename from test_regress/t/t_class_param_bad.pl rename to test_regress/t/t_class_param_bad1.pl diff --git a/test_regress/t/t_class_param_bad.v b/test_regress/t/t_class_param_bad1.v similarity index 100% rename from test_regress/t/t_class_param_bad.v rename to test_regress/t/t_class_param_bad1.v diff --git a/test_regress/t/t_class_param_bad2.out b/test_regress/t/t_class_param_bad2.out new file mode 100644 index 000000000..35b901184 --- /dev/null +++ b/test_regress/t/t_class_param_bad2.out @@ -0,0 +1,5 @@ +%Error: t/t_class_param_bad2.v:12:4: Missing type parameter: 'PARAMB' + : ... In instance t + 12 | Cls c; + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_class_param_bad2.pl b/test_regress/t/t_class_param_bad2.pl new file mode 100755 index 000000000..19ba90d40 --- /dev/null +++ b/test_regress/t/t_class_param_bad2.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_class_param_bad2.v b/test_regress/t/t_class_param_bad2.v new file mode 100644 index 000000000..9332fdde8 --- /dev/null +++ b/test_regress/t/t_class_param_bad2.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +class Cls #(type PARAMB); +endclass + +module t (/*AUTOARG*/); + + Cls c; // Missing type param + +endmodule diff --git a/test_regress/t/t_class_vparam.v b/test_regress/t/t_class_vparam.v index 660dbb5fa..6d927d6ac 100644 --- a/test_regress/t/t_class_vparam.v +++ b/test_regress/t/t_class_vparam.v @@ -14,7 +14,7 @@ virtual class vclass #(type CTYPE_t = arg_class_t, int I = 0); pure virtual function void funcname(paramed_class_t #(CTYPE_t) v); endclass -class paramed_class_t #(type TYPE = int, int I = 0); +class paramed_class_t #(type TYPE, int I = 0); TYPE memb; endclass From e6add5e0b81731907af416709aa22516152150b0 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Thu, 20 Oct 2022 12:28:55 +0200 Subject: [PATCH 134/177] Fix trace activity with --timing (#3576) (#3678) (#3696) --- src/V3Trace.cpp | 6 ++ test_regress/t/t_timing_clkgen1.v | 9 --- test_regress/t/t_timing_clkgen_unsup.out | 4 +- test_regress/t/t_timing_trace.out | 79 +++++++++++++----------- test_regress/t/t_timing_trace.pl | 4 +- test_regress/t/t_timing_trace.v | 43 +++++++++++++ 6 files changed, 96 insertions(+), 49 deletions(-) create mode 100644 test_regress/t/t_timing_trace.v diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index e0fa8ce8b..8b90cbf9f 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -432,6 +432,12 @@ private: if (AstCCall* const callp = VN_CAST(insertp, CCall)) { callp->addNextHere(setterp); } else if (AstCFunc* const funcp = VN_CAST(insertp, CFunc)) { + // If there are awaits, insert the setter after each await + if (funcp->isCoroutine() && funcp->stmtsp()) { + funcp->stmtsp()->foreachAndNext([&](AstCAwait* awaitp) { + if (awaitp->nextp()) awaitp->addNextHere(setterp->cloneTree(false)); + }); + } funcp->addStmtsp(setterp); } else { insertp->v3fatalSrc("Bad trace activity vertex"); diff --git a/test_regress/t/t_timing_clkgen1.v b/test_regress/t/t_timing_clkgen1.v index 3ef565d9b..12cc19a41 100644 --- a/test_regress/t/t_timing_clkgen1.v +++ b/test_regress/t/t_timing_clkgen1.v @@ -4,8 +4,6 @@ // any use, without warranty, 2020 by Wilson Snyder. // SPDX-License-Identifier: CC0-1.0 -`define STRINGIFY(x) `"x`" - module clkgen(output bit clk); initial begin #(8.0:5:3) clk = 1; // Middle is default @@ -40,11 +38,4 @@ module t(/*AUTOARG*/); $finish; end end - -`ifdef TEST_TRACING - initial begin - $dumpfile({`STRINGIFY(`TEST_OBJ_DIR),"/simx.vcd"}); - $dumpvars; - end -`endif endmodule diff --git a/test_regress/t/t_timing_clkgen_unsup.out b/test_regress/t/t_timing_clkgen_unsup.out index fbbd81192..8ee56209f 100644 --- a/test_regress/t/t_timing_clkgen_unsup.out +++ b/test_regress/t/t_timing_clkgen_unsup.out @@ -1,5 +1,5 @@ -%Warning-MINTYPMAXDLY: t/t_timing_clkgen1.v:11:13: Unsupported: minimum/typical/maximum delay expressions. Using the typical delay - 11 | #(8.0:5:3) clk = 1; +%Warning-MINTYPMAXDLY: t/t_timing_clkgen1.v:9:13: Unsupported: minimum/typical/maximum delay expressions. Using the typical delay + 9 | #(8.0:5:3) clk = 1; | ^ ... For warning description see https://verilator.org/warn/MINTYPMAXDLY?v=latest ... Use "/* verilator lint_off MINTYPMAXDLY */" and lint_on around source to disable this message. diff --git a/test_regress/t/t_timing_trace.out b/test_regress/t/t_timing_trace.out index 52df60e90..f4c2f96b8 100644 --- a/test_regress/t/t_timing_trace.out +++ b/test_regress/t/t_timing_trace.out @@ -1,67 +1,76 @@ $version Generated by VerilatedVcd $end -$date Wed Oct 5 13:59:40 2022 $end +$date Thu Oct 20 09:56:59 2022 $end $timescale 1ps $end $scope module TOP $end $scope module t $end - $var wire 1 # clk $end - $var wire 32 $ cyc [31:0] $end - $scope module clkgen $end - $var wire 1 # clk $end - $upscope $end + $var wire 32 * CLK_HALF_PERIOD [31:0] $end + $var wire 32 ) CLK_PERIOD [31:0] $end + $var wire 1 $ a $end + $var wire 1 % b $end + $var wire 1 & c $end + $var wire 1 ( clk $end + $var wire 1 ' d $end + $var wire 1 # rst $end $upscope $end $upscope $end $enddefinitions $end #0 -0# -b00000000000000000000000000000000 $ -#5 1# -b00000000000000000000000000000001 $ +0$ +0% +0& +0' +0( +b00000000000000000000000000001010 ) +b00000000000000000000000000000101 * +#5 +1( #10 0# +1% +0( #15 -1# -b00000000000000000000000000000010 $ +1( #20 -0# +0( #25 -1# -b00000000000000000000000000000011 $ +1( #30 -0# +0( #35 -1# -b00000000000000000000000000000100 $ +1( #40 -0# +0( #45 -1# -b00000000000000000000000000000101 $ +1( #50 -0# +0( #55 -1# -b00000000000000000000000000000110 $ +1( #60 -0# +0( #65 -1# -b00000000000000000000000000000111 $ +1( #70 -0# +0( #75 -1# -b00000000000000000000000000001000 $ +1( #80 -0# +0( #85 -1# -b00000000000000000000000000001001 $ +1( #90 -0# +0( #95 +1( +#100 +0( +#105 +1( +#110 1# -b00000000000000000000000000001010 $ +0% +0( diff --git a/test_regress/t/t_timing_trace.pl b/test_regress/t/t_timing_trace.pl index f4d0cd88e..6a98bee9d 100755 --- a/test_regress/t/t_timing_trace.pl +++ b/test_regress/t/t_timing_trace.pl @@ -14,10 +14,8 @@ if (!$Self->have_coroutines) { skip("No coroutine support"); } else { - top_filename("t/t_timing_clkgen1.v"); - compile( - verilator_flags2 => ["--exe --main --timing --trace -Wno-MINTYPMAXDLY -DTEST_TRACING"], + verilator_flags2 => ["--exe --main --timing --trace -Wno-MINTYPMAXDLY"], make_main => 0, ); diff --git a/test_regress/t/t_timing_trace.v b/test_regress/t/t_timing_trace.v new file mode 100644 index 000000000..25782e267 --- /dev/null +++ b/test_regress/t/t_timing_trace.v @@ -0,0 +1,43 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`define STRINGIFY(x) `"x`" + +module t; + localparam CLK_PERIOD = 10; + localparam CLK_HALF_PERIOD = CLK_PERIOD / 2; + + logic rst; + logic clk; + logic a; + logic b; + logic c; + logic d; + + initial begin + $dumpfile({`STRINGIFY(`TEST_OBJ_DIR),"/simx.vcd"}); + $dumpvars; + forever clk = #CLK_HALF_PERIOD ~clk; + end + + always begin + rst = 1; + clk = 0; + a = 0; + c = 0; + b = 0; + d = 0; + + #CLK_PERIOD; + rst = 0; + b = 1; + + #(10 * CLK_PERIOD); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 0e4da3b0bf9394a61fd8e3eb209d619ae62385cd Mon Sep 17 00:00:00 2001 From: Arkadiusz Kozdra Date: Thu, 20 Oct 2022 12:31:00 +0200 Subject: [PATCH 135/177] Support virtual interfaces (#3654) --- src/V3AstNodeDType.h | 9 +++ src/V3AstNodeOther.h | 4 ++ src/V3AstNodes.cpp | 2 + src/V3Common.cpp | 15 ++++ src/V3Const.cpp | 2 +- src/V3Dead.cpp | 15 +++- src/V3EmitCFunc.cpp | 2 + src/V3EmitCFunc.h | 3 + src/V3Life.cpp | 4 +- src/V3LinkDot.cpp | 26 ++++--- src/V3Localize.cpp | 1 + src/V3Scope.cpp | 26 ++++--- src/V3Width.cpp | 65 ++++++++++++++++- src/verilog.y | 30 ++++---- test_regress/t/t_class_unsup_bad.out | 7 -- test_regress/t/t_interface_virtual.out | 7 ++ test_regress/t/t_interface_virtual.pl | 22 ++++++ test_regress/t/t_interface_virtual.v | 72 +++++++++++++++++++ test_regress/t/t_interface_virtual_bad.out | 34 +++++++++ test_regress/t/t_interface_virtual_bad.pl | 19 +++++ test_regress/t/t_interface_virtual_bad.v | 49 +++++++++++++ test_regress/t/t_interface_virtual_inl.pl | 27 +++++++ .../t/t_interface_virtual_unused_bad.out | 5 ++ .../t/t_interface_virtual_unused_bad.pl | 19 +++++ .../t/t_interface_virtual_unused_bad.v | 20 ++++++ 25 files changed, 440 insertions(+), 45 deletions(-) create mode 100644 test_regress/t/t_interface_virtual.out create mode 100755 test_regress/t/t_interface_virtual.pl create mode 100644 test_regress/t/t_interface_virtual.v create mode 100644 test_regress/t/t_interface_virtual_bad.out create mode 100755 test_regress/t/t_interface_virtual_bad.pl create mode 100644 test_regress/t/t_interface_virtual_bad.v create mode 100755 test_regress/t/t_interface_virtual_inl.pl create mode 100644 test_regress/t/t_interface_virtual_unused_bad.out create mode 100755 test_regress/t/t_interface_virtual_unused_bad.pl create mode 100644 test_regress/t/t_interface_virtual_unused_bad.v diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 736eefd8a..c98b10843 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -768,6 +768,7 @@ public: }; class AstIfaceRefDType final : public AstNodeDType { // Reference to an interface, either for a port, or inside parent cell + // @astgen op1 := paramsp : List[AstPin] private: FileLine* m_modportFileline; // Where modport token was string m_cellName; // "" = no cell, such as when connects to 'input' iface @@ -790,6 +791,14 @@ public: , m_cellName{cellName} , m_ifaceName{ifaceName} , m_modportName{modport} {} + AstIfaceRefDType(FileLine* fl, FileLine* modportFl, const string& cellName, + const string& ifaceName, const string& modport, AstPin* paramsp) + : ASTGEN_SUPER_IfaceRefDType(fl) + , m_modportFileline{modportFl} + , m_cellName{cellName} + , m_ifaceName{ifaceName} { + addParamsp(paramsp); + } ASTGEN_MEMBERS_AstIfaceRefDType; // METHODS const char* broken() const override; diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index ec4612587..5d3746ebe 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2002,6 +2002,7 @@ class AstVar final : public AstNode { bool m_usedClock : 1; // Signal used as a clock bool m_usedParam : 1; // Parameter is referenced (on link; later signals not setup) bool m_usedLoopIdx : 1; // Variable subject of for unrolling + bool m_usedVirtIface : 1; // Signal used through a virtual interface bool m_funcLocal : 1; // Local variable for a function bool m_funcReturn : 1; // Return variable for a function bool m_attrScBv : 1; // User force bit vector attribute @@ -2040,6 +2041,7 @@ class AstVar final : public AstNode { m_usedClock = false; m_usedParam = false; m_usedLoopIdx = false; + m_usedVirtIface = false; m_sigPublic = false; m_sigModPublic = false; m_sigUserRdPublic = false; @@ -2188,6 +2190,7 @@ public: void usedClock(bool flag) { m_usedClock = flag; } void usedParam(bool flag) { m_usedParam = flag; } void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; } + void usedVirtIface(bool flag) { m_usedVirtIface = flag; } void sigPublic(bool flag) { m_sigPublic = flag; } void sigModPublic(bool flag) { m_sigModPublic = flag; } void sigUserRdPublic(bool flag) { @@ -2271,6 +2274,7 @@ public: bool isUsedClock() const { return m_usedClock; } bool isUsedParam() const { return m_usedParam; } bool isUsedLoopIdx() const { return m_usedLoopIdx; } + bool isUsedVirtIface() const { return m_usedVirtIface; } bool isSc() const VL_MT_SAFE { return m_sc; } bool isScQuad() const; bool isScBv() const; diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 10861568f..2a3a4d975 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -724,6 +724,8 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const { info.m_type += ">"; } else if (const auto* const adtypep = VN_CAST(dtypep, ClassRefDType)) { info.m_type = "VlClassRef<" + EmitCBaseVisitor::prefixNameProtect(adtypep) + ">"; + } else if (const auto* const adtypep = VN_CAST(dtypep, IfaceRefDType)) { + info.m_type = EmitCBaseVisitor::prefixNameProtect(adtypep->ifaceViaCellp()) + "*"; } else if (const auto* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { if (adtypep->isCompound()) compound = true; const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(compound); diff --git a/src/V3Common.cpp b/src/V3Common.cpp index 9961585b9..907c5a33d 100644 --- a/src/V3Common.cpp +++ b/src/V3Common.cpp @@ -47,6 +47,19 @@ static void makeVlToString(AstClass* nodep) { funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp}); nodep->addStmtsp(funcp); } +static void makeVlToString(AstIface* nodep) { + AstCFunc* const funcp + = new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"}; + funcp->argTypes("const " + EmitCBaseVisitor::prefixNameProtect(nodep) + "* obj"); + funcp->isMethod(false); + funcp->isConst(false); + funcp->isStatic(false); + funcp->protect(false); + AstNode* const exprp = new AstCMath{nodep->fileline(), "obj ? obj->name() : \"null\"", 0}; + exprp->dtypeSetString(); + funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp}); + nodep->addStmtsp(funcp); +} static void makeToString(AstClass* nodep) { AstCFunc* const funcp = new AstCFunc{nodep->fileline(), "to_string", nullptr, "std::string"}; funcp->isConst(true); @@ -117,6 +130,8 @@ void V3Common::commonAll() { makeVlToString(classp); makeToString(classp); makeToStringMiddle(classp); + } else if (AstIface* const ifacep = VN_CAST(nodep, Iface)) { + makeVlToString(ifacep); } } V3Global::dumpCheckGlobalTree("common", 0, dumpTree() >= 3); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index cf90d2156..b499a1ed4 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2606,7 +2606,7 @@ private: && m_doNConst && v3Global.opt.fConst() // Default value, not a "known" constant for this usage - && !nodep->varp()->isClassMember() + && !nodep->varp()->isClassMember() && !nodep->varp()->isUsedVirtIface() && !(nodep->varp()->isFuncLocal() && nodep->varp()->isNonOutput()) && !nodep->varp()->noSubst() && !nodep->varp()->isSigPublic()) || nodep->varp()->isParam())) { diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index 3c396f080..d2ce5734c 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -198,6 +198,19 @@ private: } if (nodep->classp()) nodep->classp()->user1Inc(); } + void visit(AstIfaceRefDType* nodep) override { + iterateChildren(nodep); + checkDType(nodep); + checkAll(nodep); + if (nodep->modportp()) { + if (m_elimCells) { + nodep->modportp(nullptr); + } else { + nodep->modportp()->user1Inc(); + } + } + if (nodep->ifaceViaCellp()) nodep->ifaceViaCellp()->user1Inc(); + } void visit(AstNodeDType* nodep) override { iterateChildren(nodep); checkDType(nodep); @@ -321,7 +334,7 @@ private: } bool mightElimVar(AstVar* nodep) const { if (nodep->isSigPublic()) return false; // Can't elim publics! - if (nodep->isIO() || nodep->isClassMember()) return false; + if (nodep->isIO() || nodep->isClassMember() || nodep->isUsedVirtIface()) return false; if (nodep->isTemp() && !nodep->isTrace()) return true; return m_elimUserVars; // Post-Trace can kill most anything } diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index e1b35f8ae..7ec97f234 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -663,6 +663,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP suffix + ".atDefault()" + cvtarray); } else if (VN_IS(dtypep, ClassRefDType)) { return ""; // Constructor does it + } else if (VN_IS(dtypep, IfaceRefDType)) { + return varNameProtected + suffix + " = nullptr;\n"; } else if (const AstDynArrayDType* const adtypep = VN_CAST(dtypep, DynArrayDType)) { // Access std::array as C array const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 166dd60de..c91ef4174 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -1145,6 +1145,9 @@ public: } else if (VN_IS(varModp, Class) && varModp != m_modp) { // Superclass member reference puts(prefixNameProtect(varModp) + "::"); + } else if (varp->isIfaceRef()) { + puts(nodep->selfPointerProtect(m_useSelfForThis)); + return; } else if (!nodep->selfPointer().empty()) { emitDereference(nodep->selfPointerProtect(m_useSelfForThis)); } diff --git a/src/V3Life.cpp b/src/V3Life.cpp index 41d3ab982..1c4bea7df 100644 --- a/src/V3Life.cpp +++ b/src/V3Life.cpp @@ -141,7 +141,7 @@ public: void checkRemoveAssign(const LifeMap::iterator& it) { const AstVar* const varp = it->first->varp(); LifeVarEntry* const entp = &(it->second); - if (!varp->isSigPublic()) { + if (!varp->isSigPublic() && !varp->isUsedVirtIface()) { // Rather than track what sigs AstUCFunc/AstUCStmt may change, // we just don't optimize any public sigs // Check the var entry, and remove if appropriate @@ -186,7 +186,7 @@ public: const auto it = m_map.find(nodep); if (it != m_map.end()) { if (AstConst* const constp = it->second.constNodep()) { - if (!varrefp->varp()->isSigPublic()) { + if (!varrefp->varp()->isSigPublic() && !varrefp->varp()->isUsedVirtIface()) { // Aha, variable is constant; substitute in. // We'll later constant propagate UINFO(4, " replaceconst: " << varrefp << endl); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 6c3885ff1..3879c918a 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -459,7 +459,7 @@ public: } void computeIfaceVarSyms() { for (VSymEnt* varSymp : m_ifaceVarSyms) { - const AstVar* const varp = varSymp ? VN_AS(varSymp->nodep(), Var) : nullptr; + AstVar* const varp = varSymp ? VN_AS(varSymp->nodep(), Var) : nullptr; UINFO(9, " insAllIface se" << cvtToHex(varSymp) << " " << varp << endl); AstIfaceRefDType* const ifacerefp = ifaceRefFromArray(varp->subDTypep()); UASSERT_OBJ(ifacerefp, varp, "Non-ifacerefs on list!"); @@ -475,8 +475,16 @@ public: ifacerefp->v3fatalSrc("Unlinked interface"); } } else if (ifacerefp->ifaceViaCellp()->dead()) { - ifacerefp->v3error("Parent instance's interface is not found: " - << AstNode::prettyNameQ(ifacerefp->ifaceName())); + if (varp->isIfaceRef()) { + ifacerefp->v3error("Parent instance's interface is not found: " + << AstNode::prettyNameQ(ifacerefp->ifaceName())); + } else { + ifacerefp->v3warn( + E_UNSUPPORTED, + "Unsupported: virtual interface never assigned any actual interface"); + varp->dtypep(ifacerefp->findCHandleDType()); + VL_DO_DANGLING(ifacerefp->unlinkFrBack()->deleteTree(), ifacerefp); + } continue; } VSymEnt* const ifaceSymp = getNodeSym(ifacerefp->ifaceViaCellp()); @@ -2420,7 +2428,7 @@ private: m_ds.m_dotSymp = foundp; m_ds.m_dotPos = DP_SCOPE; // Upper AstDot visitor will handle it from here - } else if (VN_IS(foundp->nodep(), Cell) && allowVar && m_cellp) { + } else if (VN_IS(foundp->nodep(), Cell) && allowVar) { AstCell* const cellp = VN_AS(foundp->nodep(), Cell); if (VN_IS(cellp->modp(), Iface)) { // Interfaces can be referenced like a variable for interconnect @@ -2438,7 +2446,7 @@ private: m_ds.m_dotPos = DP_SCOPE; UINFO(9, " cell -> iface varref " << foundp->nodep() << endl); AstNode* const newp - = new AstVarRef(ifaceRefVarp->fileline(), ifaceRefVarp, VAccess::READ); + = new AstVarRef{nodep->fileline(), ifaceRefVarp, VAccess::READ}; nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } else if (VN_IS(cellp->modp(), NotFoundModule)) { @@ -2449,7 +2457,7 @@ private: } else if (AstVar* const varp = foundToVarp(foundp, nodep, VAccess::READ)) { AstIfaceRefDType* const ifacerefp = LinkDotState::ifaceRefFromArray(varp->subDTypep()); - if (ifacerefp) { + if (ifacerefp && varp->isIfaceRef()) { UASSERT_OBJ(ifacerefp->ifaceViaCellp(), ifacerefp, "Unlinked interface"); // Really this is a scope reference into an interface UINFO(9, "varref-ifaceref " << m_ds.m_dotText << " " << nodep << endl); @@ -2538,9 +2546,9 @@ private: m_ds.m_dotText = VString::dot(m_ds.m_dotText, ".", nodep->name()); m_ds.m_dotSymp = foundp; m_ds.m_dotPos = DP_SCOPE; - UINFO(9, " cell -> iface varref " << foundp->nodep() << endl); - AstNode* newp - = new AstVarRef(ifaceRefVarp->fileline(), ifaceRefVarp, VAccess::READ); + UINFO(9, " modport -> iface varref " << foundp->nodep() << endl); + // We lose the modport name here, so we cannot detect mismatched modports. + AstNode* newp = new AstVarRef{nodep->fileline(), ifaceRefVarp, VAccess::READ}; auto* const cellarrayrefp = VN_CAST(m_ds.m_unlinkedScopep, CellArrayRef); if (cellarrayrefp) { // iface[vec].modport became CellArrayRef(iface, lsb) diff --git a/src/V3Localize.cpp b/src/V3Localize.cpp index 0d5977dcc..1de5d06e3 100644 --- a/src/V3Localize.cpp +++ b/src/V3Localize.cpp @@ -169,6 +169,7 @@ private: && !nodep->varp()->isFuncLocal() // Not already a function local (e.g.: argument) && !nodep->varp()->isStatic() // Not a static variable && !nodep->varp()->isClassMember() // Statically exists in design hierarchy + && !nodep->varp()->isUsedVirtIface() // Not used through a virtual interface && !nodep->varp()->valuep() // Does not have an initializer ) { UINFO(4, "Consider for localization: " << nodep << endl); diff --git a/src/V3Scope.cpp b/src/V3Scope.cpp index c0694e2ab..ce60b19de 100644 --- a/src/V3Scope.cpp +++ b/src/V3Scope.cpp @@ -43,6 +43,7 @@ class ScopeVisitor final : public VNVisitor { private: // NODE STATE // AstVar::user1p -> AstVarScope replacement for this variable + // AstCell::user2p -> AstScope*. The scope created inside the cell // AstTask::user2p -> AstTask*. Replacement task const VNUser1InUse m_inuser1; const VNUser2InUse m_inuser2; @@ -125,6 +126,10 @@ private: AstNodeModule* const modp = cellp->modp(); UASSERT_OBJ(modp, cellp, "Unlinked mod"); iterate(modp); // Recursive call to visit(AstNodeModule) + if (VN_IS(modp, Iface)) { + // Remember newly created scope + cellp->user2p(m_scopep); + } } } } @@ -261,7 +266,12 @@ private: void visit(AstVar* nodep) override { // Make new scope variable if (!nodep->user1p()) { - AstVarScope* const varscp = new AstVarScope(nodep->fileline(), m_scopep, nodep); + AstScope* scopep = m_scopep; + if (AstIfaceRefDType* const ifacerefp = VN_CAST(nodep->dtypep(), IfaceRefDType)) { + // Attach every non-virtual interface variable its inner scope + if (ifacerefp->cellp()) scopep = VN_AS(ifacerefp->cellp()->user2p(), Scope); + } + AstVarScope* const varscp = new AstVarScope{nodep->fileline(), scopep, nodep}; UINFO(6, " New scope " << varscp << endl); if (m_aboveCellp && !m_aboveCellp->isTrace()) varscp->trace(false); nodep->user1p(varscp); @@ -280,15 +290,11 @@ private: // VarRef needs to point to VarScope // Make sure variable has made user1p. UASSERT_OBJ(nodep->varp(), nodep, "Unlinked"); - if (nodep->varp()->isIfaceRef()) { - nodep->varScopep(nullptr); - } else { - // We may have not made the variable yet, and we can't make it now as - // the var's referenced package etc might not be created yet. - // So push to a list and post-correct. - // No check here for nodep->classOrPackagep(), will check when walk list. - m_varRefScopes.emplace(nodep, m_scopep); - } + // We may have not made the variable yet, and we can't make it now as + // the var's referenced package etc might not be created yet. + // So push to a list and post-correct. + // No check here for nodep->classOrPackagep(), will check when walk list. + m_varRefScopes.emplace(nodep, m_scopep); } void visit(AstScopeName* nodep) override { // If there's a %m in the display text, we add a special node that will contain the name() diff --git a/src/V3Width.cpp b/src/V3Width.cpp index a2ec63a30..23319a0ef 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2527,6 +2527,20 @@ private: << foundp->warnOther() << "... Location of found object\n" << foundp->warnContextSecondary()); } + } else if (AstIfaceRefDType* const adtypep = VN_CAST(fromDtp, IfaceRefDType)) { + if (AstNode* const foundp = memberSelIface(nodep, adtypep)) { + if (AstVar* const varp = VN_CAST(foundp, Var)) { + nodep->dtypep(foundp->dtypep()); + nodep->varp(varp); + varp->usedVirtIface(true); + return; + } + UINFO(1, "found object " << foundp << endl); + nodep->v3fatalSrc("MemberSel of non-variable\n" + << nodep->warnContextPrimary() << '\n' + << foundp->warnOther() << "... Location of found object\n" + << foundp->warnContextSecondary()); + } } else if (VN_IS(fromDtp, EnumDType) // || VN_IS(fromDtp, AssocArrayDType) // || VN_IS(fromDtp, WildcardArrayDType) // @@ -2576,6 +2590,26 @@ private: << (suggest.empty() ? "" : nodep->fileline()->warnMore() + suggest)); return nullptr; // Caller handles error } + AstNode* memberSelIface(AstMemberSel* nodep, AstIfaceRefDType* adtypep) { + // Returns node if ok + // No need to width-resolve the interface, as it was done when we did the child + AstNodeModule* const ifacep = adtypep->ifacep(); + UASSERT_OBJ(ifacep, nodep, "Unlinked"); + // if (AstNode* const foundp = ifacep->findMember(nodep->name())) return foundp; + VSpellCheck speller; + for (AstNode* itemp = ifacep->stmtsp(); itemp; itemp = itemp->nextp()) { + if (itemp->name() == nodep->name()) return itemp; + if (VN_IS(itemp, Var) || VN_IS(itemp, Modport)) { + speller.pushCandidate(itemp->prettyName()); + } + } + const string suggest = speller.bestCandidateMsg(nodep->prettyName()); + nodep->v3error( + "Member " << nodep->prettyNameQ() << " not found in interface " + << ifacep->prettyNameQ() << "\n" + << (suggest.empty() ? "" : nodep->fileline()->warnMore() + suggest)); + return nullptr; // Caller handles error + } bool memberSelStruct(AstMemberSel* nodep, AstNodeUOrStructDType* adtypep) { // Returns true if ok if (AstMemberDType* const memberp = adtypep->findMember(nodep->name())) { @@ -6026,11 +6060,38 @@ private: // Note the check uses the expected size, not the child's subDTypep as we want the // child node's width to end up correct for the assignment (etc) widthCheckSized(nodep, side, underp, expDTypep, extendRule, warnOn); - } else if (!VN_IS(expDTypep, IfaceRefDType) - && VN_IS(underp->dtypep(), IfaceRefDType)) { + } else if (!VN_IS(expDTypep->skipRefp(), IfaceRefDType) + && VN_IS(underp->dtypep()->skipRefp(), IfaceRefDType)) { underp->v3error(ucfirst(nodep->prettyOperatorName()) << " expected non-interface on " << side << " but '" << underp->name() << "' is an interface."); + } else if (const AstIfaceRefDType* expIfaceRefp + = VN_CAST(expDTypep->skipRefp(), IfaceRefDType)) { + const AstIfaceRefDType* underIfaceRefp + = VN_CAST(underp->dtypep()->skipRefp(), IfaceRefDType); + if (!underIfaceRefp) { + underp->v3error(ucfirst(nodep->prettyOperatorName()) + << " expected " << expIfaceRefp->ifaceViaCellp()->prettyNameQ() + << " interface on " << side << " but " << underp->prettyNameQ() + << " is not an interface."); + } else if (expIfaceRefp->ifaceViaCellp() != underIfaceRefp->ifaceViaCellp()) { + underp->v3error(ucfirst(nodep->prettyOperatorName()) + << " expected " << expIfaceRefp->ifaceViaCellp()->prettyNameQ() + << " interface on " << side << " but '" << underp->name() + << "' is a different interface (" + << underIfaceRefp->ifaceViaCellp()->prettyNameQ() << ")."); + } else if (underIfaceRefp->modportp() + && expIfaceRefp->modportp() != underIfaceRefp->modportp()) { + underp->v3error(ucfirst(nodep->prettyOperatorName()) + << " expected " + << (expIfaceRefp->modportp() + ? expIfaceRefp->modportp()->prettyNameQ() + : "no") + << " interface modport on " << side << " but got " + << underIfaceRefp->modportp()->prettyNameQ() << " modport."); + } else { + underp = userIterateSubtreeReturnEdits(underp, WidthVP{expDTypep, FINAL}.p()); + } } else { // Hope it just works out (perhaps a cast will deal with it) underp = userIterateSubtreeReturnEdits(underp, WidthVP(expDTypep, FINAL).p()); diff --git a/src/verilog.y b/src/verilog.y index f366c5b61..256c530db 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -1402,9 +1402,9 @@ port: // ==IEEE: port VARDTYPE(new AstIfaceRefDType($2, $4, "", *$2, *$4)); addNextNull($$, VARDONEP($$,$6,$7)); } | portDirNetE yINTERFACE portSig rangeListE sigAttrListE - { $$ = nullptr; BBUNSUP($2, "Unsupported: virtual or generic interfaces"); } + { $$ = nullptr; BBUNSUP($2, "Unsupported: generic interfaces"); } | portDirNetE yINTERFACE '.' idAny/*modport*/ portSig rangeListE sigAttrListE - { $$ = nullptr; BBUNSUP($2, "Unsupported: virtual or generic interfaces"); } + { $$ = nullptr; BBUNSUP($2, "Unsupported: generic interfaces"); } // // // IEEE: ansi_port_declaration, with [port_direction] removed // // IEEE: [ net_port_header | interface_port_header ] @@ -1981,21 +1981,25 @@ data_typeNoRef: // ==IEEE: data_type, excluding class_ty { $$ = new AstBasicDType{$1, VBasicDTypeKwd::CHANDLE}; } | yEVENT { $$ = new AstBasicDType{$1, VBasicDTypeKwd::EVENT}; v3Global.setHasEvents(); } - // // Rules overlap virtual_interface_declaration - // // Parameters here are SV2009 - // // IEEE has ['.' modport] but that will conflict with port - // // declarations which decode '.' modport themselves, so - // // instead see data_typeVar - | yVIRTUAL__INTERFACE yINTERFACE id/*interface*/ - { $$ = new AstBasicDType{$1, VBasicDTypeKwd::CHANDLE}; - BBUNSUP($1, "Unsupported: virtual interface"); } - | yVIRTUAL__anyID id/*interface*/ - { $$ = new AstBasicDType{$1, VBasicDTypeKwd::CHANDLE}; - BBUNSUP($1, "Unsupported: virtual data type"); } | type_reference { $$ = $1; } // // IEEE: class_scope: see data_type above // // IEEE: class_type: see data_type above // // IEEE: ps_covergroup: see data_type above + // // Rules overlap virtual_interface_declaration + | yVIRTUAL__INTERFACE yINTERFACE data_typeVirtual + { $$ = $3; } + | yVIRTUAL__anyID data_typeVirtual + { $$ = $2; } + ; + +data_typeVirtual: // ==IEEE: data_type after yVIRTUAL [ yINTERFACE ] + // // Parameters here are SV2009 + id/*interface*/ { $$ = new AstIfaceRefDType{$1, "", *$1}; } + | id/*interface*/ '.' id/*modport*/ { $$ = new AstIfaceRefDType{$1, $3, "", *$1, *$3}; } + | id/*interface*/ parameter_value_assignmentClass + { $$ = new AstIfaceRefDType{$1, nullptr, "", *$1, "", $2}; } + | id/*interface*/ parameter_value_assignmentClass '.' id/*modport*/ + { $$ = new AstIfaceRefDType{$1, $4, "", *$1, *$4, $2}; } ; data_type_or_void: // ==IEEE: data_type_or_void diff --git a/test_regress/t/t_class_unsup_bad.out b/test_regress/t/t_class_unsup_bad.out index eba10d537..4e3bc999f 100644 --- a/test_regress/t/t_class_unsup_bad.out +++ b/test_regress/t/t_class_unsup_bad.out @@ -1,10 +1,3 @@ -%Error-UNSUPPORTED: t/t_class_unsup_bad.v:7:1: Unsupported: virtual interface - 7 | virtual interface vi_t vi; - | ^~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_class_unsup_bad.v:8:1: Unsupported: virtual data type - 8 | virtual vi_t vi2; - | ^~~~~~~ %Error: t/t_class_unsup_bad.v:29:24: Syntax error: 'const'/'rand'/'randc' not allowed before function/task declaration 29 | const function void func_const; endfunction | ^~~~~~~~~~ diff --git a/test_regress/t/t_interface_virtual.out b/test_regress/t/t_interface_virtual.out new file mode 100644 index 000000000..2e63964b4 --- /dev/null +++ b/test_regress/t/t_interface_virtual.out @@ -0,0 +1,7 @@ +va.addr=aa va.data=11 ia.addr=aa ia.data=11 +vb.addr=bb vb.data=22 ib.addr=bb ib.data=22 +ca.fa.addr=a0 ca.fa.data=11 ca.fa.addr=b0 ca.fb.data=22 +cb.fa.addr=b0 cb.fa.data=22 cb.fa.addr=a0 cb.fb.data=11 +gen.x[0].addr=a0 gen.x[1].addr=b0 +gen='{x:'{top.t.ia, top.t.ib, null, null} } +*-* All Finished *-* diff --git a/test_regress/t/t_interface_virtual.pl b/test_regress/t/t_interface_virtual.pl new file mode 100755 index 000000000..6247bd126 --- /dev/null +++ b/test_regress/t/t_interface_virtual.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 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 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual.v b/test_regress/t/t_interface_virtual.v new file mode 100644 index 000000000..878df0995 --- /dev/null +++ b/test_regress/t/t_interface_virtual.v @@ -0,0 +1,72 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Arkadiusz Kozdra. +// SPDX-License-Identifier: CC0-1.0 + +// See also t_interface_virtual_bad.v + +interface PBus; + logic req, grant; + logic [7:0] addr, data; + modport phy(input addr, ref data); +endinterface + +typedef virtual PBus vpbus_t; +typedef vpbus_t vpbus2_t; + +class Cls; + vpbus2_t fa, fb; +endclass + +class Clsgen#(type T = logic); + T x[0:3]; +endclass + +module t (/*AUTOARG*/); + + PBus ia, ib; + virtual PBus va, vb; + virtual PBus.phy pa, pb; + Cls ca, cb; + Clsgen#(virtual PBus) gen; + + initial begin + va = ia; + vb = ib; + + ca = new; + cb = new; + gen = new; + + va.addr = 8'haa; + ia.data = 8'h11; + + vb.addr = 8'hbb; + ib.data = 8'h22; + + $display("va.addr=%x", va.addr, " va.data=%x", va.data, " ia.addr=%x", ia.addr, " ia.data=%x", ia.data); + $display("vb.addr=%x", vb.addr, " vb.data=%x", vb.data, " ib.addr=%x", ib.addr, " ib.data=%x", ib.data); + + ca.fa = ia; + ca.fb = ib; + cb.fa = ib; + cb.fb = ia; + gen.x[0] = va; + gen.x[1] = vb; + + pa = va; + pb = vb; + + pb.addr = 8'hb0; + pa.addr = 8'ha0; + + $display("ca.fa.addr=%x", ca.fa.addr, " ca.fa.data=%x", ca.fa.data, " ca.fa.addr=%x", ca.fb.addr, " ca.fb.data=%x", ca.fb.data); + $display("cb.fa.addr=%x", cb.fa.addr, " cb.fa.data=%x", cb.fa.data, " cb.fa.addr=%x", cb.fb.addr, " cb.fb.data=%x", cb.fb.data); + $display("gen.x[0].addr=%x", gen.x[0].addr, " gen.x[1].addr=%x", gen.x[1].addr); + $display("gen=%p", gen); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_virtual_bad.out b/test_regress/t/t_interface_virtual_bad.out new file mode 100644 index 000000000..6afbdc125 --- /dev/null +++ b/test_regress/t/t_interface_virtual_bad.out @@ -0,0 +1,34 @@ +%Error: t/t_interface_virtual_bad.v:31:12: Operator ASSIGN expected 'PBus' interface on Assign RHS but 'q8__Viftop' is a different interface ('QBus'). + : ... In instance t + 31 | v8 = q8; + | ^~ +%Error: t/t_interface_virtual_bad.v:35:12: Operator ASSIGN expected no interface modport on Assign RHS but got 'phy' modport. + : ... In instance t + 35 | v8 = v8_phy; + | ^~~~~~ +%Error: t/t_interface_virtual_bad.v:37:17: Operator ASSIGN expected non-interface on Assign RHS but 'p8__Viftop' is an interface. + : ... In instance t + 37 | data = p8.phy; + | ^~~ +%Error: t/t_interface_virtual_bad.v:38:14: Operator ASSIGN expected non-interface on Assign RHS but 'v8_phy' is an interface. + : ... In instance t + 38 | data = v8_phy; + | ^~~~~~ +%Error: t/t_interface_virtual_bad.v:39:14: Operator ASSIGN expected non-interface on Assign RHS but 'v8' is an interface. + : ... In instance t + 39 | data = v8; + | ^~ +%Error: t/t_interface_virtual_bad.v:40:14: Operator ASSIGN expected non-interface on Assign RHS but 'p8__Viftop' is an interface. + : ... In instance t + 40 | data = p8; + | ^~ +%Error: t/t_interface_virtual_bad.v:41:12: Operator ASSIGN expected 'PBus' interface on Assign RHS but 'data' is not an interface. + : ... In instance t + 41 | v8 = data; + | ^~~~ +%Error: t/t_interface_virtual_bad.v:44:79: Member 'gran' not found in interface 'PBus' + : ... In instance t + : ... Suggested alternative: 'grant' + 44 | $display("q8.grant=", p8.grant, " v8.grant=", v8.grant, v8_phy.addr, v8.gran); + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_interface_virtual_bad.pl b/test_regress/t/t_interface_virtual_bad.pl new file mode 100755 index 000000000..7be596e0f --- /dev/null +++ b/test_regress/t/t_interface_virtual_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 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 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual_bad.v b/test_regress/t/t_interface_virtual_bad.v new file mode 100644 index 000000000..3c4f690da --- /dev/null +++ b/test_regress/t/t_interface_virtual_bad.v @@ -0,0 +1,49 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Arkadiusz Kozdra. +// SPDX-License-Identifier: CC0-1.0 + +// See also t_interface_virtual.v + +interface PBus; + logic req, grant; + logic [7:0] addr, data; + modport phy(input addr, ref data); +endinterface + +interface QBus; +endinterface + +typedef virtual PBus vpbus_t; + +module t (/*AUTOARG*/); + + PBus p8; + QBus q8; + vpbus_t v8; + virtual PBus.phy v8_phy; + logic data; + + initial begin + v8 = p8; + p8 = v8; // error + v8 = q8; // error + v8_phy = p8; + v8_phy = v8; + v8_phy = p8.phy; + v8 = v8_phy; // error + v8 = p8.phy; // error + data = p8.phy; // error + data = v8_phy; // error + data = v8; // error + data = p8; // error + v8 = data; // error + v8.grant = 1'b1; + + $display("q8.grant=", p8.grant, " v8.grant=", v8.grant, v8_phy.addr, v8.gran); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_virtual_inl.pl b/test_regress/t/t_interface_virtual_inl.pl new file mode 100755 index 000000000..b342b7b3a --- /dev/null +++ b/test_regress/t/t_interface_virtual_inl.pl @@ -0,0 +1,27 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003-2009 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 + +scenarios(simulator => 1); + +top_filename("t/t_interface_virtual.v"); +golden_filename("t/t_interface_virtual.out"); + +compile( + # Avoid inlining so we find bugs in the non-inliner connection code + verilator_flags2 => ["-fno-inline"], + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual_unused_bad.out b/test_regress/t/t_interface_virtual_unused_bad.out new file mode 100644 index 000000000..5775cfc79 --- /dev/null +++ b/test_regress/t/t_interface_virtual_unused_bad.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_interface_virtual_unused_bad.v:14:12: Unsupported: virtual interface never assigned any actual interface + 14 | virtual QBus q8; + | ^~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_interface_virtual_unused_bad.pl b/test_regress/t/t_interface_virtual_unused_bad.pl new file mode 100755 index 000000000..7be596e0f --- /dev/null +++ b/test_regress/t/t_interface_virtual_unused_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 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 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual_unused_bad.v b/test_regress/t/t_interface_virtual_unused_bad.v new file mode 100644 index 000000000..c0c7d185f --- /dev/null +++ b/test_regress/t/t_interface_virtual_unused_bad.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Arkadiusz Kozdra. +// SPDX-License-Identifier: CC0-1.0 + +// See also t_interface_virtual.v + +interface QBus; +endinterface + +module t (/*AUTOARG*/); + + virtual QBus q8; + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 8c3ca30c91540ccd6a65975b3dc6109f74536e55 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Thu, 20 Oct 2022 16:15:29 +0200 Subject: [PATCH 136/177] Fix print format warning on some platforms (#3699) --- include/verilated_timing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/verilated_timing.cpp b/include/verilated_timing.cpp index baef60230..7f996d7f7 100644 --- a/include/verilated_timing.cpp +++ b/include/verilated_timing.cpp @@ -46,7 +46,7 @@ void VlCoroutineHandle::dump() const { #ifdef VL_DEBUG void VlDelayScheduler::VlDelayedCoroutine::dump() const { - VL_DBG_MSGF(" Awaiting time %lu: ", m_timestep); + VL_DBG_MSGF(" Awaiting time %" PRIu64 ": ", m_timestep); m_handle.dump(); } #endif From 6a3ec17887130f61b1ec3aaf38b786e02f6f1750 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 20 Oct 2022 14:55:17 +0100 Subject: [PATCH 137/177] DFG: Do not inline SystemC variables The emitted SystemC types (e.g. sc_bv) are not interchangeable with Verilator internal C++ types (e.g.: VlWide), so the variables themselves are not interchangeable (but can be assigned to/from each other). We can preserve correctness simply be not inlining any SystemC variables (i.e.: don't simplify any 'sc = nonSc' or 'nonSc = sc' assignments). SystemC types only appear at top level ports so this should have no significant impact. Fixes #3688 --- src/V3DfgPasses.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index e4d75371d..2abc21986 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -134,9 +134,23 @@ void V3DfgPasses::inlineVars(DfgGraph& dfg) { for (DfgVertexVar *vtxp = dfg.varVerticesBeginp(), *nextp; vtxp; vtxp = nextp) { nextp = vtxp->verticesNext(); if (DfgVarPacked* const varp = vtxp->cast()) { - if (varp->hasSinks() && varp->isDrivenFullyByDfg()) { + // Don't inline SystemC variables, as SystemC types are not interchangeable with + // internal types, and hence the variables are not interchangeable either. + if (varp->hasSinks() && varp->isDrivenFullyByDfg() && !varp->varp()->isSc()) { DfgVertex* const driverp = varp->source(0); - varp->forEachSinkEdge([=](DfgEdge& edge) { edge.relinkSource(driverp); }); + + // If driven from a SystemC variable, don't inline this variable + if (DfgVertexVar* const driverVarp = driverp->cast()) { + if (driverVarp->varp()->isSc()) continue; + } + + varp->forEachSinkEdge([=](DfgEdge& edge) { + // If sink is a SystemC variable, don't inline that sink + if (DfgVertexVar* const sinkVarp = edge.sinkp()->cast()) { + if (sinkVarp->varp()->isSc()) return; + } + edge.relinkSource(driverp); + }); } } } From 7b07cf912e52b1425e8bd5fc2805ff48007d6e52 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 20 Oct 2022 15:32:10 +0100 Subject: [PATCH 138/177] Delete trigger dump when --protect-ids is used In order to not leak signal names with --protect-ids, we simply make the trigger dump function empty (this is a debug only construct). Partial fix for #3689 --- src/V3Sched.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index b34133c77..c81c27869 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -507,6 +507,9 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui add("#endif\n"); } + // The debug code might leak signal names, so simply delete it when using --protect-ids + if (v3Global.opt.protectIds()) dumpp->stmtsp()->unlinkFrBackWithNext()->deleteTree(); + return {vscp, funcp, dumpp, map}; } From b42799f3b5555106e11a5346c48644ea4c827c0b Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Wed, 19 Oct 2022 22:04:26 -0400 Subject: [PATCH 139/177] Commentary --- docs/guide/deprecations.rst | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/guide/deprecations.rst b/docs/guide/deprecations.rst index 8c0038453..3214ef8ab 100644 --- a/docs/guide/deprecations.rst +++ b/docs/guide/deprecations.rst @@ -7,9 +7,15 @@ Deprecations The following deprecated items are scheduled for future removal: C++11 compiler support - Verilator currently requires C++11 or newer compilers. Verilator will - require C++14 or newer compilers for both compiling Verilator and - compiling Verilated models no sooner than January 2023. + Verilator currently requires a C++20 or newer compiler for timing, and a + C++11 or newer compiler for no-timing. + + Verilator will require C++14 or newer compilers for both compiling + Verilator and compiling Verilated models with --no-timing no sooner than + January 2023. + + Verilator will require C++20 or newer compilers for both compiling + Verilator and compiling all Verilated models no sooner than January 2025. Verilated_heavy.h The legacy "verilated_heavy.h" include was replaced with just including From 444a4a760cbd5df593813e53f70d80eb308c0530 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Fri, 21 Oct 2022 01:54:22 +0200 Subject: [PATCH 140/177] Support timing with more Clang and libc++ versions (#3669) (#3698) --- include/verilated_timing.h | 41 ++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/include/verilated_timing.h b/include/verilated_timing.h index c07daa7af..9b179341e 100644 --- a/include/verilated_timing.h +++ b/include/verilated_timing.h @@ -29,30 +29,27 @@ // clang-format off // Some preprocessor magic to support both Clang and GCC coroutines with both libc++ and libstdc++ -#ifdef __clang__ -# if __clang_major__ < 14 -# ifdef __GLIBCXX__ // Using stdlibc++ -# define __cpp_impl_coroutine 1 // Clang doesn't define this, but it's needed for libstdc++ -# include - namespace std { // Bring coroutine library into std::experimental, as Clang < 14 expects it to be there - namespace experimental { - using namespace std; - } - } -# else // Using libc++ -# include // Clang older than 14, coroutines are under experimental - namespace std { - using namespace experimental; // Bring std::experimental into the std namespace - } -# endif -# else // Clang >= 14 -# if __GLIBCXX__ // Using stdlibc++ -# define __cpp_impl_coroutine 1 // Clang doesn't define this, but it's needed for libstdc++ -# endif -# include +#if defined _LIBCPP_VERSION // libc++ +# if __clang_major__ > 13 // Clang > 13 warns that coroutine types in std::experimental are deprecated +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-experimental-coroutine" +# endif +# include + namespace std { + using namespace experimental; // Bring std::experimental into the std namespace + } +#else +# if defined __clang__ && defined __GLIBCXX__ +# define __cpp_impl_coroutine 1 // Clang doesn't define this, but it's needed for libstdc++ # endif -#else // Not Clang # include +# if __clang_major__ < 14 + namespace std { // Bring coroutine library into std::experimental, as Clang < 14 expects it to be there + namespace experimental { + using namespace std; + } + } +# endif #endif // clang-format on From aaadc3def7513c914cc3b67cfa4375473091e6ff Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Thu, 20 Oct 2022 19:57:50 -0400 Subject: [PATCH 141/177] github: Use checkout@v3 (#3700) --- .github/workflows/build.yml | 2 +- .github/workflows/contributor.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/format.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87310899f..88d7a655f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,7 +61,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: repo diff --git a/.github/workflows/contributor.yml b/.github/workflows/contributor.yml index bbc00ce97..e40acd32d 100644 --- a/.github/workflows/contributor.yml +++ b/.github/workflows/contributor.yml @@ -13,5 +13,5 @@ jobs: name: "'docs/CONTRIBUTORS' was signed" runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - run: test_regress/t/t_dist_contributors.pl diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b88e1f05f..bf2cdb0c2 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: repo diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 2b903aa88..3aa88e6a6 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -19,7 +19,7 @@ jobs: CI_M32: 0 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Install packages for build From 23d538fdaf5459d817d14561a26bbf8e6328944f Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Thu, 20 Oct 2022 20:04:45 -0400 Subject: [PATCH 142/177] github: upload-artifacts/download-artifacts/cache@v3 --- .github/workflows/build.yml | 6 +++--- .github/workflows/coverage.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 88d7a655f..e9f39c55f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,7 +66,7 @@ jobs: path: repo - name: Cache $CCACHE_DIR - uses: actions/cache@v2 + uses: actions/cache@v3 env: CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache with: @@ -86,7 +86,7 @@ jobs: run: tar --posix -c -z -f ${{ env.VERILATOR_ARCHIVE }} repo - name: Upload tar archive - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: path: ${{ github.workspace }}/${{ env.VERILATOR_ARCHIVE }} name: ${{ env.VERILATOR_ARCHIVE }} @@ -142,7 +142,7 @@ jobs: run: tar -x -z -f ${{ env.VERILATOR_ARCHIVE }} - name: Cache $CCACHE_DIR - uses: actions/cache@v2 + uses: actions/cache@v3 env: CACHE_KEY: ${{ env.CACHE_BASE_KEY }}-ccache with: diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bf2cdb0c2..4dcffead4 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -46,7 +46,7 @@ jobs: run: tar --posix -c -z -f ${{ env.VERILATOR_ARCHIVE }} repo - name: Upload tar archive - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: path: ${{ github.workspace }}/${{ env.VERILATOR_ARCHIVE }} name: ${{ env.VERILATOR_ARCHIVE }} @@ -81,7 +81,7 @@ jobs: steps: - name: Download tar archive - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: ${{ env.VERILATOR_ARCHIVE }} path: ${{ github.workspace }} From ca8ef7ce73881b05af8b181bcb66d59f956312f8 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Thu, 20 Oct 2022 20:23:04 -0400 Subject: [PATCH 143/177] github: upload-artifacts/download-artifacts/cache@v3 (#3700) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e9f39c55f..ac09746fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -132,7 +132,7 @@ jobs: steps: - name: Download tar archive - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: ${{ env.VERILATOR_ARCHIVE }} path: ${{ github.workspace }} From 7e1b92fa75645aa2af08905929a3a08fde3131db Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Thu, 20 Oct 2022 21:42:30 -0400 Subject: [PATCH 144/177] Add --get-supported to determine what features are in Verilator (#3688). --- Changes | 1 + bin/verilator | 1 + docs/guide/exe_verilator.rst | 9 +++++++ examples/cmake_hello_sc/Makefile | 24 +++-------------- examples/cmake_tracing_sc/Makefile | 24 +++-------------- examples/make_hello_sc/Makefile | 2 +- examples/make_tracing_sc/Makefile | 2 +- src/V3Options.cpp | 24 ++++++++++++++--- src/V3Options.h | 1 + test_regress/driver.pl | 25 ++++++++++-------- test_regress/t/t_flag_supported.pl | 42 ++++++++++++++++++++++++++++++ 11 files changed, 97 insertions(+), 58 deletions(-) create mode 100755 test_regress/t/t_flag_supported.pl diff --git a/Changes b/Changes index f9a79bec6..822275bef 100644 --- a/Changes +++ b/Changes @@ -35,6 +35,7 @@ Verilator 5.001 devel * Support tristate select/extend (#3604). [Ryszard Rozak, Antmicro Ltd> * Support linting for top module interfaces (#3635). [Kanad Kanhere] * Add --dump-tree-dot to enable dumping Ast Tree .dot files (#3636). [Marcel Chang] +* Add --get-supported to determine what features are in Verilator. * Add error on real edge event control. * Fix LSB error on --hierarchical submodules (#3539). [danbone] * Fix $display of fixed-width numbers (#3565). [Iztok Jeras] diff --git a/bin/verilator b/bin/verilator index 0e5a4bc7b..cd3a25c35 100755 --- a/bin/verilator +++ b/bin/verilator @@ -335,6 +335,7 @@ detailed descriptions of these arguments. --gdbbt Run Verilator under GDB for backtrace --generate-key Create random key for --protect-key --getenv Get environment variable with defaults + --get-supported Get if feature is supported --help Display this help --hierarchical Enable hierarchical Verilation -I Directory to search for includes diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 727371a69..bae014be9 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -648,6 +648,15 @@ Summary: a newline and exit immediately. This can be useful in makefiles. See also :vlopt:`-V`, and the various :file:`*.mk` files. +.. option:: --get-supported + + If the given feature is supported, print "1" and exit + immediately. Otherwise, print a newline and exit immediately. This can + be useful in makefiles. See also :vlopt:`-V`, and the various + :file:`*.mk` files. + + Feature may be one of the following: COROUTINES, SYSTEMC. + .. option:: --help Displays this message and program version and exits. diff --git a/examples/cmake_hello_sc/Makefile b/examples/cmake_hello_sc/Makefile index 540a32fec..9ec875aba 100644 --- a/examples/cmake_hello_sc/Makefile +++ b/examples/cmake_hello_sc/Makefile @@ -39,30 +39,12 @@ ifneq ($(CMAKE_GT_3_8),true) TARGET := oldcmake else -# Test existence of SYSTEMC_INCLUDE and SYSTEMC_LIBDIR environment variabless -ifneq (,$(SYSTEMC_INCLUDE)) -ifneq (,${SYSTEMC_LIBDIR}) -SYSTEMC_SET := true -endif -endif - -# Test existence of SYSTEMC_ROOT environment variable -ifneq (SYSTEMC_SET, true) -ifneq (,${SYSTEMC_ROOT}) -SYSTEMC_SET := true -endif -endif - -# Test existence of SYSTEMC environment variable -ifneq (SYSTEMC_SET, true) -ifneq (,${SYSTEMC}) -SYSTEMC_SET := true -endif -endif +# Check if SC exists via a verilator call (empty if not) +SYSTEMC_EXISTS := $(shell $(VERILATOR) --get-supported SYSTEMC) # Test whether SystemC is installed with CMake support # This will print a CMake error about processing arguments that can (currently) be ignored. -ifneq (SYSTEMC_SET, true) +ifneq (,$(SYSTEMC_EXISTS)) FINDSC := $(shell mkdir -p build && cd build && cmake --find-package -DNAME=SystemCLanguage -DCMAKE_USE_PTHREADS_INIT=ON -DCOMPILER_ID=GNU -DLANGUAGE=CXX -DMODE=EXIST -DThreads_FOUND=ON) ifneq (,$(findstring SystemCLanguage found,$(FINDSC))) SYSTEMC_SET := true diff --git a/examples/cmake_tracing_sc/Makefile b/examples/cmake_tracing_sc/Makefile index 65868d5bf..fb10802cc 100644 --- a/examples/cmake_tracing_sc/Makefile +++ b/examples/cmake_tracing_sc/Makefile @@ -39,30 +39,12 @@ ifneq ($(CMAKE_GT_3_8),true) TARGET := oldcmake else -# Test existence of SYSTEMC_INCLUDE and SYSTEMC_LIBDIR environment variabless -ifneq (,$(SYSTEMC_INCLUDE)) -ifneq (,${SYSTEMC_LIBDIR}) -SYSTEMC_SET := true -endif -endif - -# Test existence of SYSTEMC_ROOT environment variable -ifneq (SYSTEMC_SET, true) -ifneq (,${SYSTEMC_ROOT}) -SYSTEMC_SET := true -endif -endif - -# Test existence of SYSTEMC environment variable -ifneq (SYSTEMC_SET, true) -ifneq (,${SYSTEMC}) -SYSTEMC_SET := true -endif -endif +# Check if SC exists via a verilator call (empty if not) +SYSTEMC_EXISTS := $(shell $(VERILATOR) --get-supported SYSTEMC) # Test whether SystemC is installed with CMake support # This will print a CMake error about processing arguments that can (currently) be ignored. -ifneq (SYSTEMC_SET, true) +ifneq (,$(SYSTEMC_EXISTS)) FINDSC := $(shell mkdir -p build && cd build && cmake --find-package -DNAME=SystemCLanguage -DCMAKE_USE_PTHREADS_INIT=ON -DCOMPILER_ID=GNU -DLANGUAGE=CXX -DMODE=EXIST -DThreads_FOUND=ON) ifneq (,$(findstring SystemCLanguage found,$(FINDSC))) SYSTEMC_SET := true diff --git a/examples/make_hello_sc/Makefile b/examples/make_hello_sc/Makefile index 9548d716c..20a26d034 100644 --- a/examples/make_hello_sc/Makefile +++ b/examples/make_hello_sc/Makefile @@ -33,7 +33,7 @@ VERILATOR = $(VERILATOR_ROOT)/bin/verilator endif # Check if SC exists via a verilator call (empty if not) -SYSTEMC_EXISTS := $(shell $(VERILATOR) --getenv SYSTEMC_INCLUDE) +SYSTEMC_EXISTS := $(shell $(VERILATOR) --get-supported SYSTEMC) ifneq ($(SYSTEMC_EXISTS),) default: run diff --git a/examples/make_tracing_sc/Makefile b/examples/make_tracing_sc/Makefile index 5f90a5ebf..cf61c8b7a 100644 --- a/examples/make_tracing_sc/Makefile +++ b/examples/make_tracing_sc/Makefile @@ -55,7 +55,7 @@ VERILATOR_FLAGS += --coverage VERILATOR_INPUT = -f input.vc top.v sc_main.cpp # Check if SC exists via a verilator call (empty if not) -SYSTEMC_EXISTS := $(shell $(VERILATOR) --getenv SYSTEMC_INCLUDE) +SYSTEMC_EXISTS := $(shell $(VERILATOR) --get-supported SYSTEMC) ###################################################################### diff --git a/src/V3Options.cpp b/src/V3Options.cpp index cd556a21f..deb565700 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -604,6 +604,7 @@ V3LangCode V3Options::fileLanguage(const string& filename) { // Environment string V3Options::getenvBuiltins(const string& var) { + // If update below, also update V3Options::showVersion() if (var == "MAKE") { return getenvMAKE(); } else if (var == "PERL") { @@ -711,6 +712,17 @@ string V3Options::getenvVERILATOR_ROOT() { return var; } +string V3Options::getSupported(const string& var) { + // If update below, also update V3Options::showVersion() + if (var == "COROUTINES" && coroutineSupport()) { + return "1"; + } else if (var == "SYSTEMC" && systemCFound()) { + return "1"; + } else { + return ""; + } +} + bool V3Options::systemCSystemWide() { #ifdef HAVE_SYSTEMC return true; @@ -1179,6 +1191,10 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char cout << V3Options::getenvBuiltins(valp) << endl; std::exit(0); }); + DECL_OPTION("-get-supported", CbVal, [](const char* valp) { + cout << V3Options::getSupported(valp) << endl; + std::exit(0); + }); DECL_OPTION("-hierarchical", OnOff, &m_hierarchical); DECL_OPTION("-hierarchical-block", CbVal, [this](const char* valp) { @@ -1800,6 +1816,7 @@ void V3Options::showVersion(bool verbose) { cout << " VERILATOR_ROOT = " << DEFENV_VERILATOR_ROOT << endl; cout << " SystemC system-wide = " << cvtToStr(systemCSystemWide()) << endl; + // If update below, also update V3Options::getenvBuiltins() cout << endl; cout << "Environment:\n"; cout << " MAKE = " << V3Os::getenvStr("MAKE", "") << endl; @@ -1812,10 +1829,11 @@ void V3Options::showVersion(bool verbose) { cout << " VERILATOR_BIN = " << V3Os::getenvStr("VERILATOR_BIN", "") << endl; cout << " VERILATOR_ROOT = " << V3Os::getenvStr("VERILATOR_ROOT", "") << endl; + // If update below, also update V3Options::getSupported() cout << endl; - cout << "Features (based on environment or compiled-in support):\n"; - cout << " SystemC found = " << cvtToStr(systemCFound()) << endl; - cout << " Coroutine support = " << cvtToStr(coroutineSupport()) << endl; + cout << "Supported features (compiled-in or forced by environment):\n"; + cout << " COROUTINES = " << getSupported("COROUTINES") << endl; + cout << " SYSTEMC = " << getSupported("SYSTEMC") << endl; } //====================================================================== diff --git a/src/V3Options.h b/src/V3Options.h index a19c85269..c2c3852fc 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -666,6 +666,7 @@ public: static string getenvSYSTEMC_INCLUDE(); static string getenvSYSTEMC_LIBDIR(); static string getenvVERILATOR_ROOT(); + static string getSupported(const string& var); static bool systemCSystemWide(); static bool systemCFound(); // SystemC installed, or environment points to it static bool coroutineSupport(); // Compiler supports coroutines diff --git a/test_regress/driver.pl b/test_regress/driver.pl index 3d71ef7dc..4bd739304 100755 --- a/test_regress/driver.pl +++ b/test_regress/driver.pl @@ -1476,13 +1476,13 @@ sub sc { sub have_sc { my $self = (ref $_[0] ? shift : $Self); return 1 if (defined $ENV{SYSTEMC} || defined $ENV{SYSTEMC_INCLUDE} || $ENV{CFG_HAVE_SYSTEMC}); - return 1 if $self->verilator_version =~ /systemc found *= *1/i; + return 1 if $self->verilator_get_supported('SYSTEMC'); return 0; } sub have_coroutines { my $self = (ref $_[0] ? shift : $Self); - return 1 if $self->verilator_version =~ /coroutine support *= *1/i; + return 1 if $self->verilator_get_supported('COROUTINES'); return 0; } @@ -2162,17 +2162,20 @@ sub _read_inputs_vhdl { ####################################################################### # Verilator utilities -our $_Verilator_Version; -sub verilator_version { - # Returns verbose version, line 1 contains actual version - if (!defined $_Verilator_Version) { - my @args = ("perl", "$ENV{VERILATOR_ROOT}/bin/verilator", "-V"); +our %_Verilator_Supported; +sub verilator_get_supported { + my $self = (ref $_[0] ? shift : $Self); + my $feature = shift; + # Returns if given feature is supported + if (!defined $_Verilator_Supported{$feature}) { + my @args = ("perl", "$ENV{VERILATOR_ROOT}/bin/verilator", "-get-supported", $feature); my $args = join(' ', @args); - $_Verilator_Version = `$args`; - $_Verilator_Version or die "can't fork: $! " . join(' ', @args); - chomp $_Verilator_Version; + my $out = `$args`; + $out or die "couldn't run: $! " . join(' ', @args); + chomp $out; + $_Verilator_Supported{$feature} = ($out =~ /1/ ? 1 : 0); } - return $_Verilator_Version if defined $_Verilator_Version; + return $_Verilator_Supported{$feature}; } ####################################################################### diff --git a/test_regress/t/t_flag_supported.pl b/test_regress/t/t_flag_supported.pl new file mode 100755 index 000000000..3effe6003 --- /dev/null +++ b/test_regress/t/t_flag_supported.pl @@ -0,0 +1,42 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(vlt => 1); + +if ($Self->have_coroutines) { + run( + cmd => ["../bin/verilator --get-supported COROUTINES"], + expect => '1 +', + logfile => "$Self->{obj_dir}/vlt_coroutines.log", + verilator_run => 1, + ); +} + +if ($Self->have_sc) { + run( + cmd => ["../bin/verilator --get-supported SYSTEMC"], + expect => '1 +', + logfile => "$Self->{obj_dir}/vlt_systemc.log", + verilator_run => 1, + ); +} + +run( + cmd => ["../bin/verilator --get-supported DOES_NOT_EXIST"], + expect => '', + logfile => "$Self->{obj_dir}/vlt_does_not_exist.log", + verilator_run => 1, + ); + + +ok(1); +1; From 79682e6072571486d028416ad905dd6aa32f08d3 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Thu, 20 Oct 2022 22:04:50 -0400 Subject: [PATCH 145/177] Support empty generate_regions (#3695). [mpb27] --- Changes | 1 + src/verilog.y | 4 ++++ test_regress/t/t_gen_if.v | 6 ++++++ 3 files changed, 11 insertions(+) diff --git a/Changes b/Changes index 822275bef..c24211e13 100644 --- a/Changes +++ b/Changes @@ -34,6 +34,7 @@ Verilator 5.001 devel * Support standalone 'this' in classes (#2594) (#3248) (#3675). [Arkadiusz Kozdra, Antmicro Ltd] * Support tristate select/extend (#3604). [Ryszard Rozak, Antmicro Ltd> * Support linting for top module interfaces (#3635). [Kanad Kanhere] +* Support empty generate_regions (#3695). [mpb27] * Add --dump-tree-dot to enable dumping Ast Tree .dot files (#3636). [Marcel Chang] * Add --get-supported to determine what features are in Verilator. * Add error on real edge event control. diff --git a/src/verilog.y b/src/verilog.y index 256c530db..c73dad6e4 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -1645,6 +1645,8 @@ program_generate_item: // ==IEEE: program_generate_item loop_generate_construct { $$ = $1; } | conditional_generate_construct { $$ = $1; } | generate_region { $$ = $1; } + // not in IEEE, but presumed so can do yBEGIN ... yEND + | genItemBegin { $$ = $1; } | elaboration_system_task { $$ = $1; } ; @@ -2424,6 +2426,8 @@ module_item: // ==IEEE: module_item non_port_module_item: // ==IEEE: non_port_module_item generate_region { $$ = $1; } + // not in IEEE, but presumed so can do yBEGIN ... yEND + | genItemBegin { $$ = $1; } | module_or_generate_item { $$ = $1; } | specify_block { $$ = $1; } | specparam_declaration { $$ = $1; } diff --git a/test_regress/t/t_gen_if.v b/test_regress/t/t_gen_if.v index 3a3f8dc0c..acbd2eb12 100644 --- a/test_regress/t/t_gen_if.v +++ b/test_regress/t/t_gen_if.v @@ -14,6 +14,12 @@ module t(data_i, data_o, single); output [31:0] data_o; input single; + // Bare begin/end extension of IEEE allowed by most all tools + begin + end + begin : named + end : named + //simplistic example, should choose 1st conditional generate and assign straight through //the tool also compiles the special case and determines an error (replication value is 0 generate From 8b0d71994d92e0a1ed0bf26574361c79bd3fb78b Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 21 Oct 2022 10:50:02 +0100 Subject: [PATCH 146/177] DFG: don't try to call DfgVertex::width() on arrays In DFG DfgVertex::width() is only defined for vertices representing packed values, which DfgVertex::hash() used to violate. The only non-packed values at the moment are DfgVarArray, which is a DfgVertexVar, which are handled specially anyway, so this is easy to fix. Fixes #3682 --- src/V3Dfg.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index d4cfaff40..9f08a3b12 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -387,13 +387,13 @@ bool DfgVertex::equals(const DfgVertex& that, EqualsCache& cache) const { V3Hash DfgVertex::hash() { V3Hash& result = user(); if (!result.value()) { - V3Hash hash; - hash += m_type; - hash += width(); - hash += selfHash(); - // Variables are defined by themselves, so there is no need to hash the sources. This - // enables sound hashing of graphs circular only through variables, which we rely on. + V3Hash hash{selfHash()}; + // Variables are defined by themselves, so there is no need to hash them further + // (especially the sources). This enables sound hashing of graphs circular only through + // variables, which we rely on. if (!is()) { + hash += m_type; + hash += width(); // Currently all non-variable vertices are packed, so this is safe const auto pair = sourceEdges(); const DfgEdge* const edgesp = pair.first; const size_t arity = pair.second; From 5688d1a935239c99d4d6457d365631e75b88d39f Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Fri, 21 Oct 2022 11:05:38 +0000 Subject: [PATCH 147/177] Internals: Add `V3UniqueNames` consistency assertion (#3692) --- src/V3DfgDfgToAst.cpp | 2 +- src/V3DfgOptimizer.cpp | 2 +- src/V3SenExprBuilder.h | 6 +- src/V3String.cpp | 4 + src/V3String.h | 2 + src/V3Timing.cpp | 10 +- src/V3UniqueNames.h | 10 +- test_regress/t/t_timing_debug1.out | 148 ++++++++++++++--------------- test_regress/t/t_timing_debug2.out | 26 ++--- 9 files changed, 110 insertions(+), 100 deletions(-) diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index 12cdd2dd6..0f41df69a 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -135,7 +135,7 @@ class DfgToAstVisitor final : DfgVisitor { std::unordered_map m_resultVars; // Map from an AstVar, to the canonical AstVar that can be substituted for that AstVar std::unordered_map m_canonVars; - V3UniqueNames m_tmpNames{"_VdfgTmp"}; // For generating temporary names + V3UniqueNames m_tmpNames{"__VdfgTmp"}; // For generating temporary names // METHODS diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index c2449d678..6f3fa5faa 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -103,7 +103,7 @@ class DataflowExtractVisitor final : public VNVisitor { iterateChildrenConst(nodep); // Replace candidate expressions only reading combinationally driven signals with variables - V3UniqueNames names{"_VdfgExtracted__"}; + V3UniqueNames names{"__VdfgExtracted"}; for (AstNodeModule* modp = nodep->modulesp(); modp; modp = VN_AS(modp->nextp(), NodeModule)) { // Only extract from proper modules diff --git a/src/V3SenExprBuilder.h b/src/V3SenExprBuilder.h index b5341a157..de57cc6b4 100644 --- a/src/V3SenExprBuilder.h +++ b/src/V3SenExprBuilder.h @@ -42,9 +42,9 @@ class SenExprBuilder final { // has an update statement in m_preUpdates std::unordered_set> m_hasPostUpdate; // Likewis for m_postUpdates - V3UniqueNames m_currNames{"__Vtrigcurr__expression"}; // For generating unique current value - // signal names - V3UniqueNames m_prevNames{"__Vtrigprev__expression"}; // Likewise for previous values + V3UniqueNames m_currNames{"__Vtrigcurrexpr"}; // For generating unique current value + // signal names + V3UniqueNames m_prevNames{"__Vtrigprevexpr"}; // Likewise for previous values static bool isSupportedDType(AstNodeDType* dtypep) { dtypep = dtypep->skipRefp(); diff --git a/src/V3String.cpp b/src/V3String.cpp index 6f4aa473b..4b118a537 100644 --- a/src/V3String.cpp +++ b/src/V3String.cpp @@ -183,6 +183,10 @@ bool VString::startsWith(const string& str, const string& prefix) { return str.rfind(prefix, 0) == 0; // Faster than .find(_) == 0 } +bool VString::endsWith(const string& str, const string& suffix) { + return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0; +} + //###################################################################### // VHashSha256 diff --git a/src/V3String.h b/src/V3String.h index 1fbcecaf7..7aacdb150 100644 --- a/src/V3String.h +++ b/src/V3String.h @@ -116,6 +116,8 @@ public: static string replaceWord(const string& str, const string& from, const string& to); // Predicate to check if 'str' starts with 'prefix' static bool startsWith(const string& str, const string& prefix); + // Predicate to check if 'str' ends with 'suffix' + static bool endsWith(const string& str, const string& suffix); }; //###################################################################### diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index a7f278122..c7026d230 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -108,11 +108,11 @@ private: double m_timescaleFactor = 1.0; // Factor to scale delays by // Unique names - V3UniqueNames m_contAssignVarNames{"__VassignWtmp__"}; // Names for temp AssignW vars - V3UniqueNames m_intraValueNames{"__Vintraval__"}; // Intra assign delay value var names - V3UniqueNames m_intraIndexNames{"__Vintraidx__"}; // Intra assign delay index var names - V3UniqueNames m_intraLsbNames{"__Vintralsb__"}; // Intra assign delay LSB var names - V3UniqueNames m_forkNames{"__Vfork__"}; // Fork name generator + V3UniqueNames m_contAssignVarNames{"__VassignWtmp"}; // Names for temp AssignW vars + V3UniqueNames m_intraValueNames{"__Vintraval"}; // Intra assign delay value var names + V3UniqueNames m_intraIndexNames{"__Vintraidx"}; // Intra assign delay index var names + V3UniqueNames m_intraLsbNames{"__Vintralsb"}; // Intra assign delay LSB var names + V3UniqueNames m_forkNames{"__Vfork"}; // Fork name generator V3UniqueNames m_trigSchedNames{"__VtrigSched"}; // Trigger scheduler name generator // DTypes diff --git a/src/V3UniqueNames.h b/src/V3UniqueNames.h index 34d83b8e1..1fe8439e9 100644 --- a/src/V3UniqueNames.h +++ b/src/V3UniqueNames.h @@ -33,10 +33,14 @@ class V3UniqueNames final { std::unordered_map m_multiplicity; // Suffix number for given key public: - V3UniqueNames() - : m_prefix{""} {} + V3UniqueNames() = default; explicit V3UniqueNames(const std::string& prefix) - : m_prefix{prefix} {} + : m_prefix{prefix} { + if (!m_prefix.empty()) { + UASSERT(VString::startsWith(m_prefix, "__V"), "Prefix must start with '__V'"); + UASSERT(!VString::endsWith(m_prefix, "_"), "Prefix must not end with '_'"); + } + } // Return argument, prepended with the prefix if any, then appended with a unique suffix each // time we are called with the same argument. diff --git a/test_regress/t/t_timing_debug1.out b/test_regress/t/t_timing_debug1.out index eff0962b3..792642e49 100644 --- a/test_regress/t/t_timing_debug1.out +++ b/test_regress/t/t_timing_debug1.out @@ -17,8 +17,8 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__stl -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__stl -V{t#,#} 'stl' region trigger index 0 is active: Internal 'stl' trigger - first iteration --V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk1__0) --V{t#,#} 'stl' region trigger index 2 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#} 'stl' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#} 'stl' region trigger index 2 is active: @([hybrid] __VassignWtmp_t.clk2__0) -V{t#,#} 'stl' region trigger index 3 is active: @([hybrid] t.c1) -V{t#,#}+ Vt_timing_debug1___024root___eval_stl -V{t#,#}+ Vt_timing_debug1___024root___stl_sequent__TOP__0 @@ -26,9 +26,9 @@ -V{t#,#}+ Vt_timing_debug1___024root___stl_sequent__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___stl_comb__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___stl_comb__TOP__2 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__stl -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__stl -V{t#,#} No triggers active @@ -36,8 +36,8 @@ -V{t#,#}+ Vt_timing_debug1___024root___eval -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) --V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) -V{t#,#} 'act' region trigger index 2 is active: @([hybrid] t.c1) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk1): @@ -48,9 +48,9 @@ -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__2 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act @@ -83,12 +83,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -140,12 +140,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -208,12 +208,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -260,12 +260,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 5 is active: @(posedge t.clk2) @@ -324,14 +324,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk2): -V{t#,#} - Process waiting at t/t_timing_sched.v:46 -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -392,12 +392,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -448,12 +448,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -514,12 +514,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -564,12 +564,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -600,12 +600,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -666,12 +666,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -716,12 +716,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -783,15 +783,15 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) --V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -886,12 +886,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -952,12 +952,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -1008,12 +1008,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -1074,12 +1074,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -1109,12 +1109,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -1159,12 +1159,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -1225,12 +1225,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -1275,12 +1275,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -1313,14 +1313,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk1): -V{t#,#} - Process waiting at t/t_timing_sched.v:17 -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 5 is active: @(posedge t.clk2) @@ -1406,12 +1406,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -1462,12 +1462,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -1528,12 +1528,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -1579,15 +1579,15 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) --V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -1648,12 +1648,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -1698,12 +1698,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active @@ -1764,12 +1764,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 3 is active: @(posedge t.clk1) @@ -1814,12 +1814,12 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp___t.clk2__0) +-V{t#,#} 'act' region trigger index 1 is active: @([hybrid] __VassignWtmp_t.clk2__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__1 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} 'act' region trigger index 5 is active: @(posedge t.clk2) @@ -1880,14 +1880,14 @@ -V{t#,#}+ Vt_timing_debug1___024root___act_comb__TOP__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp___t.clk1__0) +-V{t#,#} 'act' region trigger index 0 is active: @([hybrid] __VassignWtmp_t.clk1__0) -V{t#,#}+ Vt_timing_debug1___024root___timing_commit -V{t#,#} Committing processes waiting for @(posedge t.clk2): -V{t#,#} - Process waiting at t/t_timing_sched.v:46 -V{t#,#}+ Vt_timing_debug1___024root___timing_resume -V{t#,#}+ Vt_timing_debug1___024root___eval_act -V{t#,#}+ Vt_timing_debug1___024root___act_sequent__TOP__0 --V{t#,#}+ Vt_timing_debug1___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug1___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug1___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug1___024root___dump_triggers__act -V{t#,#} No triggers active diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out index 08eab7b7d..1c26fcefc 100644 --- a/test_regress/t/t_timing_debug2.out +++ b/test_regress/t/t_timing_debug2.out @@ -7,12 +7,12 @@ -V{t#,#}+ Vt_timing_debug2___024root___eval_initial -V{t#,#}+ Vt_timing_debug2___024root___eval_initial__TOP__0 -V{t#,#} Process forked at t/t_timing_fork_join.v:14 finished --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__2 --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__3 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__1 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__2 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__3 [0] fork..join process 4 -V{t#,#} Process forked at t/t_timing_fork_join.v:18 finished --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__5 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__5 -V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:13 -V{t#,#}+ Vt_timing_debug2___024root___eval_initial__TOP__1 -V{t#,#}+ Vt_timing_debug2___024root___eval_initial__TOP__2 @@ -123,9 +123,9 @@ -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:16 [16] fork in fork starts --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__2 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__1 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__2 [16] fork..join process 8 -V{t#,#} Process forked at t/t_timing_fork_join.v:25 finished -V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:21 @@ -239,7 +239,7 @@ -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:30 [64] main process --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__0 -V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:33 fork..join_any process 2 -V{t#,#} Process forked at t/t_timing_fork_join.v:37 finished @@ -315,8 +315,8 @@ fork..join_any process 1 -V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:41 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:41 --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__1 -V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:44 -V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:41 -V{t#,#}+ Vt_timing_debug2___024root___eval_act @@ -400,10 +400,10 @@ fork..join_any process 2 -V{t#,#} - Process waiting at t/t_timing_fork_join.v:51 -V{t#,#} Resuming processes waiting for @([event] t.e1) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:51 --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__0 --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__1 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__0 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__1 -V{t#,#} Suspending process waiting for @([event] t.e2) at t/t_timing_fork_join.v:62 --V{t#,#}+ Vt_timing_debug2___024root____Vfork___h########__0__2 +-V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__2 -V{t#,#} Suspending process waiting for @([event] t.e3) at t/t_timing_fork_join.v:68 in main process -V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:75 From 627a144b83d3cd2725aefef6b874e0d3fc36975b Mon Sep 17 00:00:00 2001 From: Arkadiusz Kozdra Date: Fri, 21 Oct 2022 15:00:40 +0200 Subject: [PATCH 148/177] Support access to constructs inside type parameters (#3702) This changeset brings support for accesses like: class Cls#(type TYPE1); TYPE1::some_method(); endclass It is done by delaying dot resolution on type parameters until they get resolved by V3Param, and doing a more thorough reference skip. --- src/V3AstNodeDType.h | 1 + src/V3AstNodes.cpp | 16 +++++++++++++- src/V3LinkDot.cpp | 9 +++++--- src/V3Param.cpp | 2 ++ test_regress/t/t_class_param_type.pl | 21 ++++++++++++++++++ test_regress/t/t_class_param_type.v | 32 ++++++++++++++++++++++++++++ 6 files changed, 77 insertions(+), 4 deletions(-) create mode 100755 test_regress/t/t_class_param_type.pl create mode 100644 test_regress/t/t_class_param_type.v diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index c98b10843..f8547b265 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -912,6 +912,7 @@ public: dtypep(nullptr); // V3Width will resolve } ASTGEN_MEMBERS_AstParamTypeDType; + void dump(std::ostream& str = std::cout) const override; AstNodeDType* getChildDTypep() const override { return childDTypep(); } AstNodeDType* subDTypep() const override { return dtypep() ? dtypep() : childDTypep(); } AstBasicDType* basicp() const override VL_MT_SAFE { return subDTypep()->basicp(); } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 2a3a4d975..bb0ca080e 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1719,12 +1719,25 @@ void AstTimeImport::dump(std::ostream& str) const { void AstTypedef::dump(std::ostream& str) const { this->AstNode::dump(str); if (attrPublic()) str << " [PUBLIC]"; + if (subDTypep()) { + str << " -> "; + subDTypep()->dump(str); + } } void AstNodeRange::dump(std::ostream& str) const { this->AstNode::dump(str); } void AstRange::dump(std::ostream& str) const { this->AstNodeRange::dump(str); if (littleEndian()) str << " [LITTLE]"; } +void AstParamTypeDType::dump(std::ostream& str) const { + this->AstNodeDType::dump(str); + if (subDTypep()) { + str << " -> "; + subDTypep()->dump(str); + } else { + str << " -> UNLINKED"; + } +} void AstRefDType::dump(std::ostream& str) const { this->AstNodeDType::dump(str); if (typedefp() || subDTypep()) { @@ -2095,7 +2108,8 @@ void AstClassOrPackageRef::dump(std::ostream& str) const { } AstNodeModule* AstClassOrPackageRef::classOrPackagep() const { AstNode* foundp = m_classOrPackageNodep; - while (auto* const anodep = VN_CAST(foundp, Typedef)) foundp = anodep->subDTypep(); + if (auto* const anodep = VN_CAST(foundp, Typedef)) foundp = anodep->subDTypep(); + if (auto* const anodep = VN_CAST(foundp, NodeDType)) foundp = anodep->skipRefp(); if (auto* const anodep = VN_CAST(foundp, ClassRefDType)) foundp = anodep->classp(); return VN_CAST(foundp, NodeModule); } diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 3879c918a..3ed4bd599 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -2069,11 +2069,13 @@ private: } bool isParamedClassRef(const AstNode* nodep) { + // Is this a parametrized reference to a class, or a reference to class parameter if (const auto* classRefp = VN_CAST(nodep, ClassOrPackageRef)) { if (classRefp->paramsp()) return true; const auto* classp = classRefp->classOrPackageNodep(); while (const auto* typedefp = VN_CAST(classp, Typedef)) classp = typedefp->subDTypep(); - return VN_IS(classp, ClassRefDType) && VN_AS(classp, ClassRefDType)->paramsp(); + return (VN_IS(classp, ClassRefDType) && VN_AS(classp, ClassRefDType)->paramsp()) + || VN_IS(classp, ParamTypeDType); } return false; } @@ -2633,8 +2635,9 @@ private: // Class: Recurse inside or cleanup not founds // checkNoDot not appropriate, can be under a dot AstNode::user5ClearTree(); - UASSERT_OBJ(m_statep->forPrimary() || nodep->classOrPackagep(), nodep, - "ClassRef has unlinked class"); + UASSERT_OBJ(m_statep->forPrimary() || VN_IS(nodep->classOrPackageNodep(), ParamTypeDType) + || nodep->classOrPackagep(), + nodep, "ClassRef has unlinked class"); UASSERT_OBJ(m_statep->forPrimary() || !nodep->paramsp(), nodep, "class reference parameter not removed by V3Param"); VL_RESTORER(m_ds); diff --git a/src/V3Param.cpp b/src/V3Param.cpp index baef76ddf..b1153931b 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -936,11 +936,13 @@ class ParamVisitor final : public VNVisitor { srcModp = modCellp->modp(); } else if (const auto* classRefp = VN_CAST(cellp, ClassOrPackageRef)) { srcModp = classRefp->classOrPackagep(); + if (VN_IS(classRefp->classOrPackageNodep(), ParamTypeDType)) continue; } else if (const auto* classRefp = VN_CAST(cellp, ClassRefDType)) { srcModp = classRefp->classp(); } else { cellp->v3fatalSrc("Expected module parametrization"); } + UASSERT_OBJ(srcModp, cellp, "Unlinked class ref"); // Update path string someInstanceName(modp->someInstanceName()); diff --git a/test_regress/t/t_class_param_type.pl b/test_regress/t/t_class_param_type.pl new file mode 100755 index 000000000..aabcde63e --- /dev/null +++ b/test_regress/t/t_class_param_type.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2020 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 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_class_param_type.v b/test_regress/t/t_class_param_type.v new file mode 100644 index 000000000..1501f0819 --- /dev/null +++ b/test_regress/t/t_class_param_type.v @@ -0,0 +1,32 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Arkadiusz Kozdra. +// SPDX-License-Identifier: CC0-1.0 + +// See also t_class_param.v + +class Parcls #(type T); + static function int get_p; + return T::get_p(); + endfunction +endclass + +class Cls; + static function int get_p; + return 20; + endfunction +endclass + +typedef Cls cls_t; +typedef cls_t cls2_t; + +module t (/*AUTOARG*/); + + initial begin + if (Parcls#(cls2_t)::get_p() != 20) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 196f3292d5f811e0ed8d9c67cfc05ce0617c0048 Mon Sep 17 00:00:00 2001 From: HungMingWu Date: Thu, 20 Oct 2022 20:48:44 +0800 Subject: [PATCH 149/177] Improve V3Ast function usage ergonomics (#3650) Signed-off-by: HungMingWu --- .clang-format | 1 - src/V3Active.cpp | 6 +- src/V3ActiveTop.cpp | 2 +- src/V3Ast.h | 419 +++++++++++++++++++++------------------ src/V3AstNodeOther.h | 7 +- src/V3Broken.cpp | 2 +- src/V3Const.cpp | 4 +- src/V3Dead.cpp | 2 +- src/V3DfgAstToDfg.cpp | 2 +- src/V3DfgDfgToAst.cpp | 2 +- src/V3DfgOptimizer.cpp | 2 +- src/V3EmitCSyms.cpp | 17 +- src/V3Force.cpp | 8 +- src/V3FunctionTraits.h | 50 +++++ src/V3Gate.cpp | 4 +- src/V3Inline.cpp | 2 +- src/V3MergeCond.cpp | 2 +- src/V3Order.cpp | 2 +- src/V3Partition.cpp | 2 +- src/V3Premit.cpp | 4 +- src/V3Sched.cpp | 12 +- src/V3SchedAcyclic.cpp | 2 +- src/V3SchedPartition.cpp | 14 +- src/V3SchedReplicate.cpp | 4 +- src/V3SchedTiming.cpp | 2 +- src/V3SenExprBuilder.h | 2 +- src/V3StdFuture.h | 13 ++ src/V3Task.cpp | 4 +- src/V3Timing.cpp | 8 +- src/V3Trace.cpp | 2 +- 30 files changed, 343 insertions(+), 260 deletions(-) create mode 100644 src/V3FunctionTraits.h diff --git a/.clang-format b/.clang-format index b41d0c25a..39b753e7a 100644 --- a/.clang-format +++ b/.clang-format @@ -56,7 +56,6 @@ DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - - foreach - Q_FOREACH - BOOST_FOREACH diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 8c804b92a..7ca4e1742 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -385,7 +385,7 @@ private: // Blocking assignments are always OK in combinational (and initial/final) processes if (m_check != CT_SEQ) return; - const bool ignore = nodep->lhsp()->forall([&](const AstVarRef* refp) { + const bool ignore = nodep->lhsp()->forall([&](const AstVarRef* refp) { // Ignore reads (e.g.: index expressions) if (refp->access().isReadOnly()) return true; const AstVar* const varp = refp->varp(); @@ -500,7 +500,7 @@ private: void visitSenItems(AstNode* nodep) { if (v3Global.opt.timing().isSetTrue()) { - nodep->foreach([this](AstSenItem* senItemp) { visit(senItemp); }); + nodep->foreach([this](AstSenItem* senItemp) { visit(senItemp); }); } } @@ -561,7 +561,7 @@ private: } } - nodep->sensp()->foreach([](const AstVarRef* refp) { + nodep->sensp()->foreach([](const AstVarRef* refp) { refp->varp()->usedClock(true); refp->varScopep()->user1(true); }); diff --git a/src/V3ActiveTop.cpp b/src/V3ActiveTop.cpp index 266451a41..2be352c17 100644 --- a/src/V3ActiveTop.cpp +++ b/src/V3ActiveTop.cpp @@ -47,7 +47,7 @@ class ActiveTopVisitor final : public VNVisitor { static bool isInitial(AstNode* nodep) { const VNUser1InUse user1InUse; // Return true if no variables that read. - return nodep->forall([&](const AstVarRef* refp) -> bool { + return nodep->forall([&](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. diff --git a/src/V3Ast.h b/src/V3Ast.h index 3814493ae..ecac64da1 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -23,8 +23,10 @@ #include "V3Broken.h" #include "V3Error.h" #include "V3FileLine.h" +#include "V3FunctionTraits.h" #include "V3Global.h" #include "V3Number.h" +#include "V3StdFuture.h" #include "V3Ast__gen_forward_class_decls.h" // From ./astgen @@ -2084,95 +2086,127 @@ private: using ConstCorrectAstNode = typename std::conditional::value, const AstNode, AstNode>::type; - template - inline static void foreachImpl(ConstCorrectAstNode* nodep, - const std::function& f, bool visitNext); + template + inline static void foreachImpl(ConstCorrectAstNode* nodep, const Callable& f, + bool visitNext); - template - inline static bool predicateImpl(ConstCorrectAstNode* nodep, - const std::function& p); + template + inline static bool predicateImpl(ConstCorrectAstNode* nodep, const Callable& p); - template - constexpr static bool checkTypeParameter() { - static_assert(!std::is_const::value, - "Type parameter 'T_Node' should not be const qualified"); - static_assert(std::is_base_of::value, - "Type parameter 'T_Node' must be a subtype of AstNode"); - return true; - } + template + struct Arg0NoPointerNoCV final { + using Traits = FunctionTraits; + using T_Arg0 = typename Traits::template arg<0>::type; + using T_Arg0NoPtr = typename std::remove_pointer::type; + using type = typename std::remove_cv::type; + }; public: - // Traverse subtree and call given function 'f' in pre-order on each node that has type - // 'T_Node'. The node passd to the function 'f' can be removed or replaced, but other editing + // Given a callable 'f' that takes a single argument of some AstNode subtype 'T_Node', traverse + // the tree rooted at this node, and call 'f' in pre-order on each node that is of type + // 'T_Node'. The node passed to the callable 'f' can be removed or replaced, but other editing // of the iterated tree is not safe. Prefer 'foreach' over simple VNVisitor that only needs to // handle a single (or a few) node types, as it's easier to write, but more importantly, the - // dispatch to the operation function in 'foreach' should be completely predictable by branch - // target caches in modern CPUs, while it is basically unpredictable for VNVisitor. - template - void foreach (std::function f) { - static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'"); + // dispatch to the callable in 'foreach' should be completely predictable by branch target + // caches in modern CPUs, while it is basically unpredictable for VNVisitor. + template + void foreach(Callable&& f) { + using T_Node = typename Arg0NoPointerNoCV::type; + static_assert(vlstd::is_invocable::value + && std::is_base_of::value, + "Callable 'f' must have a signature compatible with 'void(T_Node*)', " + "with 'T_Node' being a subtype of 'AstNode'"); foreachImpl(this, f, /* visitNext: */ false); } // Same as above, but for 'const' nodes - template - void foreach (std::function f) const { - static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'"); + template + void foreach(Callable&& f) const { + using T_Node = typename Arg0NoPointerNoCV::type; + static_assert(vlstd::is_invocable::value + && std::is_base_of::value, + "Callable 'f' must have a signature compatible with 'void(const T_Node*)', " + "with 'T_Node' being a subtype of 'AstNode'"); foreachImpl(this, f, /* visitNext: */ false); } - // Same as 'foreach' but also follows 'this->nextp()' - template - void foreachAndNext(std::function f) { - static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'"); + // Same as 'foreach' but also traverses 'this->nextp()' transitively + template + void foreachAndNext(Callable&& f) { + using T_Node = typename Arg0NoPointerNoCV::type; + static_assert(vlstd::is_invocable::value + && std::is_base_of::value, + "Callable 'f' must have a signature compatible with 'void(T_Node*)', " + "with 'T_Node' being a subtype of 'AstNode'"); foreachImpl(this, f, /* visitNext: */ true); } - // Same as 'foreach' but also follows 'this->nextp()' - template - void foreachAndNext(std::function f) const { - static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'"); + // Same as above, but for 'const' nodes + template + void foreachAndNext(Callable&& f) const { + using T_Node = typename Arg0NoPointerNoCV::type; + static_assert(vlstd::is_invocable::value + && std::is_base_of::value, + "Callable 'f' must have a signature compatible with 'void(const T_Node*)', " + "with 'T_Node' being a subtype of 'AstNode'"); foreachImpl(this, f, /* visitNext: */ true); } - // Given a predicate function 'p' return true if and only if there exists a node of type - // 'T_Node' that satisfies the predicate 'p'. Returns false if no node of type 'T_Node' is - // present. Traversal is performed in some arbitrary order and is terminated as soon as the - // result can be determined. - template - bool exists(std::function p) { - static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'"); + // Given a predicate 'p' that takes a single argument of some AstNode subtype 'T_Node', return + // true if and only if there exists a node of type 'T_Node' in the tree rooted at this node, + // that satisfies the predicate 'p'. Returns false if no node of type 'T_Node' is present. + // Traversal is performed in some arbitrary order and is terminated as soon as the result can + // be determined. + template + bool exists(Callable&& p) { + using T_Node = typename Arg0NoPointerNoCV::type; + static_assert(vlstd::is_invocable_r::value + && std::is_base_of::value, + "Predicate 'p' must have a signature compatible with 'bool(T_Node*)', " + "with 'T_Node' being a subtype of 'AstNode'"); return predicateImpl(this, p); } // Same as above, but for 'const' nodes - template - bool exists(std::function p) const { - static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'"); + template + bool exists(Callable&& p) const { + using T_Node = typename Arg0NoPointerNoCV::type; + static_assert(vlstd::is_invocable_r::value + && std::is_base_of::value, + "Predicate 'p' must have a signature compatible with 'bool(const T_Node*)', " + "with 'T_Node' being a subtype of 'AstNode'"); return predicateImpl(this, p); } - // Given a predicate function 'p' return true if and only if all nodes of type - // 'T_Node' satisfy the predicate 'p'. Returns true if no node of type 'T_Node' is - // present. Traversal is performed in some arbitrary order and is terminated as soon as the - // result can be determined. - template - bool forall(std::function p) { - static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'"); + // Given a predicate 'p' that takes a single argument of some AstNode subtype 'T_Node', return + // true if and only if all nodes of type 'T_Node' in the tree rooted at this node satisfy the + // predicate 'p'. Returns true if no node of type 'T_Node' is present. Traversal is performed + // in some arbitrary order and is terminated as soon as the result can be determined. + template + bool forall(Callable&& p) { + using T_Node = typename Arg0NoPointerNoCV::type; + static_assert(vlstd::is_invocable_r::value + && std::is_base_of::value, + "Predicate 'p' must have a signature compatible with 'bool(T_Node*)', " + "with 'T_Node' being a subtype of 'AstNode'"); return predicateImpl(this, p); } // Same as above, but for 'const' nodes - template - bool forall(std::function p) const { - static_assert(checkTypeParameter(), "Invalid type parameter 'T_Node'"); + template + bool forall(Callable&& p) const { + using T_Node = typename Arg0NoPointerNoCV::type; + static_assert(vlstd::is_invocable_r::value + && std::is_base_of::value, + "Predicate 'p' must have a signature compatible with 'bool(const T_Node*)', " + "with 'T_Node' being a subtype of 'AstNode'"); return predicateImpl(this, p); } int nodeCount() const { // TODO: this should really return size_t, but need to fix use sites int count = 0; - this->foreach([&count](const AstNode*) { ++count; }); + this->foreach([&count](const AstNode*) { ++count; }); return count; } }; @@ -2243,172 +2277,161 @@ constexpr bool AstNode::isLeaf() { } // foreach implementation -template -void AstNode::foreachImpl(ConstCorrectAstNode* nodep, const std::function& f, - bool visitNext) { - // Checking the function is bound up front eliminates this check from the loop at invocation - if (!f) { - nodep->v3fatal("AstNode::foreach called with unbound function"); // LCOV_EXCL_LINE - } else { - // Pre-order traversal implemented directly (without recursion) for speed reasons. The very - // first iteration (the one that operates on the input nodep) is special, as we might or - // might not need to enqueue nodep->nextp() depending on VisitNext, while in all other - // iterations, we do want to enqueue nodep->nextp(). Duplicating code (via - // 'foreachImplVisit') for the initial iteration here to avoid an extra branch in the loop +template +void AstNode::foreachImpl(ConstCorrectAstNode* nodep, const Callable& f, bool visitNext) { + // Pre-order traversal implemented directly (without recursion) for speed reasons. The very + // first iteration (the one that operates on the input nodep) is special, as we might or + // might not need to enqueue nodep->nextp() depending on VisitNext, while in all other + // iterations, we do want to enqueue nodep->nextp(). Duplicating code (via + // 'foreachImplVisit') for the initial iteration here to avoid an extra branch in the loop - using T_Arg_NonConst = typename std::remove_const::type; - using Node = ConstCorrectAstNode; + using T_Arg_NonConst = typename std::remove_const::type; + using Node = ConstCorrectAstNode; - // Traversal stack - std::vector stack; // Kept as a vector for easy resizing - Node** basep = nullptr; // Pointer to base of stack - Node** topp = nullptr; // Pointer to top of stack - Node** limp = nullptr; // Pointer to stack limit (when need growing) + // Traversal stack + std::vector stack; // Kept as a vector for easy resizing + Node** basep = nullptr; // Pointer to base of stack + Node** topp = nullptr; // Pointer to top of stack + Node** limp = nullptr; // Pointer to stack limit (when need growing) - // We prefetch this far into the stack - constexpr int prefetchDistance = 2; + // We prefetch this far into the stack + constexpr int prefetchDistance = 2; - // Grow stack to given size - const auto grow = [&](size_t size) { - const ptrdiff_t occupancy = topp - basep; - stack.resize(size); - basep = stack.data() + prefetchDistance; - topp = basep + occupancy; - limp = basep + size - 5; // We push max 5 items per iteration - }; + // Grow stack to given size + const auto grow = [&](size_t size) { + const ptrdiff_t occupancy = topp - basep; + stack.resize(size); + basep = stack.data() + prefetchDistance; + topp = basep + occupancy; + limp = basep + size - 5; // We push max 5 items per iteration + }; - // Initial stack size - grow(32); + // Initial stack size + grow(32); - // We want some non-null pointers at the beginning. These will be prefetched, but not - // visited, so the root node will suffice. This eliminates needing branches in the loop. - for (int i = -prefetchDistance; i; ++i) basep[i] = nodep; + // We want some non-null pointers at the beginning. These will be prefetched, but not + // visited, so the root node will suffice. This eliminates needing branches in the loop. + for (int i = -prefetchDistance; i; ++i) basep[i] = nodep; - // Visit given node, enqueue children for traversal - const auto visit = [&](Node* currp) { - // Type test this node - if (AstNode::privateTypeTest(currp)) { - // Call the client function - f(static_cast(currp)); - // Short circuit if iterating leaf nodes - if VL_CONSTEXPR_CXX17 (isLeaf()) return; - } - - // Enqueue children for traversal, unless futile - if (mayBeUnder(currp)) { - if (AstNode* const op4p = currp->op4p()) *topp++ = op4p; - if (AstNode* const op3p = currp->op3p()) *topp++ = op3p; - if (AstNode* const op2p = currp->op2p()) *topp++ = op2p; - if (AstNode* const op1p = currp->op1p()) *topp++ = op1p; - } - }; - - // Enqueue the next of the root node, if required - if (visitNext && nodep->nextp()) *topp++ = nodep->nextp(); - - // Visit the root node - visit(nodep); - - // Visit the rest of the tree - while (VL_LIKELY(topp > basep)) { - // Pop next node in the traversal - Node* const headp = *--topp; - - // Prefetch in case we are ascending the tree - ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]); - - // Ensure we have stack space for nextp and the 4 children - if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2); - - // Enqueue the next node - if (headp->nextp()) *topp++ = headp->nextp(); - - // Visit the head node - visit(headp); + // Visit given node, enqueue children for traversal + const auto visit = [&](Node* currp) { + // Type test this node + if (AstNode::privateTypeTest(currp)) { + // Call the client function + f(static_cast(currp)); + // Short circuit if iterating leaf nodes + if VL_CONSTEXPR_CXX17 (isLeaf()) return; } + + // Enqueue children for traversal, unless futile + if (mayBeUnder(currp)) { + if (AstNode* const op4p = currp->op4p()) *topp++ = op4p; + if (AstNode* const op3p = currp->op3p()) *topp++ = op3p; + if (AstNode* const op2p = currp->op2p()) *topp++ = op2p; + if (AstNode* const op1p = currp->op1p()) *topp++ = op1p; + } + }; + + // Enqueue the next of the root node, if required + if (visitNext && nodep->nextp()) *topp++ = nodep->nextp(); + + // Visit the root node + visit(nodep); + + // Visit the rest of the tree + while (VL_LIKELY(topp > basep)) { + // Pop next node in the traversal + Node* const headp = *--topp; + + // Prefetch in case we are ascending the tree + ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]); + + // Ensure we have stack space for nextp and the 4 children + if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2); + + // Enqueue the next node + if (headp->nextp()) *topp++ = headp->nextp(); + + // Visit the head node + visit(headp); } } // predicate implementation -template -bool AstNode::predicateImpl(ConstCorrectAstNode* nodep, - const std::function& p) { - // Implementation similar to foreach, but abort traversal as soon as result is determined. - if (VL_UNCOVERABLE(!p)) { - nodep->v3fatal("AstNode::foreach called with unbound function"); // LCOV_EXCL_LINE - } else { - using T_Arg_NonConst = typename std::remove_const::type; - using Node = ConstCorrectAstNode; +template +bool AstNode::predicateImpl(ConstCorrectAstNode* nodep, const Callable& p) { + // Implementation similar to foreach, but abort traversal as soon as result is determined + using T_Arg_NonConst = typename std::remove_const::type; + using Node = ConstCorrectAstNode; - // Traversal stack - std::vector stack; // Kept as a vector for easy resizing - Node** basep = nullptr; // Pointer to base of stack - Node** topp = nullptr; // Pointer to top of stack - Node** limp = nullptr; // Pointer to stack limit (when need growing) + // Traversal stack + std::vector stack; // Kept as a vector for easy resizing + Node** basep = nullptr; // Pointer to base of stack + Node** topp = nullptr; // Pointer to top of stack + Node** limp = nullptr; // Pointer to stack limit (when need growing) - // We prefetch this far into the stack - constexpr int prefetchDistance = 2; + // We prefetch this far into the stack + constexpr int prefetchDistance = 2; - // Grow stack to given size - const auto grow = [&](size_t size) { - const ptrdiff_t occupancy = topp - basep; - stack.resize(size); - basep = stack.data() + prefetchDistance; - topp = basep + occupancy; - limp = basep + size - 5; // We push max 5 items per iteration - }; + // Grow stack to given size + const auto grow = [&](size_t size) { + const ptrdiff_t occupancy = topp - basep; + stack.resize(size); + basep = stack.data() + prefetchDistance; + topp = basep + occupancy; + limp = basep + size - 5; // We push max 5 items per iteration + }; - // Initial stack size - grow(32); + // Initial stack size + grow(32); - // We want some non-null pointers at the beginning. These will be prefetched, but not - // visited, so the root node will suffice. This eliminates needing branches in the loop. - for (int i = -prefetchDistance; i; ++i) basep[i] = nodep; + // We want some non-null pointers at the beginning. These will be prefetched, but not + // visited, so the root node will suffice. This eliminates needing branches in the loop. + for (int i = -prefetchDistance; i; ++i) basep[i] = nodep; - // Visit given node, enqueue children for traversal, return true if result determined. - const auto visit = [&](Node* currp) { - // Type test this node - if (AstNode::privateTypeTest(currp)) { - // Call the client function - if (p(static_cast(currp)) != Default) return true; - // Short circuit if iterating leaf nodes - if VL_CONSTEXPR_CXX17 (isLeaf()) return false; - } - - // Enqueue children for traversal, unless futile - if (mayBeUnder(currp)) { - if (AstNode* const op4p = currp->op4p()) *topp++ = op4p; - if (AstNode* const op3p = currp->op3p()) *topp++ = op3p; - if (AstNode* const op2p = currp->op2p()) *topp++ = op2p; - if (AstNode* const op1p = currp->op1p()) *topp++ = op1p; - } - - return false; - }; - - // Visit the root node - if (visit(nodep)) return !Default; - - // Visit the rest of the tree - while (VL_LIKELY(topp > basep)) { - // Pop next node in the traversal - Node* const headp = *--topp; - - // Prefetch in case we are ascending the tree - ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]); - - // Ensure we have stack space for nextp and the 4 children - if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2); - - // Enqueue the next node - if (headp->nextp()) *topp++ = headp->nextp(); - - // Visit the head node - if (visit(headp)) return !Default; + // Visit given node, enqueue children for traversal, return true if result determined. + const auto visit = [&](Node* currp) { + // Type test this node + if (AstNode::privateTypeTest(currp)) { + // Call the client function + if (p(static_cast(currp)) != Default) return true; + // Short circuit if iterating leaf nodes + if VL_CONSTEXPR_CXX17 (isLeaf()) return false; } - return Default; + // Enqueue children for traversal, unless futile + if (mayBeUnder(currp)) { + if (AstNode* const op4p = currp->op4p()) *topp++ = op4p; + if (AstNode* const op3p = currp->op3p()) *topp++ = op3p; + if (AstNode* const op2p = currp->op2p()) *topp++ = op2p; + if (AstNode* const op1p = currp->op1p()) *topp++ = op1p; + } + + return false; + }; + + // Visit the root node + if (visit(nodep)) return !Default; + + // Visit the rest of the tree + while (VL_LIKELY(topp > basep)) { + // Pop next node in the traversal + Node* const headp = *--topp; + + // Prefetch in case we are ascending the tree + ASTNODE_PREFETCH_NON_NULL(topp[-prefetchDistance]); + + // Ensure we have stack space for nextp and the 4 children + if (VL_UNLIKELY(topp >= limp)) grow(stack.size() * 2); + + // Enqueue the next node + if (headp->nextp()) *topp++ = headp->nextp(); + + // Visit the head node + if (visit(headp)) return !Default; } + + return Default; } inline std::ostream& operator<<(std::ostream& os, const AstNode* rhs) { diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 5d3746ebe..764dcfaaf 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -3833,10 +3833,9 @@ public: return new AstAssignW{fileline(), lhsp, rhsp, controlp}; } bool isTimingControl() const override { - return timingControlp() - || lhsp()->exists([](const AstNodeVarRef* const refp) { - return refp->access().isWriteOrRW() && refp->varp()->delayp(); - }); + return timingControlp() || lhsp()->exists([](const AstNodeVarRef* refp) { + return refp->access().isWriteOrRW() && refp->varp()->delayp(); + }); } bool brokeLhsMustBeLvalue() const override { return true; } AstAlways* convertToAlways(); diff --git a/src/V3Broken.cpp b/src/V3Broken.cpp index c47463936..9b76b8d2b 100644 --- a/src/V3Broken.cpp +++ b/src/V3Broken.cpp @@ -329,7 +329,7 @@ void V3Broken::brokenAll(AstNetlist* nodep) { // Mark every node in the tree const uint8_t brokenCntCurrent = s_brokenCntGlobal.get(); - nodep->foreach([brokenCntCurrent](AstNode* nodep) { + nodep->foreach([brokenCntCurrent](AstNode* nodep) { #ifdef VL_LEAK_CHECKS UASSERT_OBJ(s_allocTable.isAllocated(nodep), nodep, "AstNode is in tree, but not allocated"); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index b499a1ed4..158561f07 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2038,10 +2038,10 @@ private: // Note only do this (need user4) when m_warn, which is // done as unique visitor const VNUser4InUse m_inuser4; - nodep->lhsp()->foreach([](const AstVarRef* nodep) { + nodep->lhsp()->foreach([](const AstVarRef* nodep) { if (nodep->varp()) nodep->varp()->user4(1); }); - nodep->rhsp()->foreach([&need_temp](const AstVarRef* nodep) { + nodep->rhsp()->foreach([&need_temp](const AstVarRef* nodep) { if (nodep->varp() && nodep->varp()->user4()) need_temp = true; }); } diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index d2ce5734c..32dedc071 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -322,7 +322,7 @@ private: // And its children may now be killable too; correct counts // Recurse, as cells may not be directly under the module but in a generate if (!modp->dead()) { // If was dead didn't increment user1's - modp->foreach([](const AstCell* cellp) { // + modp->foreach([](const AstCell* cellp) { // cellp->modp()->user1Inc(-1); }); } diff --git a/src/V3DfgAstToDfg.cpp b/src/V3DfgAstToDfg.cpp index 93dc09186..b494c81dc 100644 --- a/src/V3DfgAstToDfg.cpp +++ b/src/V3DfgAstToDfg.cpp @@ -91,7 +91,7 @@ class AstToDfgVisitor final : public VNVisitor { // METHODS void markReferenced(AstNode* nodep) { - nodep->foreach([this](const AstVarRef* refp) { + nodep->foreach([this](const AstVarRef* refp) { // No need to (and in fact cannot) mark variables with unsupported dtypes if (!DfgVertex::isSupportedDType(refp->varp()->dtypep())) return; getNet(refp->varp())->setHasModRefs(); diff --git a/src/V3DfgDfgToAst.cpp b/src/V3DfgDfgToAst.cpp index 0f41df69a..1ac43d319 100644 --- a/src/V3DfgDfgToAst.cpp +++ b/src/V3DfgDfgToAst.cpp @@ -425,7 +425,7 @@ class DfgToAstVisitor final : DfgVisitor { // Remap all references to point to the canonical variables, if one exists VNDeleter deleter; - m_modp->foreach([&](AstVarRef* refp) { + m_modp->foreach([&](AstVarRef* refp) { // Any variable that is written outside the DFG will have itself as the canonical // var, so need not be replaced, furthermore, if a variable is traced, we don't // want to update the write ref we just created above, so we only replace read only diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index 6f3fa5faa..db8fef6a1 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -249,7 +249,7 @@ void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) { const VNUser2InUse user2InUse; // Mark cross-referenced variables - netlistp->foreach([](const AstVarXRef* xrefp) { xrefp->varp()->user2(true); }); + netlistp->foreach([](const AstVarXRef* xrefp) { xrefp->varp()->user2(true); }); V3DfgOptimizationContext ctx{label}; diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index 6dcfab8b1..d47e749ff 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -786,15 +786,14 @@ void EmitCSyms::emitSymImp() { if (v3Global.opt.profPgo()) { puts("// Configure profiling for PGO\n"); if (v3Global.opt.mtasks()) { - v3Global.rootp()->topModulep()->foreach( - [&](const AstExecGraph* execGraphp) { - for (const V3GraphVertex* vxp = execGraphp->depGraphp()->verticesBeginp(); vxp; - vxp = vxp->verticesNextp()) { - const ExecMTask* const mtp = static_cast(vxp); - puts("_vm_pgoProfiler.addCounter(" + cvtToStr(mtp->profilerId()) + ", \"" - + mtp->hashName() + "\");\n"); - } - }); + v3Global.rootp()->topModulep()->foreach([&](const AstExecGraph* execGraphp) { + for (const V3GraphVertex* vxp = execGraphp->depGraphp()->verticesBeginp(); vxp; + vxp = vxp->verticesNextp()) { + const ExecMTask* const mtp = static_cast(vxp); + puts("_vm_pgoProfiler.addCounter(" + cvtToStr(mtp->profilerId()) + ", \"" + + mtp->hashName() + "\");\n"); + } + }); } } diff --git a/src/V3Force.cpp b/src/V3Force.cpp index bebf210ac..a1c136f6b 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -153,7 +153,7 @@ class ForceConvertVisitor final : public VNVisitor { // referenced AstVarScope with the given function. void transformWritenVarScopes(AstNode* nodep, std::function f) { UASSERT_OBJ(nodep->backp(), nodep, "Must have backp, otherwise will be lost if replaced"); - nodep->foreach([&f](AstNodeVarRef* refp) { + nodep->foreach([&f](AstNodeVarRef* refp) { if (refp->access() != VAccess::WRITE) return; // TODO: this is not strictly speaking safe for some complicated lvalues, eg.: // 'force foo[a(cnt)] = 1;', where 'cnt' is an out parameter, but it will @@ -230,7 +230,7 @@ class ForceConvertVisitor final : public VNVisitor { AstAssign* const resetRdp = new AstAssign{fl_nowarn, lhsp->cloneTree(false), lhsp->unlinkFrBack()}; // Replace write refs on the LHS - resetRdp->lhsp()->foreach([this](AstNodeVarRef* refp) { + resetRdp->lhsp()->foreach([this](AstNodeVarRef* refp) { if (refp->access() != VAccess::WRITE) return; AstVarScope* const vscp = refp->varScopep(); AstVarScope* const newVscp @@ -243,7 +243,7 @@ class ForceConvertVisitor final : public VNVisitor { VL_DO_DANGLING(refp->deleteTree(), refp); }); // Replace write refs on RHS - resetRdp->rhsp()->foreach([this](AstNodeVarRef* refp) { + resetRdp->rhsp()->foreach([this](AstNodeVarRef* refp) { if (refp->access() != VAccess::WRITE) return; AstVarScope* const vscp = refp->varScopep(); AstVarScope* const newVscp @@ -273,7 +273,7 @@ class ForceConvertVisitor final : public VNVisitor { iterateAndNextNull(nodep->modulesp()); // Replace references to forced signals - nodep->modulesp()->foreachAndNext([this](AstVarRef* nodep) { + nodep->modulesp()->foreachAndNext([this](AstVarRef* nodep) { if (ForceComponentsVarScope* const fcp = m_forceComponentsVarScope.tryGet(nodep->varScopep())) { switch (nodep->access()) { diff --git a/src/V3FunctionTraits.h b/src/V3FunctionTraits.h new file mode 100644 index 000000000..6f8a854e0 --- /dev/null +++ b/src/V3FunctionTraits.h @@ -0,0 +1,50 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Function traits for metaprogramming +// +// 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_V3FUNCTIONTRAITS_H_ +#define VERILATOR_V3FUNCTIONTRAITS_H_ + +#include "verilatedos.h" + +#include +#include +#include + +template +struct FunctionTraits; + +// For generic types, directly use the result of the signature of its 'operator()' +template +struct FunctionTraits final + : public FunctionTraits::type::operator())> {}; + +// Specialization for pointers to member function +template +struct FunctionTraits VL_NOT_FINAL { + // Number of arguments + static constexpr size_t arity = sizeof...(Args); + + // Type of result + using result_type = ReturnType; + + // Type of arguments + template + struct arg { + using type = typename std::tuple_element>::type; + }; +}; + +#endif diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp index 8a25f4513..cb5a1f498 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -1022,13 +1022,13 @@ static void eliminate(AstNode* logicp, nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); // Recursively substitute the new tree - newp->foreach(visit); + newp->foreach(visit); // Remove from recursion filter replaced.erase(vscp); }; - logicp->foreach(visit); + logicp->foreach(visit); } // ###################################################################### diff --git a/src/V3Inline.cpp b/src/V3Inline.cpp index cad404fbc..3b0214adb 100644 --- a/src/V3Inline.cpp +++ b/src/V3Inline.cpp @@ -513,7 +513,7 @@ private: newmodp = nodep->modp(); } // Find cell cross-references - nodep->modp()->foreach([](AstCell* cellp) { + nodep->modp()->foreach([](AstCell* cellp) { // clonep is nullptr when inlining the last instance, if so the use original node cellp->user4p(cellp->clonep() ? cellp->clonep() : cellp); }); diff --git a/src/V3MergeCond.cpp b/src/V3MergeCond.cpp index 6103c62b1..fea4cae5e 100644 --- a/src/V3MergeCond.cpp +++ b/src/V3MergeCond.cpp @@ -744,7 +744,7 @@ private: if (!m_mgFirstp) { UASSERT_OBJ(condp, nodep, "Cannot start new list without condition"); // Mark variable references in the condition - condp->foreach([](const AstVarRef* nodep) { nodep->varp()->user1(1); }); + condp->foreach([](const AstVarRef* nodep) { nodep->varp()->user1(1); }); // Now check again if mergeable. We need this to pick up assignments to conditions, // e.g.: 'c = c ? a : b' at the beginning of the list, which is in fact not mergeable // because it updates the condition. We simply bail on these. diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 80c147a40..88283587b 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -230,7 +230,7 @@ class OrderBuildVisitor final : public VNVisitor { m_hybridp = nodep->sensesp(); // Mark AstVarScopes that are explicit sensitivities AstNode::user3ClearTree(); - senTreep->foreach([](const AstVarRef* refp) { // + senTreep->foreach([](const AstVarRef* refp) { // refp->varScopep()->user3(true); }); m_readTriggersCombLogic = [](const AstVarScope* vscp) { return !vscp->user3(); }; diff --git a/src/V3Partition.cpp b/src/V3Partition.cpp index 94b48c99b..a9106d549 100644 --- a/src/V3Partition.cpp +++ b/src/V3Partition.cpp @@ -3215,7 +3215,7 @@ static void implementExecGraph(AstExecGraph* const execGraphp) { void V3Partition::finalize(AstNetlist* netlistp) { // Called by Verilator top stage - netlistp->topModulep()->foreach([&](AstExecGraph* execGraphp) { + netlistp->topModulep()->foreach([&](AstExecGraph* execGraphp) { // Back in V3Order, we partitioned mtasks using provisional cost // estimates. However, V3Order precedes some optimizations (notably // V3LifePost) that can change the cost of logic within each mtask. diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index 48f8c5c31..5411dbf07 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -186,10 +186,10 @@ private: bool noopt = false; { const VNUser3InUse user3InUse; - nodep->lhsp()->foreach([](const AstVarRef* refp) { + nodep->lhsp()->foreach([](const AstVarRef* refp) { if (refp->access().isWriteOrRW()) refp->varp()->user3(true); }); - nodep->rhsp()->foreach([&noopt](const AstVarRef* refp) { + nodep->rhsp()->foreach([&noopt](const AstVarRef* refp) { if (refp->access().isReadOnly() && refp->varp()->user3()) noopt = true; }); } diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index c81c27869..bb48fa171 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -161,10 +161,10 @@ void splitCheck(AstCFunc* ofuncp) { LogicClasses gatherLogicClasses(AstNetlist* netlistp) { LogicClasses result; - netlistp->foreach([&](AstScope* scopep) { + netlistp->foreach([&](AstScope* scopep) { std::vector empty; - scopep->foreach([&](AstActive* activep) { + scopep->foreach([&](AstActive* activep) { AstSenTree* const senTreep = activep->sensesp(); if (!activep->stmtsp()) { // Some AstActives might be empty due to previous optimizations @@ -671,7 +671,7 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde // so we can make them sc_sensitive if (v3Global.opt.systemC()) { logic.foreachLogic([](AstNode* logicp) { - logicp->foreach([](AstVarRef* refp) { + logicp->foreach([](AstVarRef* refp) { if (refp->access().isWriteOnly()) return; AstVarScope* const vscp = refp->varScopep(); if (vscp->scopep()->isTop() && vscp->varp()->isNonOutput()) { @@ -769,13 +769,13 @@ void createEval(AstNetlist* netlistp, // AstCFunc* const nbaDumpp = actTrig.m_dumpp->cloneTree(false); actTrig.m_dumpp->addNextHere(nbaDumpp); nbaDumpp->name("_dump_triggers__nba"); - nbaDumpp->foreach([&](AstVarRef* refp) { + nbaDumpp->foreach([&](AstVarRef* refp) { UASSERT_OBJ(refp->access().isReadOnly(), refp, "Should only read state"); if (refp->varScopep() == actTrig.m_vscp) { refp->replaceWith(new AstVarRef{refp->fileline(), nbaTrigsp, VAccess::READ}); } }); - nbaDumpp->foreach([&](AstText* textp) { // + nbaDumpp->foreach([&](AstText* textp) { // textp->text(VString::replaceWord(textp->text(), "act", "nba")); }); @@ -970,7 +970,7 @@ void schedule(AstNetlist* netlistp) { // Replace references in each mapped value with a reference to the given vscp for (auto& pair : newMap) { pair.second = pair.second->cloneTree(false); - pair.second->foreach([&](AstVarRef* refp) { + pair.second->foreach([&](AstVarRef* refp) { UASSERT_OBJ(refp->varScopep() == actTrigVscp, refp, "Unexpected reference"); UASSERT_OBJ(refp->access() == VAccess::READ, refp, "Should be read ref"); refp->replaceWith(new AstVarRef{refp->fileline(), vscp, VAccess::READ}); diff --git a/src/V3SchedAcyclic.cpp b/src/V3SchedAcyclic.cpp index 6818761a5..89c783fb8 100644 --- a/src/V3SchedAcyclic.cpp +++ b/src/V3SchedAcyclic.cpp @@ -140,7 +140,7 @@ std::unique_ptr buildGraph(const LogicByScope& lbs) { const VNUser2InUse user2InUse; const VNUser3InUse user3InUse; - nodep->foreach([&](AstVarRef* refp) { + nodep->foreach([&](AstVarRef* refp) { AstVarScope* const vscp = refp->varScopep(); VarVertex* const vvtxp = getVarVertex(vscp); // We want to cut the narrowest signals diff --git a/src/V3SchedPartition.cpp b/src/V3SchedPartition.cpp index 573a89993..b356afde1 100644 --- a/src/V3SchedPartition.cpp +++ b/src/V3SchedPartition.cpp @@ -164,7 +164,7 @@ class SchedGraphBuilder final : public VNVisitor { SchedSenVertex* const vtxp = new SchedSenVertex{m_graphp, senItemp}; // Connect up the variable references - senItemp->sensp()->foreach([&](AstVarRef* refp) { + senItemp->sensp()->foreach([&](AstVarRef* refp) { new V3GraphEdge{m_graphp, getVarVertex(refp->varScopep()), vtxp, 1}; }); @@ -186,7 +186,7 @@ class SchedGraphBuilder final : public VNVisitor { // Clocked or hybrid logic has explicit sensitivity, so add edge from sensitivity vertex if (!m_senTreep->hasCombo()) { - m_senTreep->foreach([=](AstSenItem* senItemp) { + m_senTreep->foreach([=](AstSenItem* senItemp) { if (senItemp->isIllegal()) return; UASSERT_OBJ(senItemp->isClocked() || senItemp->isHybrid(), nodep, "Non-clocked SenItem under clocked SenTree"); @@ -196,7 +196,7 @@ class SchedGraphBuilder final : public VNVisitor { } // Add edges based on references - nodep->foreach([=](const AstVarRef* vrefp) { + nodep->foreach([=](const AstVarRef* vrefp) { AstVarScope* const vscp = vrefp->varScopep(); if (vrefp->access().isReadOrRW() && m_readTriggersThisLogic(vscp)) { new V3GraphEdge{m_graphp, getVarVertex(vscp), logicVtxp, 10}; @@ -208,7 +208,7 @@ class SchedGraphBuilder final : public VNVisitor { // If the logic calls a 'context' DPI import, it might fire the DPI Export trigger if (m_dpiExportTriggerp) { - nodep->foreach([=](const AstCCall* callp) { + nodep->foreach([=](const AstCCall* callp) { if (!callp->funcp()->dpiImportWrapper()) return; if (!callp->funcp()->dpiContext()) return; new V3GraphEdge{m_graphp, logicVtxp, getVarVertex(m_dpiExportTriggerp), 10}; @@ -226,7 +226,7 @@ class SchedGraphBuilder final : public VNVisitor { // Mark explicit sensitivities as not triggering these blocks if (senTreep->hasHybrid()) { AstNode::user2ClearTree(); - senTreep->foreach([](const AstVarRef* refp) { // + senTreep->foreach([](const AstVarRef* refp) { // refp->varScopep()->user2(true); }); } @@ -364,7 +364,7 @@ LogicRegions partition(LogicByScope& clockedLogic, LogicByScope& combinationalLo const VNUser2InUse user2InUse; // AstVarScope::user2() -> bool: writen in Active region const auto markVars = [](AstNode* nodep) { - nodep->foreach([](const AstNodeVarRef* vrefp) { + nodep->foreach([](const AstNodeVarRef* vrefp) { AstVarScope* const vscp = vrefp->varScopep(); if (vrefp->access().isReadOrRW()) vscp->user1(true); if (vrefp->access().isWriteOrRW()) vscp->user2(true); @@ -386,7 +386,7 @@ LogicRegions partition(LogicByScope& clockedLogic, LogicByScope& combinationalLo nextp = nodep->nextp(); if (AstAssignPre* const logicp = VN_CAST(nodep, AssignPre)) { bool toActiveRegion = false; - logicp->foreach([&](const AstNodeVarRef* vrefp) { + logicp->foreach([&](const AstNodeVarRef* vrefp) { AstVarScope* const vscp = vrefp->varScopep(); if (vrefp->access().isReadOnly()) { // Variable only read in Pre, and is written in active region diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 81a1ff1fb..7c3d3fb3c 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -176,7 +176,7 @@ std::unique_ptr buildGraph(const LogicRegions& logicRegions) { // Hybrid logic is triggered by all reads, except for reads of the explicit // sensitivities readTriggersThisLogic = [](AstVarScope* vscp) { return !vscp->user4(); }; - senTreep->foreach([](const AstVarRef* refp) { // + senTreep->foreach([](const AstVarRef* refp) { // refp->varScopep()->user4(true); }); } @@ -187,7 +187,7 @@ std::unique_ptr buildGraph(const LogicRegions& logicRegions) { const VNUser2InUse user2InUse; const VNUser3InUse user3InUse; - nodep->foreach([&](AstVarRef* refp) { + nodep->foreach([&](AstVarRef* refp) { AstVarScope* const vscp = refp->varScopep(); VarVertex* const vvtxp = getVarVertex(vscp); diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index 6fc2e0656..36d9acd54 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -256,7 +256,7 @@ void transformForks(AstNetlist* const netlistp) { // flow analysis framework which we don't have at the moment void remapLocals(AstCFunc* const funcp, AstCCall* const callp) { const VNUser2InUse user2InUse; // AstVarScope -> AstVarScope: var to remap to - funcp->foreach([&](AstNodeVarRef* refp) { + funcp->foreach([&](AstNodeVarRef* refp) { AstVar* const varp = refp->varp(); AstBasicDType* const dtypep = varp->dtypep()->basicp(); // If it a fork sync or an intra-assignment variable, pass it by value diff --git a/src/V3SenExprBuilder.h b/src/V3SenExprBuilder.h index de57cc6b4..c1e5ef57c 100644 --- a/src/V3SenExprBuilder.h +++ b/src/V3SenExprBuilder.h @@ -56,7 +56,7 @@ class SenExprBuilder final { } static bool isSimpleExpr(const AstNode* const exprp) { - return exprp->forall([](const AstNode* const nodep) { + return exprp->forall([](const AstNode* const nodep) { return VN_IS(nodep, Const) || VN_IS(nodep, NodeVarRef) || VN_IS(nodep, Sel) || VN_IS(nodep, NodeSel) || VN_IS(nodep, MemberSel) || VN_IS(nodep, CMethodHard); diff --git a/src/V3StdFuture.h b/src/V3StdFuture.h index 32a4f7539..ea630b7fd 100644 --- a/src/V3StdFuture.h +++ b/src/V3StdFuture.h @@ -17,6 +17,8 @@ #ifndef VERILATOR_V3STDFUTURE_H_ #define VERILATOR_V3STDFUTURE_H_ +#include + namespace vlstd { // constexpr std::max with arguments passed by value (required by constexpr before C++14) @@ -25,6 +27,17 @@ constexpr T max(T a, T b) { return a > b ? a : b; } +// C++17 is_invocable +template +struct is_invocable + : std::is_constructible, + std::reference_wrapper::type>> {}; + +// C++17 is_invocable_r +template +struct is_invocable_r + : std::is_constructible, + std::reference_wrapper::type>> {}; }; // namespace vlstd #endif // Guard diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 528d7a32b..0d1eb2c22 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -408,7 +408,7 @@ private: // Replace varrefs with new var pointer void relink(AstNode* nodep) { - nodep->foreachAndNext([](AstVarRef* refp) { + nodep->foreachAndNext([](AstVarRef* refp) { if (refp->varp()->user2p()) { // It's being converted to an alias. AstVarScope* const newvscp = VN_AS(refp->varp()->user2p(), VarScope); refp->varScopep(newvscp); @@ -1276,7 +1276,7 @@ private: // Mark non-local variables written by the exported function bool writesNonLocals = false; - cfuncp->foreach([&writesNonLocals](AstVarRef* refp) { + cfuncp->foreach([&writesNonLocals](AstVarRef* refp) { if (refp->access().isReadOnly()) return; // Ignore read reference AstVar* const varp = refp->varScopep()->varp(); // We are ignoring function locals as they should not be referenced anywhere diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index c7026d230..6eddfd97a 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -138,7 +138,7 @@ private: AstDelay* getLhsNetDelay(AstNodeAssign* nodep) const { bool foundWrite = false; AstDelay* delayp = nullptr; - nodep->lhsp()->foreach([&](const AstNodeVarRef* const refp) { + nodep->lhsp()->foreach([&](const AstNodeVarRef* const refp) { if (!refp->access().isWriteOrRW()) return; UASSERT_OBJ(!foundWrite, nodep, "Should only be one variable written to on the LHS"); foundWrite = true; @@ -190,7 +190,7 @@ private: AstSenItem* varRefpsToSenItemsp(AstNode* const nodep) const { AstNode* senItemsp = nullptr; const VNUser4InUse user4InUse; - nodep->foreach([&](AstNodeVarRef* refp) { + nodep->foreach([&](AstNodeVarRef* refp) { if (refp->access().isWriteOnly()) return; AstVarScope* const vscp = refp->varScopep(); if (vscp->user4SetOnce()) return; @@ -530,12 +530,12 @@ private: // we want as only the top level selects are LValues. As an example, // this transforms 'x[a[i]][b[j]] = y' // into 't1 = a[i]; t0 = b[j]; x[t1][t0] = y'. - nodep->lhsp()->foreach([&](AstSel* selp) { + nodep->lhsp()->foreach([&](AstSel* selp) { if (VN_IS(selp->lsbp(), Const)) return; replaceWithIntermediate(selp->lsbp(), m_intraLsbNames.get(nodep)); // widthp should be const }); - nodep->lhsp()->foreach([&](AstNodeSel* selp) { + nodep->lhsp()->foreach([&](AstNodeSel* selp) { if (VN_IS(selp->bitp(), Const)) return; replaceWithIntermediate(selp->bitp(), m_intraIndexNames.get(nodep)); }); diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index 8b90cbf9f..89703a67f 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -434,7 +434,7 @@ private: } else if (AstCFunc* const funcp = VN_CAST(insertp, CFunc)) { // If there are awaits, insert the setter after each await if (funcp->isCoroutine() && funcp->stmtsp()) { - funcp->stmtsp()->foreachAndNext([&](AstCAwait* awaitp) { + funcp->stmtsp()->foreachAndNext([&](AstCAwait* awaitp) { if (awaitp->nextp()) awaitp->addNextHere(setterp->cloneTree(false)); }); } From 2e4f5c863ffa6ab1afca883559ee5a6ca989e9d7 Mon Sep 17 00:00:00 2001 From: Jiuyang Liu Date: Sat, 22 Oct 2022 01:04:42 +0800 Subject: [PATCH 150/177] Fix VPI inline module naming mismatch (#3690) (#3694) --- docs/CONTRIBUTORS | 1 + src/V3EmitCSyms.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 480e34d5e..1c78172f7 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -53,6 +53,7 @@ Jamie Iles Jan Van Winkel Jean Berniolles Jeremy Bennett +Jiuyang Liu John Coiner John Demme Jonathan Drolet diff --git a/src/V3EmitCSyms.cpp b/src/V3EmitCSyms.cpp index d47e749ff..756b29096 100644 --- a/src/V3EmitCSyms.cpp +++ b/src/V3EmitCSyms.cpp @@ -295,7 +295,7 @@ class EmitCSyms final : EmitCBaseVisitor { if (v3Global.opt.vpi()) { const string type = (nodep->origModName() == "__BEGIN__") ? "SCOPE_OTHER" : "SCOPE_MODULE"; - const string name = nodep->scopep()->name() + "__DOT__" + nodep->name(); + const string name = nodep->scopep()->shortName() + "__DOT__" + nodep->name(); const string name_dedot = AstNode::dedotName(name); const int timeunit = m_modp->timeunit().powerOfTen(); m_vpiScopeCandidates.insert( From 785c51dd0b9e67b87b18b6c23a19934aa1937f2b Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Fri, 21 Oct 2022 20:56:44 +0000 Subject: [PATCH 151/177] Fix emitting timing debug info with `--protect-ids` (#3689) (#3701) --- include/verilated_timing.h | 16 +++-- src/V3SchedTiming.cpp | 4 +- src/V3Timing.cpp | 29 ++++---- test_regress/t/t_timing_debug2.out | 104 ++++++++++++++-------------- test_regress/t/t_timing_fork_join.v | 46 ++++++------ test_regress/t/t_timing_protect.pl | 43 ++++++++++++ 6 files changed, 147 insertions(+), 95 deletions(-) create mode 100755 test_regress/t/t_timing_protect.pl diff --git a/include/verilated_timing.h b/include/verilated_timing.h index 9b179341e..79eb1c0dd 100644 --- a/include/verilated_timing.h +++ b/include/verilated_timing.h @@ -53,6 +53,9 @@ #endif // clang-format on +// Placeholder for compiling with --protect-ids +#define VL_UNKNOWN "" + //============================================================================= // VlFileLineDebug stores a SystemVerilog source code location. Used in VlCoroutineHandle for // debugging purposes. @@ -170,7 +173,7 @@ public: void dump() const; #endif // Used by coroutines for co_awaiting a certain simulation time - auto delay(uint64_t delay, const char* filename, int lineno) { + auto delay(uint64_t delay, const char* filename = VL_UNKNOWN, int lineno = 0) { struct Awaitable { VlDelayedCoroutineQueue& queue; uint64_t delay; @@ -208,16 +211,17 @@ class VlTriggerScheduler final { public: // METHODS // Resumes all coroutines from the 'ready' stage - void resume(const char* eventDescription); + void resume(const char* eventDescription = VL_UNKNOWN); // Moves all coroutines from m_uncommitted to m_ready - void commit(const char* eventDescription); + void commit(const char* eventDescription = VL_UNKNOWN); // Are there no coroutines awaiting? bool empty() const { return m_ready.empty() && m_uncommitted.empty(); } #ifdef VL_DEBUG void dump(const char* eventDescription) const; #endif // Used by coroutines for co_awaiting a certain trigger - auto trigger(const char* eventDescription, const char* filename, int lineno) { + auto trigger(const char* eventDescription = VL_UNKNOWN, const char* filename = VL_UNKNOWN, + int lineno = 0) { VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n", eventDescription, filename, lineno);); struct Awaitable { @@ -273,9 +277,9 @@ public: void init(size_t count) { m_join.reset(new VlJoin{count, {}}); } // Called whenever any of the forked processes finishes. If the join counter reaches 0, the // main process gets resumed - void done(const char* filename, int lineno); + void done(const char* filename = VL_UNKNOWN, int lineno = 0); // Used by coroutines for co_awaiting a join - auto join(const char* filename, int lineno) { + auto join(const char* filename = VL_UNKNOWN, int lineno = 0) { assert(m_join); VL_DEBUG_IF( VL_DBG_MSGF(" Awaiting join of fork at: %s:%d\n", filename, lineno);); diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp index 36d9acd54..ac208122a 100644 --- a/src/V3SchedTiming.cpp +++ b/src/V3SchedTiming.cpp @@ -115,7 +115,7 @@ AstCCall* TimingKit::createCommit(AstNetlist* const netlistp) { // Create the commit call and put it in the commit function auto* const commitp = new AstCMethodHard{ flp, new AstVarRef{flp, schedulerp, VAccess::READWRITE}, "commit"}; - commitp->addPinsp(resumep->pinsp()->cloneTree(false)); + if (resumep->pinsp()) commitp->addPinsp(resumep->pinsp()->cloneTree(false)); commitp->statement(true); commitp->dtypeSetVoid(); newactp->addStmtsp(commitp); @@ -159,7 +159,7 @@ TimingKit prepareTiming(AstNetlist* const netlistp) { // Create a resume() call on the timing scheduler auto* const resumep = new AstCMethodHard{ flp, new AstVarRef{flp, schedulerp, VAccess::READWRITE}, "resume"}; - if (schedulerp->dtypep()->basicp()->isTriggerScheduler()) { + if (schedulerp->dtypep()->basicp()->isTriggerScheduler() && methodp->pinsp()) { resumep->addPinsp(methodp->pinsp()->cloneTree(false)); } resumep->statement(true); diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 6eddfd97a..02cdee3e5 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -256,6 +256,19 @@ private: } return VN_AS(sensesp->user2p(), Text)->cloneTree(false); } + // Adds debug info to a hardcoded method call + void addDebugInfo(AstCMethodHard* const methodp) const { + if (v3Global.opt.protectIds()) return; + FileLine* const flp = methodp->fileline(); + methodp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'}); + methodp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())}); + } + // Adds debug info to a trigSched.trigger() call + void addEventDebugInfo(AstCMethodHard* const methodp, AstSenTree* const sensesp) const { + if (v3Global.opt.protectIds()) return; + methodp->addPinsp(createEventDescription(sensesp)); + addDebugInfo(methodp); + } // Creates the fork handle type and returns it AstBasicDType* getCreateForkSyncDTypep() { if (m_forkDtp) return m_forkDtp; @@ -287,9 +300,7 @@ private: beginp->fileline(), new AstVarRef{flp, forkVscp, VAccess::WRITE}, "done"}; donep->dtypeSetVoid(); donep->statement(true); - // Add debug info - donep->addPinsp(new AstText{flp, '"' + flp->filename() + '"'}); - donep->addPinsp(new AstText{flp, cvtToStr(flp->lineno())}); + addDebugInfo(donep); beginp->addStmtsp(donep); } // Handle the 'join' part of a fork..join @@ -317,9 +328,7 @@ private: auto* const joinp = new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE}, "join"}; joinp->dtypeSetVoid(); - // Add debug info - joinp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'}); - joinp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())}); + addDebugInfo(joinp); auto* const awaitp = new AstCAwait{flp, joinp}; awaitp->statement(true); forkp->addNextHere(awaitp); @@ -462,9 +471,7 @@ private: auto* const delayMethodp = new AstCMethodHard{ flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::WRITE}, "delay", valuep}; delayMethodp->dtypeSetVoid(); - // Add debug info - delayMethodp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'}); - delayMethodp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())}); + addDebugInfo(delayMethodp); // Create the co_await auto* const awaitp = new AstCAwait{flp, delayMethodp, getCreateDelaySenTree()}; awaitp->statement(true); @@ -487,9 +494,7 @@ private: "trigger"}; triggerMethodp->dtypeSetVoid(); // Add debug info - triggerMethodp->addPinsp(createEventDescription(sensesp)); - triggerMethodp->addPinsp(new AstText{flp, '"' + flp->filename() + '"'}); - triggerMethodp->addPinsp(new AstText{flp, cvtToStr(flp->lineno())}); + addEventDebugInfo(triggerMethodp, sensesp); // Create the co_await auto* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp}; awaitp->statement(true); diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out index 1c26fcefc..4988bab73 100644 --- a/test_regress/t/t_timing_debug2.out +++ b/test_regress/t/t_timing_debug2.out @@ -240,7 +240,7 @@ -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:30 [64] main process -V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__0 --V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:33 +-V{t#,#} Suspending process waiting for @([event] t.event1) at t/t_timing_fork_join.v:33 fork..join_any process 2 -V{t#,#} Process forked at t/t_timing_fork_join.v:37 finished -V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:31 @@ -250,7 +250,7 @@ back in main process -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#} No triggers active -V{t#,#}+ Vt_timing_debug2___024root___timing_commit --V{t#,#} Committing processes waiting for @([event] t.e1): +-V{t#,#} Committing processes waiting for @([event] t.event1): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:33 -V{t#,#}+ Vt_timing_debug2___024root___eval_nba -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act @@ -274,23 +274,23 @@ back in main process -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 1 is active: @([event] t.e1) +-V{t#,#} 'act' region trigger index 1 is active: @([event] t.event1) -V{t#,#}+ Vt_timing_debug2___024root___timing_commit -V{t#,#}+ Vt_timing_debug2___024root___timing_resume --V{t#,#} Ready processes waiting for @([event] t.e1): +-V{t#,#} Ready processes waiting for @([event] t.event1): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:33 --V{t#,#} Resuming processes waiting for @([event] t.e1) +-V{t#,#} Resuming processes waiting for @([event] t.event1) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:33 fork..join_any process 1 -V{t#,#} Process forked at t/t_timing_fork_join.v:32 finished -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 1 is active: @([event] t.e1) +-V{t#,#} 'act' region trigger index 1 is active: @([event] t.event1) -V{t#,#}+ Vt_timing_debug2___024root___timing_commit -V{t#,#}+ Vt_timing_debug2___024root___timing_resume --V{t#,#} No ready processes waiting for @([event] t.e1) --V{t#,#} Resuming processes waiting for @([event] t.e1) +-V{t#,#} No ready processes waiting for @([event] t.event1) +-V{t#,#} Resuming processes waiting for @([event] t.event1) -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act @@ -317,14 +317,14 @@ fork..join_any process 1 -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:41 -V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__1 --V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:44 +-V{t#,#} Suspending process waiting for @([event] t.event1) at t/t_timing_fork_join.v:44 -V{t#,#} Awaiting join of fork at: t/t_timing_fork_join.v:41 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#} No triggers active -V{t#,#}+ Vt_timing_debug2___024root___timing_commit --V{t#,#} Committing processes waiting for @([event] t.e1): +-V{t#,#} Committing processes waiting for @([event] t.event1): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:44 -V{t#,#}+ Vt_timing_debug2___024root___eval_nba -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act @@ -373,50 +373,50 @@ back in main process -V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:50 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:50 --V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:51 +-V{t#,#} Suspending process waiting for @([event] t.event1) at t/t_timing_fork_join.v:51 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 1 is active: @([event] t.e1) +-V{t#,#} 'act' region trigger index 1 is active: @([event] t.event1) -V{t#,#}+ Vt_timing_debug2___024root___timing_commit -V{t#,#}+ Vt_timing_debug2___024root___timing_resume --V{t#,#} Ready processes waiting for @([event] t.e1): +-V{t#,#} Ready processes waiting for @([event] t.event1): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:44 --V{t#,#} Uncommitted processes waiting for @([event] t.e1): +-V{t#,#} Uncommitted processes waiting for @([event] t.event1): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:51 --V{t#,#} Resuming processes waiting for @([event] t.e1) +-V{t#,#} Resuming processes waiting for @([event] t.event1) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:44 fork..join_any process 2 -V{t#,#} Process forked at t/t_timing_fork_join.v:43 finished --V{t#,#} Committing processes waiting for @([event] t.e1): +-V{t#,#} Committing processes waiting for @([event] t.event1): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:51 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 1 is active: @([event] t.e1) +-V{t#,#} 'act' region trigger index 1 is active: @([event] t.event1) -V{t#,#}+ Vt_timing_debug2___024root___timing_commit -V{t#,#}+ Vt_timing_debug2___024root___timing_resume --V{t#,#} Ready processes waiting for @([event] t.e1): +-V{t#,#} Ready processes waiting for @([event] t.event1): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:51 --V{t#,#} Resuming processes waiting for @([event] t.e1) +-V{t#,#} Resuming processes waiting for @([event] t.event1) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:51 -V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__0 -V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__1 --V{t#,#} Suspending process waiting for @([event] t.e2) at t/t_timing_fork_join.v:62 +-V{t#,#} Suspending process waiting for @([event] t.event2) at t/t_timing_fork_join.v:62 -V{t#,#}+ Vt_timing_debug2___024root____Vfork_h########__0__2 --V{t#,#} Suspending process waiting for @([event] t.e3) at t/t_timing_fork_join.v:68 +-V{t#,#} Suspending process waiting for @([event] t.event3) at t/t_timing_fork_join.v:68 in main process --V{t#,#} Suspending process waiting for @([event] t.e1) at t/t_timing_fork_join.v:75 --V{t#,#} Committing processes waiting for @([event] t.e1): +-V{t#,#} Suspending process waiting for @([event] t.event1) at t/t_timing_fork_join.v:75 +-V{t#,#} Committing processes waiting for @([event] t.event1): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:75 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act -V{t#,#} No triggers active -V{t#,#}+ Vt_timing_debug2___024root___timing_commit --V{t#,#} Committing processes waiting for @([event] t.e2): +-V{t#,#} Committing processes waiting for @([event] t.event2): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:62 --V{t#,#} Committing processes waiting for @([event] t.e3): +-V{t#,#} Committing processes waiting for @([event] t.event3): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:68 -V{t#,#}+ Vt_timing_debug2___024root___eval_nba -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act @@ -438,21 +438,21 @@ in main process -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:56 fork..join_none process 1 --V{t#,#} Suspending process waiting for @([event] t.e2) at t/t_timing_fork_join.v:58 +-V{t#,#} Suspending process waiting for @([event] t.event2) at t/t_timing_fork_join.v:58 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 2 is active: @([event] t.e2) +-V{t#,#} 'act' region trigger index 2 is active: @([event] t.event2) -V{t#,#}+ Vt_timing_debug2___024root___timing_commit -V{t#,#}+ Vt_timing_debug2___024root___timing_resume --V{t#,#} Ready processes waiting for @([event] t.e2): +-V{t#,#} Ready processes waiting for @([event] t.event2): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:62 --V{t#,#} Uncommitted processes waiting for @([event] t.e2): +-V{t#,#} Uncommitted processes waiting for @([event] t.event2): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:58 --V{t#,#} Resuming processes waiting for @([event] t.e2) +-V{t#,#} Resuming processes waiting for @([event] t.event2) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:62 fork..join_none process 2 --V{t#,#} Committing processes waiting for @([event] t.e2): +-V{t#,#} Committing processes waiting for @([event] t.event2): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:58 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act @@ -478,21 +478,21 @@ fork..join_none process 2 -V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:63 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:63 --V{t#,#} Suspending process waiting for @([event] t.e3) at t/t_timing_fork_join.v:64 +-V{t#,#} Suspending process waiting for @([event] t.event3) at t/t_timing_fork_join.v:64 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 3 is active: @([event] t.e3) +-V{t#,#} 'act' region trigger index 3 is active: @([event] t.event3) -V{t#,#}+ Vt_timing_debug2___024root___timing_commit -V{t#,#}+ Vt_timing_debug2___024root___timing_resume --V{t#,#} Ready processes waiting for @([event] t.e3): +-V{t#,#} Ready processes waiting for @([event] t.event3): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:68 --V{t#,#} Uncommitted processes waiting for @([event] t.e3): +-V{t#,#} Uncommitted processes waiting for @([event] t.event3): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:64 --V{t#,#} Resuming processes waiting for @([event] t.e3) +-V{t#,#} Resuming processes waiting for @([event] t.event3) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:68 fork..join_none process 3 --V{t#,#} Committing processes waiting for @([event] t.e3): +-V{t#,#} Committing processes waiting for @([event] t.event3): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:64 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act @@ -518,21 +518,21 @@ fork..join_none process 3 -V{t#,#} Awaiting time 100: Process waiting at t/t_timing_fork_join.v:69 -V{t#,#} Resuming delayed processes -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:69 --V{t#,#} Suspending process waiting for @([event] t.e3) at t/t_timing_fork_join.v:70 +-V{t#,#} Suspending process waiting for @([event] t.event3) at t/t_timing_fork_join.v:70 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 3 is active: @([event] t.e3) +-V{t#,#} 'act' region trigger index 3 is active: @([event] t.event3) -V{t#,#}+ Vt_timing_debug2___024root___timing_commit -V{t#,#}+ Vt_timing_debug2___024root___timing_resume --V{t#,#} Ready processes waiting for @([event] t.e3): +-V{t#,#} Ready processes waiting for @([event] t.event3): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:64 --V{t#,#} Uncommitted processes waiting for @([event] t.e3): +-V{t#,#} Uncommitted processes waiting for @([event] t.event3): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:70 --V{t#,#} Resuming processes waiting for @([event] t.e3) +-V{t#,#} Resuming processes waiting for @([event] t.event3) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:64 fork..join_none process 2 again --V{t#,#} Committing processes waiting for @([event] t.e3): +-V{t#,#} Committing processes waiting for @([event] t.event3): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:70 -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act @@ -561,12 +561,12 @@ fork..join_none process 2 again -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 2 is active: @([event] t.e2) +-V{t#,#} 'act' region trigger index 2 is active: @([event] t.event2) -V{t#,#}+ Vt_timing_debug2___024root___timing_commit -V{t#,#}+ Vt_timing_debug2___024root___timing_resume --V{t#,#} Ready processes waiting for @([event] t.e2): +-V{t#,#} Ready processes waiting for @([event] t.event2): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:58 --V{t#,#} Resuming processes waiting for @([event] t.e2) +-V{t#,#} Resuming processes waiting for @([event] t.event2) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:58 fork..join_none process 1 again -V{t#,#}+ Vt_timing_debug2___024root___eval_act @@ -596,23 +596,23 @@ fork..join_none process 1 again -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 3 is active: @([event] t.e3) +-V{t#,#} 'act' region trigger index 3 is active: @([event] t.event3) -V{t#,#}+ Vt_timing_debug2___024root___timing_commit -V{t#,#}+ Vt_timing_debug2___024root___timing_resume --V{t#,#} Ready processes waiting for @([event] t.e3): +-V{t#,#} Ready processes waiting for @([event] t.event3): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:70 --V{t#,#} Resuming processes waiting for @([event] t.e3) +-V{t#,#} Resuming processes waiting for @([event] t.event3) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:70 fork..join_none process 3 again -V{t#,#}+ Vt_timing_debug2___024root___eval_act -V{t#,#}+ Vt_timing_debug2___024root___eval_triggers__act -V{t#,#}+ Vt_timing_debug2___024root___dump_triggers__act --V{t#,#} 'act' region trigger index 1 is active: @([event] t.e1) +-V{t#,#} 'act' region trigger index 1 is active: @([event] t.event1) -V{t#,#}+ Vt_timing_debug2___024root___timing_commit -V{t#,#}+ Vt_timing_debug2___024root___timing_resume --V{t#,#} Ready processes waiting for @([event] t.e1): +-V{t#,#} Ready processes waiting for @([event] t.event1): -V{t#,#} - Process waiting at t/t_timing_fork_join.v:75 --V{t#,#} Resuming processes waiting for @([event] t.e1) +-V{t#,#} Resuming processes waiting for @([event] t.event1) -V{t#,#} Resuming: Process waiting at t/t_timing_fork_join.v:75 *-* All Finished *-* -V{t#,#}+ Vt_timing_debug2___024root___eval_act diff --git a/test_regress/t/t_timing_fork_join.v b/test_regress/t/t_timing_fork_join.v index 12ec14ef6..71865da6a 100644 --- a/test_regress/t/t_timing_fork_join.v +++ b/test_regress/t/t_timing_fork_join.v @@ -5,9 +5,9 @@ // SPDX-License-Identifier: CC0-1.0 module t; - event e1; - event e2; - event e3; + event event1; + event event2; + event event3; initial begin fork @@ -30,49 +30,49 @@ module t; #32 $write("[%0t] main process\n", $time); fork begin - @e1; + @event1; $write("fork..join_any process 1\n"); - ->e1; + ->event1; end $write("fork..join_any process 2\n"); join_any $write("back in main process\n"); - #1 ->e1; + #1 ->event1; #1 fork #2 $write("fork..join_any process 1\n"); begin - @e1; + @event1; $write("fork..join_any process 2\n"); - ->e1; + ->event1; end join_any $write("back in main process\n"); - #1 ->e1; - @e1; + #1 ->event1; + @event1; // Order of triggering: - // p1->e2 ==> p2->e3 ==> p3->e3 ==> p2->e2 ==> p1->e3 ==> p3->e1 + // p1->event2 ==> p2->event3 ==> p3->event3 ==> p2->event2 ==> p1->event3 ==> p3->event1 fork begin #1 $write("fork..join_none process 1\n"); - ->e2; - @e2 $write("fork..join_none process 1 again\n"); - #1 ->e3; + ->event2; + @event2 $write("fork..join_none process 1 again\n"); + #1 ->event3; end begin - @e2 $write("fork..join_none process 2\n"); - #1 ->e3; - @e3 $write("fork..join_none process 2 again\n"); - #1 ->e2; + @event2 $write("fork..join_none process 2\n"); + #1 ->event3; + @event3 $write("fork..join_none process 2 again\n"); + #1 ->event2; end begin - @e3 $write("fork..join_none process 3\n"); - #1 ->e3; - @e3 $write("fork..join_none process 3 again\n"); - ->e1; + @event3 $write("fork..join_none process 3\n"); + #1 ->event3; + @event3 $write("fork..join_none process 3 again\n"); + ->event1; end join_none $write("in main process\n"); - @e1; + @event1; $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_timing_protect.pl b/test_regress/t/t_timing_protect.pl new file mode 100755 index 000000000..96606c188 --- /dev/null +++ b/test_regress/t/t_timing_protect.pl @@ -0,0 +1,43 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_timing_fork_join.v"); # Contains all relevant constructs + + compile( + verilator_flags2 => ["--exe --main --timing --protect-ids"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); + + if ($Self->{vlt_all}) { + # Check for secret in any outputs + my $any; + foreach my $filename (glob $Self->{obj_dir} . "/*.[ch]*") { + file_grep_not($filename, qr/event[123]/i); + file_grep_not($filename, qr/t_timing_fork_join/i); + $any = 1; + } + $any or $Self->error("No outputs found"); + +} + +} + +ok(1); +1; From 9d020828013af6df8a1e741ea506a0e08ddd12d0 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Fri, 21 Oct 2022 17:08:57 -0400 Subject: [PATCH 152/177] Internals: Avoid VM_COVERAGE ifdef in library to get closer to libverilated.a --- include/verilated_cov.cpp | 3 --- include/verilated_cov.h | 23 +++++------------------ 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/include/verilated_cov.cpp b/include/verilated_cov.cpp index 89b7fb86d..c442bbae7 100644 --- a/include/verilated_cov.cpp +++ b/include/verilated_cov.cpp @@ -357,9 +357,6 @@ public: void write(const char* filename) VL_MT_SAFE_EXCLUDES(m_mutex) { Verilated::quiesce(); const VerilatedLockGuard lock{m_mutex}; -#ifndef VM_COVERAGE - VL_FATAL_MT("", 0, "", "%Error: Called VerilatedCov::write when VM_COVERAGE disabled"); -#endif selftest(); std::ofstream os{filename}; diff --git a/include/verilated_cov.h b/include/verilated_cov.h index 4c3b1550d..4697c4f88 100644 --- a/include/verilated_cov.h +++ b/include/verilated_cov.h @@ -35,22 +35,6 @@ class VerilatedCovImp; -//============================================================================= -/// Conditionally compile statements only when doing coverage (when -/// VM_COVERAGE is defined) - -// clang-format off -#ifdef VM_COVERAGE -# define VL_IF_COVER(stmts) \ - do { stmts; } while (false) -#else -# define VL_IF_COVER(stmts) \ - do { \ - if (false) { stmts; } \ - } while (false) -#endif -// clang-format on - //============================================================================= /// Insert a item for coverage analysis. /// The first argument is a pointer to the count to be dumped. @@ -83,8 +67,11 @@ class VerilatedCovImp; /// } #define VL_COVER_INSERT(covcontextp, countp, ...) \ - VL_IF_COVER(covcontextp->_inserti(countp); covcontextp->_insertf(__FILE__, __LINE__); \ - covcontextp->_insertp("hier", name(), __VA_ARGS__)) + do { \ + covcontextp->_inserti(countp); \ + covcontextp->_insertf(__FILE__, __LINE__); \ + covcontextp->_insertp("hier", name(), __VA_ARGS__); \ + } while (false) //============================================================================= // Convert VL_COVER_INSERT value arguments to strings, is \internal From 347e9b4ec86ec8e2aef84ecbc6b6a52106724b8e Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Fri, 21 Oct 2022 18:26:39 -0400 Subject: [PATCH 153/177] Fix cell assigning integer array parameters (#3299). --- Changes | 1 + src/V3Width.cpp | 13 ++++++++++--- test_regress/t/t_param_array8.pl | 21 +++++++++++++++++++++ test_regress/t/t_param_array8.v | 26 ++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) create mode 100755 test_regress/t/t_param_array8.pl create mode 100644 test_regress/t/t_param_array8.v diff --git a/Changes b/Changes index c24211e13..b942c54ac 100644 --- a/Changes +++ b/Changes @@ -38,6 +38,7 @@ Verilator 5.001 devel * Add --dump-tree-dot to enable dumping Ast Tree .dot files (#3636). [Marcel Chang] * Add --get-supported to determine what features are in Verilator. * Add error on real edge event control. +* Fix cell assigning integer array parameters (#3299). [Michael Platzer] * Fix LSB error on --hierarchical submodules (#3539). [danbone] * Fix $display of fixed-width numbers (#3565). [Iztok Jeras] * Fix foreach and pre/post increment in functions (#3613). [Nandu Raj] diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 23319a0ef..8e1a0ffa4 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -4740,12 +4740,19 @@ private: // TOP LEVEL NODE if (nodep->modVarp() && nodep->modVarp()->isGParam()) { // Widthing handled as special init() case + bool didWidth = false; if (auto* const patternp = VN_CAST(nodep->exprp(), Pattern)) { - if (const auto* modVarp = nodep->modVarp()) { - patternp->childDTypep(modVarp->childDTypep()->cloneTree(false)); + if (const AstVar* const modVarp = nodep->modVarp()) { + // Convert BracketArrayDType + userIterate(modVarp->childDTypep(), + WidthVP{SELF, BOTH}.p()); // May relink pointed to node + AstNodeDType* const setDtp = modVarp->childDTypep()->cloneTree(false); + patternp->childDTypep(setDtp); + userIterateChildren(nodep, WidthVP{setDtp, BOTH}.p()); + didWidth = true; } } - userIterateChildren(nodep, WidthVP(SELF, BOTH).p()); + if (!didWidth) userIterateChildren(nodep, WidthVP(SELF, BOTH).p()); } else if (!m_paramsOnly) { if (!nodep->modVarp()->didWidth()) { // Var hasn't been widthed, so make it so. diff --git a/test_regress/t/t_param_array8.pl b/test_regress/t/t_param_array8.pl new file mode 100755 index 000000000..b46d46042 --- /dev/null +++ b/test_regress/t/t_param_array8.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_param_array8.v b/test_regress/t/t_param_array8.v new file mode 100644 index 000000000..c19d4bba8 --- /dev/null +++ b/test_regress/t/t_param_array8.v @@ -0,0 +1,26 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module sub + #( + parameter int unsigned VAL[2] = '{1, 2} + ) + (); +endmodule + +module t; + sub sub12 (); + sub #(.VAL ( '{3, 4} )) sub34 (); + + initial begin + if (sub12.VAL[0] != 1) $stop; + if (sub12.VAL[1] != 2) $stop; + if (sub34.VAL[0] != 3) $stop; + if (sub34.VAL[1] != 4) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 203993b018f2990ab4025f44a283eb213afd07fc Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Fri, 21 Oct 2022 19:03:40 -0400 Subject: [PATCH 154/177] Internals: Fix constructor style. --- src/V3Active.cpp | 7 ++--- src/V3Assert.cpp | 72 ++++++++++++++++++++++++------------------------ 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 7ca4e1742..06a1d7f51 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -129,7 +129,7 @@ public: // Start a new if/else tracking graph // See NODE STATE comment in ActiveLatchCheckVisitor AstNode::user1ClearTree(); - m_curVertexp = new LatchDetectGraphVertex(this, "ROOT"); + m_curVertexp = new LatchDetectGraphVertex{this, "ROOT"}; } // Clear out userp field of referenced outputs on destruction // (occurs at the end of each combinational always block) @@ -165,7 +165,7 @@ public: } else { outVertexp = castVertexp(nodep->varp()->user1p()); } - new V3GraphEdge(this, m_curVertexp, outVertexp, 1); + new V3GraphEdge{this, m_curVertexp, outVertexp, 1}; } // Run latchCheckInternal on each variable assigned by the always block to see if all control // paths make an assignment. Detected latches are flagged in the variables AstVar @@ -271,7 +271,7 @@ public: // No such AstActive yet, creat it, and add to map. AstSenTree* const newsenp = sensesp->cloneTree(false); - AstActive* const activep = new AstActive(fl, "sequent", newsenp); + AstActive* const activep = new AstActive{fl, "sequent", newsenp}; activep->sensesStorep(activep->sensesp()); addActive(activep); m_activeMap.emplace(*newsenp, activep); @@ -556,7 +556,6 @@ private: if (const auto* const dtypep = nodep->sensp()->dtypep()) { if (const auto* const basicp = dtypep->basicp()) { - if (basicp->isEvent()) nodep->edgeType(VEdgeType::ET_EVENT); } } diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index a499aaf7b..bc49f8834 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -59,7 +59,7 @@ private: nodep->displayType(VDisplayType::DT_WRITE); nodep->fmtp()->text(assertDisplayMessage(nodep, prefix, nodep->fmtp()->text())); // cppcheck-suppress nullPointer - AstNode* const timenewp = new AstTime(nodep->fileline(), m_modp->timeunit()); + AstNode* const timenewp = new AstTime{nodep->fileline(), m_modp->timeunit()}; if (AstNode* const timesp = nodep->fmtp()->exprsp()) { timesp->unlinkFrBackWithNext(); timenewp->addNext(timesp); @@ -80,7 +80,7 @@ private: nodep->findUInt64DType()}; v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorNumVarp); } - const auto varrefp = new AstVarRef(nodep->fileline(), m_monitorNumVarp, access); + const auto varrefp = new AstVarRef{nodep->fileline(), m_monitorNumVarp, access}; varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp()); return varrefp; } @@ -90,7 +90,7 @@ private: nodep->findBitDType()}; v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(m_monitorOffVarp); } - const auto varrefp = new AstVarRef(nodep->fileline(), m_monitorOffVarp, access); + const auto varrefp = new AstVarRef{nodep->fileline(), m_monitorOffVarp, access}; varrefp->classOrPackagep(v3Global.rootp()->dollarUnitPkgAddp()); return varrefp; } @@ -98,16 +98,16 @@ private: // Add a internal if to check assertions are on. // Don't make this a AND term, as it's unlikely to need to test this. FileLine* const fl = nodep->fileline(); - AstNode* const newp = new AstIf( + AstNode* const newp = new AstIf{ fl, - (force ? new AstConst(fl, AstConst::BitTrue()) + (force ? new AstConst{fl, AstConst::BitTrue{}} : // If assertions are off, have constant propagation rip them out later // This allows syntax errors and such to be detected normally. (v3Global.opt.assertOn() ? static_cast( - new AstCMath(fl, "vlSymsp->_vm_contextp__->assertOn()", 1)) - : static_cast(new AstConst(fl, AstConst::BitFalse())))), - nodep); + new AstCMath{fl, "vlSymsp->_vm_contextp__->assertOn()", 1}) + : static_cast(new AstConst{fl, AstConst::BitFalse{}}))), + nodep}; newp->user1(true); // Don't assert/cover this if return newp; } @@ -115,11 +115,11 @@ private: AstNode* newFireAssertUnchecked(AstNode* nodep, const string& message) { // Like newFireAssert() but omits the asserts-on check AstDisplay* const dispp - = new AstDisplay(nodep->fileline(), VDisplayType::DT_ERROR, message, nullptr, nullptr); + = new AstDisplay{nodep->fileline(), VDisplayType::DT_ERROR, message, nullptr, nullptr}; dispp->fmtp()->timeunit(m_modp->timeunit()); AstNode* const bodysp = dispp; replaceDisplay(dispp, "%%Error"); // Convert to standard DISPLAY format - bodysp->addNext(new AstStop(nodep->fileline(), true)); + bodysp->addNext(new AstStop{nodep->fileline(), true}); return bodysp; } @@ -163,7 +163,7 @@ private: } if (bodysp && passsp) bodysp = bodysp->addNext(passsp); - ifp = new AstIf(nodep->fileline(), propp, bodysp); + ifp = new AstIf{nodep->fileline(), propp, bodysp}; bodysp = ifp; } else if (VN_IS(nodep, Assert) || VN_IS(nodep, AssertIntrinsic)) { if (nodep->immediate()) { @@ -175,7 +175,7 @@ private: if (passsp) passsp = newIfAssertOn(passsp, force); if (failsp) failsp = newIfAssertOn(failsp, force); if (!failsp) failsp = newFireAssertUnchecked(nodep, "'assert' failed."); - ifp = new AstIf(nodep->fileline(), propp, passsp, failsp); + ifp = new AstIf{nodep->fileline(), propp, passsp, failsp}; // It's more LIKELY that we'll take the nullptr if clause // than the sim-killing else clause: ifp->branchPred(VBranchPred::BP_LIKELY); @@ -186,7 +186,7 @@ private: AstNode* newp; if (sentreep) { - newp = new AstAlways(nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, bodysp); + newp = new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, bodysp}; } else { newp = bodysp; } @@ -227,7 +227,7 @@ private: // Build a bitmask of the true predicates AstNode* const predp = ifp->condp()->cloneTree(false); if (propp) { - propp = new AstConcat(nodep->fileline(), predp, propp); + propp = new AstConcat{nodep->fileline(), predp, propp}; } else { propp = predp; } @@ -242,17 +242,17 @@ private: const bool allow_none = nodep->unique0Pragma(); // Empty case means no property - if (!propp) propp = new AstConst(nodep->fileline(), AstConst::BitFalse()); + if (!propp) propp = new AstConst{nodep->fileline(), AstConst::BitFalse{}}; // Note: if this ends with an 'else', then we don't need to validate that one of the // predicates evaluates to true. AstNode* const ohot = ((allow_none || hasDefaultElse) - ? static_cast(new AstOneHot0(nodep->fileline(), propp)) - : static_cast(new AstOneHot(nodep->fileline(), propp))); + ? static_cast(new AstOneHot0{nodep->fileline(), propp}) + : static_cast(new AstOneHot{nodep->fileline(), propp})); AstIf* const checkifp - = new AstIf(nodep->fileline(), new AstLogNot(nodep->fileline(), ohot), - newFireAssert(nodep, "'unique if' statement violated"), newifp); + = new AstIf{nodep->fileline(), new AstLogNot{nodep->fileline(), ohot}, + newFireAssert(nodep, "'unique if' statement violated"), newifp}; checkifp->branchPred(VBranchPred::BP_UNLIKELY); nodep->replaceWith(checkifp); pushDeletep(nodep); @@ -274,9 +274,9 @@ private: // Need to add a default if there isn't one already ++m_statAsFull; if (!has_default) { - nodep->addItemsp(new AstCaseItem( + nodep->addItemsp(new AstCaseItem{ nodep->fileline(), nullptr /*DEFAULT*/, - newFireAssert(nodep, "synthesis full_case, but non-match found"))); + newFireAssert(nodep, "synthesis full_case, but non-match found")}); } } if (nodep->parallelPragma() || nodep->uniquePragma() || nodep->unique0Pragma()) { @@ -305,24 +305,24 @@ private: icondp->cloneTree(false)); } if (propp) { - propp = new AstConcat(icondp->fileline(), onep, propp); + propp = new AstConcat{icondp->fileline(), onep, propp}; } else { propp = onep; } } } // Empty case means no property - if (!propp) propp = new AstConst(nodep->fileline(), AstConst::BitFalse()); + if (!propp) propp = new AstConst{nodep->fileline(), AstConst::BitFalse{}}; const bool allow_none = has_default || nodep->unique0Pragma(); AstNode* const ohot = (allow_none - ? static_cast(new AstOneHot0(nodep->fileline(), propp)) - : static_cast(new AstOneHot(nodep->fileline(), propp))); - AstIf* const ifp = new AstIf( - nodep->fileline(), new AstLogNot(nodep->fileline(), ohot), + ? static_cast(new AstOneHot0{nodep->fileline(), propp}) + : static_cast(new AstOneHot{nodep->fileline(), propp})); + AstIf* const ifp = new AstIf{ + nodep->fileline(), new AstLogNot{nodep->fileline(), ohot}, newFireAssert(nodep, - "synthesis parallel_case, but multiple matches found")); + "synthesis parallel_case, but multiple matches found")}; ifp->branchPred(VBranchPred::BP_UNLIKELY); nodep->addNotParallelp(ifp); } @@ -346,19 +346,19 @@ private: AstSenTree* const sentreep = nodep->sentreep(); sentreep->unlinkFrBack(); AstAlways* const alwaysp - = new AstAlways(nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, nullptr); + = new AstAlways{nodep->fileline(), VAlwaysKwd::ALWAYS, sentreep, nullptr}; m_modp->addStmtsp(alwaysp); for (uint32_t i = 0; i < ticks; ++i) { - AstVar* const outvarp = new AstVar( + AstVar* const outvarp = new AstVar{ nodep->fileline(), VVarType::MODULETEMP, - "_Vpast_" + cvtToStr(m_modPastNum++) + "_" + cvtToStr(i), inp->dtypep()); + "_Vpast_" + cvtToStr(m_modPastNum++) + "_" + cvtToStr(i), inp->dtypep()}; m_modp->addStmtsp(outvarp); - AstNode* const assp = new AstAssignDly( - nodep->fileline(), new AstVarRef(nodep->fileline(), outvarp, VAccess::WRITE), inp); + AstNode* const assp = new AstAssignDly{ + nodep->fileline(), new AstVarRef{nodep->fileline(), outvarp, VAccess::WRITE}, inp}; alwaysp->addStmtsp(assp); // if (debug() >= 9) assp->dumpTree(cout, "-ass: "); invarp = outvarp; - inp = new AstVarRef(nodep->fileline(), invarp, VAccess::READ); + inp = new AstVarRef{nodep->fileline(), invarp, VAccess::READ}; } nodep->replaceWith(inp); } @@ -452,8 +452,8 @@ private: } void visit(AstMonitorOff* nodep) override { const auto newp - = new AstAssign(nodep->fileline(), newMonitorOffVarRefp(nodep, VAccess::WRITE), - new AstConst(nodep->fileline(), AstConst::BitTrue{}, nodep->off())); + = new AstAssign{nodep->fileline(), newMonitorOffVarRefp(nodep, VAccess::WRITE), + new AstConst{nodep->fileline(), AstConst::BitTrue{}, nodep->off()}}; nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } From a57a3579c0a38ffec51c835d737b8f96a1dc301d Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Fri, 21 Oct 2022 19:10:06 -0400 Subject: [PATCH 155/177] Fix false LATCH warning on 'unique if' (#3088). --- Changes | 3 ++- src/V3Assert.cpp | 9 ++++++++- src/V3AstNodeOther.h | 2 +- test_regress/t/t_lint_latch_4.v | 32 +++++++++++++++++++------------- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/Changes b/Changes index b942c54ac..528fc7bf7 100644 --- a/Changes +++ b/Changes @@ -25,7 +25,7 @@ Verilator 5.001 devel * Introduce a new combinational logic optimizer (DFG), that can yield significant performance improvements on some designs. [Geza Lore, Shunyao CAD] * Add --binary option as alias of --main --exe --build --timing (#3625). - For designs where C++ was only used to make a simple testbench we + For designs where C++ was only used to make a simple no-I/O testbench, we recommend abandoning that C++, and instead letting Verilator build it with --binary (or --main). @@ -38,6 +38,7 @@ Verilator 5.001 devel * Add --dump-tree-dot to enable dumping Ast Tree .dot files (#3636). [Marcel Chang] * Add --get-supported to determine what features are in Verilator. * Add error on real edge event control. +* Fix false LATCH warning on 'unique if' (#3088). [Rachit Nigam] * Fix cell assigning integer array parameters (#3299). [Michael Platzer] * Fix LSB error on --hierarchical submodules (#3539). [danbone] * Fix $display of fixed-width numbers (#3565). [Iztok Jeras] diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index bc49f8834..40d9f609f 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -98,7 +98,7 @@ private: // Add a internal if to check assertions are on. // Don't make this a AND term, as it's unlikely to need to test this. FileLine* const fl = nodep->fileline(); - AstNode* const newp = new AstIf{ + AstNodeIf* const newp = new AstIf{ fl, (force ? new AstConst{fl, AstConst::BitTrue{}} : // If assertions are off, have constant propagation rip them out later @@ -108,6 +108,7 @@ private: new AstCMath{fl, "vlSymsp->_vm_contextp__->assertOn()", 1}) : static_cast(new AstConst{fl, AstConst::BitFalse{}}))), nodep}; + newp->isBoundsCheck(true); // To avoid LATCH warning newp->user1(true); // Don't assert/cover this if return newp; } @@ -164,6 +165,7 @@ private: if (bodysp && passsp) bodysp = bodysp->addNext(passsp); ifp = new AstIf{nodep->fileline(), propp, bodysp}; + ifp->isBoundsCheck(true); // To avoid LATCH warning bodysp = ifp; } else if (VN_IS(nodep, Assert) || VN_IS(nodep, AssertIntrinsic)) { if (nodep->immediate()) { @@ -176,6 +178,7 @@ private: if (failsp) failsp = newIfAssertOn(failsp, force); if (!failsp) failsp = newFireAssertUnchecked(nodep, "'assert' failed."); ifp = new AstIf{nodep->fileline(), propp, passsp, failsp}; + ifp->isBoundsCheck(true); // To avoid LATCH warning // It's more LIKELY that we'll take the nullptr if clause // than the sim-killing else clause: ifp->branchPred(VBranchPred::BP_LIKELY); @@ -253,6 +256,7 @@ private: AstIf* const checkifp = new AstIf{nodep->fileline(), new AstLogNot{nodep->fileline(), ohot}, newFireAssert(nodep, "'unique if' statement violated"), newifp}; + checkifp->isBoundsCheck(true); // To avoid LATCH warning checkifp->branchPred(VBranchPred::BP_UNLIKELY); nodep->replaceWith(checkifp); pushDeletep(nodep); @@ -323,6 +327,7 @@ private: nodep->fileline(), new AstLogNot{nodep->fileline(), ohot}, newFireAssert(nodep, "synthesis parallel_case, but multiple matches found")}; + ifp->isBoundsCheck(true); // To avoid LATCH warning ifp->branchPred(VBranchPred::BP_UNLIKELY); nodep->addNotParallelp(ifp); } @@ -425,6 +430,7 @@ private: new AstEq{fl, new AstConst{fl, monNum}, newMonitorNumVarRefp(nodep, VAccess::READ)}}, stmtsp}; + ifp->isBoundsCheck(true); // To avoid LATCH warning ifp->branchPred(VBranchPred::BP_UNLIKELY); AstNode* const newp = new AstAlways{fl, VAlwaysKwd::ALWAYS, nullptr, ifp}; m_modp->addStmtsp(newp); @@ -443,6 +449,7 @@ private: // Add "always_comb if (__Vstrobe) begin $display(...); __Vstrobe = '0; end" AstNode* const stmtsp = nodep; AstIf* const ifp = new AstIf{fl, new AstVarRef{fl, varp, VAccess::READ}, stmtsp}; + ifp->isBoundsCheck(true); // To avoid LATCH warning ifp->branchPred(VBranchPred::BP_UNLIKELY); AstNode* const newp = new AstAlwaysPostponed{fl, ifp}; stmtsp->addNext(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 764dcfaaf..3e2fd6430 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -504,7 +504,7 @@ class AstNodeIf VL_NOT_FINAL : public AstNodeStmt { // @astgen op3 := elsesp : List[AstNode] private: VBranchPred m_branchPred; // Branch prediction as taken/untaken? - bool m_isBoundsCheck; // True if this if node was inserted for array bounds checking + bool m_isBoundsCheck; // True if this if node is for assertion/bounds checking protected: AstNodeIf(VNType t, FileLine* fl, AstNode* condp, AstNode* thensp, AstNode* elsesp) : AstNodeStmt{t, fl} { diff --git a/test_regress/t/t_lint_latch_4.v b/test_regress/t/t_lint_latch_4.v index e7182f4ca..9f5504dc0 100644 --- a/test_regress/t/t_lint_latch_4.v +++ b/test_regress/t/t_lint_latch_4.v @@ -7,21 +7,27 @@ module test ( input [2:0] a, input [3:0] c, - output reg [7:0] b + output reg [7:0] o1, + output reg [7:0] o2 ); - integer i; + integer i; - always @ (*) - begin - case(a) - {3'b000}: b = 8'd1; - {3'b001}: - for(i=0;i<4;i=i+1) b[i*2+:2] = 2'(c[i]); - {3'b010}: b = 8'd3; - {3'b011}: b = 8'd4; - default : b = 0; - endcase - end + always @ (*) begin + case(a) + {3'b000}: o1 = 8'd1; + {3'b001}: + for(i=0;i<4;i=i+1) o1[i*2+:2] = 2'(c[i]); + {3'b010}: o1 = 8'd3; + {3'b011}: o1 = 8'd4; + default : o1 = 0; + endcase + end + + always_comb begin + unique if (a[0]) o2 = 1; + else if (a[1]) o2 = 2; + else o2 = 3; + end endmodule From 4154584c4bd27c53e97f281b6154cd84eb9b1cf0 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Fri, 21 Oct 2022 20:04:07 -0400 Subject: [PATCH 156/177] Commentary: Changes update --- Changes | 6 +- docs/guide/contributors.rst | 175 +++++++++++++++++++----------------- 2 files changed, 99 insertions(+), 82 deletions(-) diff --git a/Changes b/Changes index 528fc7bf7..14ab87065 100644 --- a/Changes +++ b/Changes @@ -31,10 +31,14 @@ Verilator 5.001 devel **Minor:** +* Split UNUSED warning into genvar, param, and signal warnings (#3607). [Topa Topino] * Support standalone 'this' in classes (#2594) (#3248) (#3675). [Arkadiusz Kozdra, Antmicro Ltd] * Support tristate select/extend (#3604). [Ryszard Rozak, Antmicro Ltd> * Support linting for top module interfaces (#3635). [Kanad Kanhere] +* Support virtual interfaces (#3654). [Arkadiusz Kozdra, Antmicro Ltd] +* Support class type params without defaults (#3693). [Krzysztof Bieganski, Antmicro Ltd] * Support empty generate_regions (#3695). [mpb27] +* Support access to constructs inside type parameters (#3702). [Arkadiusz Kozdra, Antmicro Ltd] * Add --dump-tree-dot to enable dumping Ast Tree .dot files (#3636). [Marcel Chang] * Add --get-supported to determine what features are in Verilator. * Add error on real edge event control. @@ -45,7 +49,7 @@ Verilator 5.001 devel * Fix foreach and pre/post increment in functions (#3613). [Nandu Raj] * Fix linker errors in user-facing timing functions (#3657). [Krzysztof Bieganski, Antmicro Ltd] * Fix null access on optimized-out fork statements (#3658). [Krzysztof Bieganski, Antmicro Ltd] - +* Fix VPI inline module naming mismatch (#3690) (#3694). [Jiuyang Liu] Verilator 4.228 2022-10-01 ========================== diff --git a/docs/guide/contributors.rst b/docs/guide/contributors.rst index f4d3f4956..290619929 100644 --- a/docs/guide/contributors.rst +++ b/docs/guide/contributors.rst @@ -23,7 +23,8 @@ Contributors Many people have provided ideas and other assistance with Verilator. Verilator is receiving major development support from the `CHIPS Alliance -`_ and `Shunyao CAD `_. +`_, `Antmicro Ltd `_ and +`Shunyao CAD `_. Previous major corporate sponsors of Verilator, by providing significant contributions of time or funds included include: Atmel Corporation, Cavium @@ -32,90 +33,101 @@ Hicamp Systems, Intel Corporation, Mindspeed Technologies Inc., MicroTune Inc., picoChip Designs Ltd., Sun Microsystems Inc., Nauticus Networks Inc., SiCortex Inc, and Shunyao CAD. -The people who have contributed major functionality are: Byron Bradley, -Jeremy Bennett, Lane Brooks, John Coiner, Duane Galbi, Geza Lore, Todd -Strader, Stefan Wallentowitz, Paul Wasson, Jie Xu, and Wilson Snyder. -Major testers included Jeff Dutton, Jonathon Donaldson, Ralf Karge, David -Hewson, Iztok Jeras, Wim Michiels, Alex Solomatnikov, Sebastien Van -Cauwenberghe, Gene Weber, and Clifford Wolf. +The people who have contributed major functionality are: Krzysztof +Bieganski, Byron Bradley, Jeremy Bennett, Lane Brooks, John Coiner, Duane +Galbi, Geza Lore, Todd Strader, Stefan Wallentowitz, Paul Wasson, Jie Xu, +and Wilson Snyder. Major testers included Jeff Dutton, Jonathon Donaldson, +Ralf Karge, David Hewson, Iztok Jeras, Wim Michiels, Alex Solomatnikov, +Sebastien Van Cauwenberghe, Gene Weber, and Clifford Wolf. Some of the people who have provided ideas, and feedback for Verilator -include: David Addison, Nikana Anastasiadis, Vasu Arasanipalai, Jens Arm, -Tariq B. Ahmad, Sharad Bagri, Matthew Ballance, Andrew Bardsley, Matthew -Barr, Geoff Barrett, Kaleb Barrett, Julius Baxter, Jeremy Bennett, Michael -Berman, Jean Berniolles, Victor Besyakov, Moinak Bhattacharyya, Krzysztof -Bieganski, David Binderman, Piotr Binkowski, Johan Bjork, David Black, -Tymoteusz Blazejczyk, Daniel Bone, Morten Borup Petersen, Gregg Bouchard, -Christopher Boumenot, Nick Bowler, Byron Bradley, Bryan Brady, Charlie -Brej, J Briquet, Lane Brooks, John Brownlee, Jeff Bush, Lawrence Butcher, -Tony Bybell, Iru Cai, Ted Campbell, Chris Candler, Lauren Carlson, Donal -Casey, Alex Chadwick, Terry Chen, Yi-Chung Chen, Enzo Chi, Robert A. Clark, -Ryan Clarke, Allan Cochrane, John Coiner, Gianfranco Costamagna, Sean -Cross, George Cuan, Joe DErrico, Lukasz Dalek, Gunter Dannoritzer, Ashutosh -Das, Maarten De Braekeleer, Bernard Deadman, Alberto Del Rio, John Demme, -Mike Denio, John Deroo, Philip Derrick, John Dickol, Ruben Diez, Danny -Ding, Jacko Dirks, Ivan Djordjevic, Jonathon Donaldson, Sebastian Dressler, -Alex Duller, Jeff Dutton, Tomas Dzetkulic, Richard E George, Edgar -E. Iglesias, Usuario Eda, Charles Eddleston, Chandan Egbert, Jan Egil Ruud, -Joe Eiler, Ahmed El-Mahmoudy, Trevor Elbourne, Mats Engstrom, Charles Eric -LaForest, Robert Farrell, Eugen Fekete, Fabrizio Ferrandi, Udi Finkelstein, -Brian Flachs, Andrea Foletto, Bob Fredieu, Duane Galbi, Benjamin Gartner, -Christian Gelinek, Peter Gerst, Glen Gibb, Michael Gielda, Barbara Gigerl, -Shankar Giri, Dan Gisselquist, Petr Gladkikh, Sam Gladstone, Andrew -Goessling, Amir Gonnen, Chitlesh Goorah, Tomasz Gorochowik, Kai Gossner, -Sergi Granell, Al Grant, Alexander Grobman, Xuan Guo, Driss Hafdi, Neil +include: + +David Addison, Tariq B. Ahmad, Nikana Anastasiadis, Vasu Arasanipalai, Jens +Arm, Sharad Bagri, Matthew Ballance, Andrew Bardsley, Matthew Barr, Geoff +Barrett, Kaleb Barrett, Julius Baxter, Jeremy Bennett, Michael Berman, Jean +Berniolles, Victor Besyakov, Moinak Bhattacharyya, Krzysztof Bieganski, +David Binderman, Piotr Binkowski, Johan Bjork, David Black, Tymoteusz +Blazejczyk, Daniel Bone, Gregg Bouchard, Christopher Boumenot, Nick Bowler, +Byron Bradley, Bryan Brady, Maarten De Braekeleer, Charlie Brej, J Briquet, +Lane Brooks, John Brownlee, Jeff Bush, Lawrence Butcher, Tony Bybell, Iru +Cai, Ted Campbell, Chris Candler, Lauren Carlson, Donal Casey, Alex +Chadwick, Marcel Chang, Aliaksei Chapyzhenka, Guokai Chen, Terry Chen, +Yi-Chung Chen, Enzo Chi, Robert A. Clark, Ryan Clarke, Allan Cochrane, John +Coiner, Keith Colbert, Gianfranco Costamagna, Sean Cross, George Cuan, Joe +DErrico, Lukasz Dalek, Gunter Dannoritzer, Ashutosh Das, Bernard Deadman, +John Demme, Mike Denio, John Deroo, Philip Derrick, John Dickol, Ruben +Diez, Danny Ding, Jacko Dirks, Ivan Djordjevic, Jonathon Donaldson, Larry +Doolittle, Sebastian Dressler, Jonathan Drolet, Alex Duller, Jeff Dutton, +Tomas Dzetkulic, Usuario Eda, Charles Eddleston, Chandan Egbert, Joe Eiler, +Ahmed El-Mahmoudy, Trevor Elbourne, Mats Engstrom, Robert Farrell, Eugen +Fekete, Fabrizio Ferrandi, Udi Finkelstein, Brian Flachs, Bill Flynn, +Andrea Foletto, Bob Fredieu, Duane Galbi, Mostafa Gamal, Benjamin Gartner, +Christian Gelinek, Richard E George, Peter Gerst, Glen Gibb, Michael +Gielda, Barbara Gigerl, Shankar Giri, Dan Gisselquist, Petr Gladkikh, Sam +Gladstone, Mariusz Glebocki, Andrew Goessling, Amir Gonnen, Chitlesh +Goorah, Tomasz Gorochowik, Kai Gossner, Sergi Granell, Al Grant, Nathan +Graybeal, Alexander Grobman, Graham Rushton, Xuan Guo, Driss Hafdi, Neil Hamilton, James Hanlon, Oyvind Harboe, Jannis Harder, Junji Hashimoto, Thomas Hawkins, Mitch Hayenga, Harald Heckmann, Robert Henry, Stephen Henry, David Hewson, Jamey Hicks, Joel Holdsworth, Andrew Holme, Hiroki Honda, Alex Hornung, Pierre-Henri Horrein, David Horton, Peter Horvath, Jae -Hossell, Kuoping Hsu, Alan Hunter, James Hutchinson, Anderson Ignacio da -Silva, Jamie Iles, Thomas J Whatson, Ben Jackson, Mark Jackson Pulver, -Shareef Jalloq, Marlon James, Krzysztof Jankowski, HyungKi Jeong, Iztok -Jeras, James Johnson, Christophe Joly, Franck Jullien, James Jung, Mike -Kagen, Arthur Kahlich, Kaalia Kahn, Guy-Armand Kamendje, Vasu Kandadi, -Kanad Kanhere, Patricio Kaplan, Pieter Kapsenberg, Rafal Kapuscik, Ralf -Karge, Dan Katz, Sol Katzman, Ian Kennedy, Michael Killough, Jonathan -Kimmitt, Olof Kindgren, Kevin Kiningham, Dan Kirkham, Sobhan Klnv, Gernot -Koch, Jack Koenig, Soon Koh, Nathan Kohagen, Steve Kolecki, Brett Koonce, -Will Korteland, Wojciech Koszek, Varun Koyyalagunta, Markus Krause, David -Kravitz, Roland Kruse, Andreas Kuster, Sergey Kvachonok, Ed Lander, Steve -Lang, Stephane Laurent, Walter Lavino, Christian Leber, Larry Lee, Igor -Lesik, John Li, Eivind Liland, Charlie Lind, Andrew Ling, Jiuyang Liu, Paul -Liu, Derek Lockhart, Jake Longo, Geza Lore, Arthur Low, Stefan Ludwig, Dan -Lussier, Fred Ma, Duraid Madina, Odd Magne Reitan, Affe Mao, Julien -Margetts, Mark Marshall, Alfonso Martinez, Unai Martinez-Corral, Yves -Mathieu, Patrick Maupin, Conor McCullough, Jason McMullan, Elliot Mednick, -Wim Michiels, Miodrag Milanovic, Peter Monsson, Sean Moore, Dennis -Muhlestein, John Murphy, Matt Myers, Nathan Myers, Richard Myers, Dimitris -Nalbantis, Peter Nelson, Bob Newgard, Paul Nitza, Yossi Nivin, Pete Nixon, -Lisa Noack, Mark Nodine, Kuba Ober, Andreas Olofsson, Baltazar Ortiz, -Aleksander Osman, Don Owen, James Pallister, Vassilis Papaefstathiou, Brad -Parker, Dan Petrisko, Maciej Piechotka, David Pierce, Cody Piersall, -Dominic Plunkett, David Poole, Mike Popoloski, Roman Popov, Rich Porter, -Niranjan Prabhu, Usha Priyadharshini, Prateek Puri, Marshal Qiao, Nandu -Raj, Danilo Ramos, Chris Randall, Anton Rapp, Josh Redford, Frederic -Requin, Dustin Richmond, Samuel Riedel, Eric Rippey, Oleg Rodionov, Ludwig -Rogiers, Paul Rolfe, Arjen Roodselaar, Tobias Rosenkranz, Huang Rui, Denis -Rystsov, John Sanguinetti, Galen Seitz, Joseph Shaker, Salman Sheikh, Yu -Sheng Lin, Hao Shi, Mike Shinkarovsky, Rafael Shirakawa, Jeffrey Short, Fan -Shupei, Rodney Sinclair, Steven Slatter, Brian Small, Garrett Smith, Tim -Snyder, Wilson Snyder, Maciej Sobkowski, Stan Sokorac, Alex Solomatnikov, -Wei Song, Art Stamness, David Stanford, John Stevenson, Pete Stevenson, -Patrick Stewart, Rob Stoddard, Todd Strader, John Stroebel, Sven Stucki, -Howard Su, Emerson Suguimoto, Gene Sullivan, Wai Sum Mong, Qingyao Sun, -Renga Sundararajan, Rupert Swarbrick, Yutetsu Takatsukasa, Thierry Tambe, -Drew Taussig, Peter Tengstrand, Wesley Terpstra, Rui Terra, Stefan Thiede, -Gary Thomas, Ian Thompson, Kevin Thompson, Mike Thyer, Hans Tichelaar, -Viktor Tomov, Steve Tong, Alex Torregrosa, Michael Tresidder, David Turner, -Neil Turton, Cong Van Nguyen, Hans Van Antwerpen, Jan Van Winkel, Sebastien -Van Cauwenberghe, Laurens van Dam, Leendert van Doorn, Srini Vemuri, Yuri +Hossell, Kuoping Hsu, Teng Huang, Steven Hugg, Alan Hunter, James +Hutchinson, Ehab Ibrahim, Edgar E. Iglesias, Jamie Iles, Vighnesh Iyer, Ben +Jackson, Shareef Jalloq, Marlon James, Krzysztof Jankowski, HyungKi Jeong, +Iztok Jeras, Alexandre Joannou, James Johnson, Christophe Joly, Franck +Jullien, James Jung, Mike Kagen, Arthur Kahlich, Kaalia Kahn, Guy-Armand +Kamendje, Vasu Kandadi, Kanad Kanhere, Patricio Kaplan, Pieter Kapsenberg, +Rafal Kapuscik, Ralf Karge, Per Karlsson, Dan Katz, Sol Katzman, Ian +Kennedy, Michael Killough, Sun Kim, Jonathan Kimmitt, Olof Kindgren, Kevin +Kiningham, Dan Kirkham, Aleksander Kiryk, Sobhan Klnv, Gernot Koch, Jack +Koenig, Soon Koh, Nathan Kohagen, Steve Kolecki, Brett Koonce, Will +Korteland, Wojciech Koszek, Varun Koyyalagunta, Arkadiusz Kozdra, Markus +Krause, David Kravitz, Roland Kruse, Andreas Kuster, Sergey Kvachonok, +Charles Eric LaForest, Ed Lander, Steve Lang, Stephane Laurent, Walter +Lavino, Christian Leber, Larry Lee, Yoda Lee, Michaël Lefebvre, Igor Lesik, +John Li, Eivind Liland, Yu Sheng Lin, Charlie Lind, Andrew Ling, Jiuyang +Liu, Paul Liu, Derek Lockhart, Jake Longo, Geza Lore, Arthur Low, Stefan +Ludwig, Dan Lussier, Fred Ma, Duraid Madina, Affe Mao, Julien Margetts, +Mark Marshall, Alfonso Martinez, Unai Martinez-Corral, Adrien Le Masle, +Yves Mathieu, Patrick Maupin, Conor McCullough, Jason McMullan, Elliot +Mednick, David Metz, Wim Michiels, Miodrag Milanovic, Kevin Millis, Wai Sum +Mong, Peter Monsson, Sean Moore, Dennis Muhlestein, John Murphy, Matt +Myers, Nathan Myers, Richard Myers, Dimitris Nalbantis, Peter Nelson, Bob +Newgard, Rachit Nigam, Paul Nitza, Yossi Nivin, Pete Nixon, Lisa Noack, +Mark Nodine, Kuba Ober, Andreas Olofsson, Baltazar Ortiz, Aleksander Osman, +Don Owen, James Pallister, Vassilis Papaefstathiou, Brad Parker, Morten +Borup Petersen, Dan Petrisko, Maciej Piechotka, David Pierce, Cody +Piersall, Michael Platzer, Dominic Plunkett, David Poole, Mike Popoloski, +Roman Popov, Rich Porter, Stefan Post, Niranjan Prabhu, Damien Pretet, Usha +Priyadharshini, Mark Jackson Pulver, Prateek Puri, Marshal Qiao, Nandu Raj, +Kamil Rakoczy, Danilo Ramos, Drew Ranck, Chris Randall, Anton Rapp, Josh +Redford, Odd Magne Reitan, Frederic Requin, Dustin Richmond, Samuel Riedel, +Alberto Del Rio, Eric Rippey, Oleg Rodionov, Ludwig Rogiers, Paul Rolfe, +Arjen Roodselaar, Tobias Rosenkranz, Ryszard Rozak, Huang Rui, Graham +Rushton, Jan Egil Ruud, Denis Rystsov, John Sanguinetti, Martin Schmidt, +Julie Schwartz, Galen Seitz, Joseph Shaker, Salman Sheikh, Hao Shi, Mike +Shinkarovsky, Rafael Shirakawa, Jeffrey Short, Fan Shupei, Anderson Ignacio +da Silva, Rodney Sinclair, Ameya Vikram Singh, Steven Slatter, Mladen +Slijepcevic, Brian Small, Garrett Smith, Tim Snyder, Wilson Snyder, Maciej +Sobkowski, Stan Sokorac, Alex Solomatnikov, Flavien Solt, Wei Song, Trefor +Southwell, Martin Stadler, Art Stamness, David Stanford, John Stevenson, +Pete Stevenson, Patrick Stewart, Rob Stoddard, Todd Strader, John Stroebel, +Sven Stucki, Howard Su, Emerson Suguimoto, Gene Sullivan, Qingyao Sun, +Renga Sundararajan, Gustav Svensk, Rupert Swarbrick, Yutetsu Takatsukasa, +Thierry Tambe, Drew Taussig, Jose Tejada, Peter Tengstrand, Wesley +Terpstra, Rui Terra, Stefan Thiede, Gary Thomas, Ian Thompson, Kevin +Thompson, Mike Thyer, Hans Tichelaar, Viktor Tomov, Steve Tong, Topa +Topino, Alex Torregrosa, Michael Tresidder, David Turner, Neil Turton, Mike +Urbach, Hans Van Antwerpen, Sebastien Van Cauwenberghe, Laurens van Dam, +Leendert van Doorn, Cong Van Nguyen, Jan Van Winkel, Srini Vemuri, Yuri Victorovich, Bogdan Vukobratovic, Holger Waechtler, Philipp Wagner, Stefan -Wallentowitz, Shawn Wang, Paul Wasson, Greg Waters, Thomas Watts, Eugene -Weber, David Welch, Martin Whitaker, Marco Widmer, Leon Wildman, Daniel -Wilkerson, Gerald Williams, Trevor Williams, Jeff Winston, Joshua Wise, -Clifford Wolf, Tobias Wolfel, Johan Wouters, Paul Wright, Junyi Xi, Ding -Xiaoliang, Jie Xu, Mandy Xu, Yinan Xu, Luke Yang, Amir Yazdanbakhsh, and -Keyi Zhang. +Wallentowitz, Shawn Wang, Zhanglei Wang, Paul Wasson, Greg Waters, Thomas +Watts, Eugene Weber, David Welch, Thomas J Whatson, Martin Whitaker, Marco +Widmer, Leon Wildman, Daniel Wilkerson, Gerald Williams, Trevor Williams, +Jeff Winston, Joshua Wise, Clifford Wolf, Tobias Wolfel, Johan Wouters, +Paul Wright, Junyi Xi, Ding Xiaoliang, Jie Xu, Mandy Xu, Yinan Xu, Luke +Yang, Amir Yazdanbakhsh, Keyi Zhang, and Xi Zhang. Thanks to them, and all those we've missed including above, or wished to remain anonymous. @@ -152,8 +164,9 @@ In 2019, Verilator joined the `CHIPS Alliance `_. In 2022, Verilator 5.000 was released with IEEE scheduling semantics, -fork/join, delay handling, and other improvements. +fork/join, delay handling, DFG performance optimizations, and other +improvements. Currently, various language features and performance enhancements are added -as the need arises. Verilator is now about 3x faster than in 2002, and is -faster than most (if not every) other simulator. +as the need arises, with a focus towards getting to full Universal +Verification Methodology (UVM, IEEE 1800.2-2017) support. From fcf0d03cd4cbf5017635d60e32159a816117d23d Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Sat, 22 Oct 2022 14:05:39 +0000 Subject: [PATCH 157/177] Dynamic triggers for non-static contexts (#3599) In non-static contexts like class objects or stack frames, the use of global trigger evaluation is not feasible. The concept of dynamic triggers allows for trigger evaluation in such cases. These triggers are simply local variables, and coroutines are themselves responsible for evaluating them. They await the global dynamic trigger scheduler object, which is responsible for resuming them during the trigger evaluation step in the 'act' eval region. Once the trigger is set, they await the dynamic trigger scheduler once again, and then get resumed during the resumption step in the 'act' eval region. Signed-off-by: Krzysztof Bieganski --- docs/internals.rst | 30 + include/verilated_timing.cpp | 48 + include/verilated_timing.h | 74 ++ src/V3Ast.h | 36 +- src/V3AstNodeDType.h | 3 + src/V3AstNodes.cpp | 2 + src/V3EmitCFunc.cpp | 2 + src/V3Sched.cpp | 37 +- src/V3Sched.h | 6 +- src/V3SchedTiming.cpp | 22 +- src/V3SenExprBuilder.h | 32 +- src/V3Timing.cpp | 186 +++- test_regress/t/t_mailbox_class.out | 17 - test_regress/t/t_mailbox_class.pl | 17 +- test_regress/t/t_semaphore_class.out | 6 - test_regress/t/t_semaphore_class.pl | 17 +- test_regress/t/t_timing_class.v | 81 +- test_regress/t/t_timing_class_unsup.out | 6 - test_regress/t/t_timing_class_unsup.v | 12 - test_regress/t/t_timing_debug2.out | 892 +++++++++--------- test_regress/t/t_timing_debug2.pl | 2 +- test_regress/t/t_timing_localevent_unsup.out | 6 + ..._unsup.pl => t_timing_localevent_unsup.pl} | 0 test_regress/t/t_timing_localevent_unsup.v | 24 + 24 files changed, 947 insertions(+), 611 deletions(-) delete mode 100644 test_regress/t/t_mailbox_class.out delete mode 100644 test_regress/t/t_semaphore_class.out delete mode 100644 test_regress/t/t_timing_class_unsup.out delete mode 100644 test_regress/t/t_timing_class_unsup.v create mode 100644 test_regress/t/t_timing_localevent_unsup.out rename test_regress/t/{t_timing_class_unsup.pl => t_timing_localevent_unsup.pl} (100%) create mode 100644 test_regress/t/t_timing_localevent_unsup.v diff --git a/docs/internals.rst b/docs/internals.rst index c2031c857..a3f70ea36 100644 --- a/docs/internals.rst +++ b/docs/internals.rst @@ -588,6 +588,31 @@ This split is done to avoid self-triggering and triggering coroutines multiple times. See the `Scheduling with timing` section for details on how this is used. +``VlDynamicTriggerScheduler`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Like ``VlTriggerScheduler``, ``VlDynamicTriggerScheduler`` manages processes +that await triggers. However, it does not rely on triggers evaluated externally +by the 'act' trigger eval function. Instead, it is also responsible for trigger +evaluation. Coroutines that make use of this scheduler must adhere to a certain +procedure: + +:: + __Vtrigger = 0; + + while (!__Vtrigger) { + co_await __VdynSched.evaluation(); +
;
+      __Vtrigger = ;
+      [optionally] co_await __VdynSched.postUpdate();
+      ;
+  }
+  co_await __VdynSched.resumption();
+
+The coroutines get resumed at trigger evaluation time, evaluate their local
+triggers, optionally await the post update step, and if the trigger is set,
+await proper resumption in the 'act' eval step.
+
 ``VlForkSync``
 ^^^^^^^^^^^^^^
 
@@ -616,6 +641,11 @@ The visitor in ``V3Timing.cpp`` transforms each timing control into a ``co_await
   ``trigger`` method. The awaited trigger scheduler is the one corresponding to
   the sentree referenced by the event control. This sentree is also referenced
   by the ``AstCAwait`` node, to be used later by the static scheduling code.
+* if an event control waits on a local variable or class member, it uses a
+  local trigger which it evaluates inline. It awaits a dynamic trigger
+  scheduler multiple times: for trigger evaluation, updates, and resumption.
+  The dynamic trigger scheduler is responsible for resuming the coroutine at
+  the correct point of evaluation.
 * delays are turned into ``co_await`` on a delay scheduler's ``delay`` method.
   The created ``AstCAwait`` nodes also reference a special sentree related to
   delays, to be used later by the static scheduling code.
diff --git a/include/verilated_timing.cpp b/include/verilated_timing.cpp
index 7f996d7f7..f644ed64f 100644
--- a/include/verilated_timing.cpp
+++ b/include/verilated_timing.cpp
@@ -139,6 +139,54 @@ void VlTriggerScheduler::dump(const char* eventDescription) const {
 }
 #endif
 
+//======================================================================
+// VlDynamicTriggerScheduler:: Methods
+
+bool VlDynamicTriggerScheduler::evaluate() {
+    VL_DEBUG_IF(dump(););
+    std::swap(m_suspended, m_evaluated);
+    for (auto& coro : m_evaluated) coro.resume();
+    m_evaluated.clear();
+    return !m_triggered.empty();
+}
+
+void VlDynamicTriggerScheduler::doPostUpdates() {
+    VL_DEBUG_IF(if (!m_post.empty())
+                    VL_DBG_MSGF("         Doing post updates for processes:\n");  //
+                for (const auto& susp
+                     : m_post) {
+                    VL_DBG_MSGF("           - ");
+                    susp.dump();
+                });
+    for (auto& coro : m_post) coro.resume();
+    m_post.clear();
+}
+
+void VlDynamicTriggerScheduler::resume() {
+    VL_DEBUG_IF(if (!m_triggered.empty()) VL_DBG_MSGF("         Resuming processes:\n");  //
+                for (const auto& susp
+                     : m_triggered) {
+                    VL_DBG_MSGF("           - ");
+                    susp.dump();
+                });
+    for (auto& coro : m_triggered) coro.resume();
+    m_triggered.clear();
+}
+
+#ifdef VL_DEBUG
+void VlDynamicTriggerScheduler::dump() const {
+    if (m_suspended.empty()) {
+        VL_DBG_MSGF("         No suspended processes waiting for dynamic trigger evaluation\n");
+    } else {
+        for (const auto& susp : m_suspended) {
+            VL_DBG_MSGF("         Suspended processes waiting for dynamic trigger evaluation:\n");
+            VL_DBG_MSGF("           - ");
+            susp.dump();
+        }
+    }
+}
+#endif
+
 //======================================================================
 // VlForkSync:: Methods
 
diff --git a/include/verilated_timing.h b/include/verilated_timing.h
index 79eb1c0dd..f39d88ed6 100644
--- a/include/verilated_timing.h
+++ b/include/verilated_timing.h
@@ -238,6 +238,80 @@ public:
     }
 };
 
+//=============================================================================
+// VlDynamicTriggerScheduler is used for cases where triggers cannot be statically referenced and
+// evaluated. Coroutines that make use of this scheduler must adhere to a certain procedure:
+//     __Vtrigger = 0;
+//     
+//     while (!__Vtrigger) {
+//         co_await __VdynSched.evaluation();
+//         
;
+//         __Vtrigger = ;
+//         [optionally] co_await __VdynSched.postUpdate();
+//         ;
+//     }
+//    co_await __VdynSched.resumption();
+// The coroutines get resumed at trigger evaluation time, evaluate their local triggers, optionally
+// await the post update step, and if the trigger is set, await proper resumption in the 'act' eval
+// step.
+
+class VlDynamicTriggerScheduler final {
+    // TYPES
+    using VlCoroutineVec = std::vector;
+
+    // MEMBERS
+    VlCoroutineVec m_suspended;  // Suspended coroutines awaiting trigger evaluation
+    VlCoroutineVec m_evaluated;  // Coroutines currently being evaluated (for evaluate())
+    VlCoroutineVec m_triggered;  // Coroutines whose triggers were set, and are awaiting resumption
+    VlCoroutineVec m_post;  // Coroutines awaiting the post update step (only relevant for triggers
+                            // with destructive post updates, e.g. named events)
+
+    // METHODS
+    auto awaitable(VlCoroutineVec& queue, const char* filename, int lineno) {
+        struct Awaitable {
+            VlCoroutineVec& suspended;  // Coros waiting on trigger
+            VlFileLineDebug fileline;
+
+            bool await_ready() const { return false; }  // Always suspend
+            void await_suspend(std::coroutine_handle<> coro) {
+                suspended.emplace_back(coro, fileline);
+            }
+            void await_resume() const {}
+        };
+        return Awaitable{queue, VlFileLineDebug{filename, lineno}};
+    }
+
+public:
+    // Evaluates all dynamic triggers (resumed coroutines that co_await evaluation())
+    bool evaluate();
+    // Runs post updates for all dynamic triggers (resumes coroutines that co_await postUpdate())
+    void doPostUpdates();
+    // Resumes all coroutines whose triggers are set (those that co_await resumption())
+    void resume();
+#ifdef VL_DEBUG
+    void dump() const;
+#endif
+    // Used by coroutines for co_awaiting trigger evaluation
+    auto evaluation(const char* eventDescription, const char* filename, int lineno) {
+        VL_DEBUG_IF(VL_DBG_MSGF("         Suspending process waiting for %s at %s:%d\n",
+                                eventDescription, filename, lineno););
+        return awaitable(m_suspended, filename, lineno);
+    }
+    // Used by coroutines for co_awaiting the trigger post update step
+    auto postUpdate(const char* eventDescription, const char* filename, int lineno) {
+        VL_DEBUG_IF(
+            VL_DBG_MSGF("         Process waiting for %s at %s:%d awaiting the post update step\n",
+                        eventDescription, filename, lineno););
+        return awaitable(m_post, filename, lineno);
+    }
+    // Used by coroutines for co_awaiting the resumption step (in 'act' eval)
+    auto resumption(const char* eventDescription, const char* filename, int lineno) {
+        VL_DEBUG_IF(VL_DBG_MSGF("         Process waiting for %s at %s:%d awaiting resumption\n",
+                                eventDescription, filename, lineno););
+        return awaitable(m_triggered, filename, lineno);
+    }
+};
+
 //=============================================================================
 // VlNow is a helper awaitable type that always suspends, and then immediately resumes a coroutine.
 // Allows forcing the move of coroutine locals to the heap.
diff --git a/src/V3Ast.h b/src/V3Ast.h
index ecac64da1..0253e20fe 100644
--- a/src/V3Ast.h
+++ b/src/V3Ast.h
@@ -460,6 +460,7 @@ public:
         TRIGGERVEC,
         DELAY_SCHEDULER,
         TRIGGER_SCHEDULER,
+        DYNAMIC_TRIGGER_SCHEDULER,
         FORK_SYNC,
         // Unsigned and two state; fundamental types
         UINT32,
@@ -490,6 +491,7 @@ public:
                                             "VlTriggerVec",
                                             "VlDelayScheduler",
                                             "VlTriggerScheduler",
+                                            "VlDynamicTriggerScheduler",
                                             "VlFork",
                                             "IData",
                                             "QData",
@@ -498,30 +500,12 @@ public:
         return names[m_e];
     }
     const char* dpiType() const {
-        static const char* const names[] = {"%E-unk",
-                                            "svBit",
-                                            "char",
-                                            "void*",
-                                            "char",
-                                            "int",
-                                            "%E-integer",
-                                            "svLogic",
-                                            "long long",
-                                            "double",
-                                            "short",
-                                            "%E-time",
-                                            "const char*",
-                                            "dpiScope",
-                                            "const char*",
-                                            "%E-mtaskstate",
-                                            "%E-triggervec",
-                                            "%E-dly-sched",
-                                            "%E-trig-sched",
-                                            "%E-fork",
-                                            "IData",
-                                            "QData",
-                                            "%E-logic-implct",
-                                            " MAX"};
+        static const char* const names[]
+            = {"%E-unk",        "svBit",         "char",         "void*",           "char",
+               "int",           "%E-integer",    "svLogic",      "long long",       "double",
+               "short",         "%E-time",       "const char*",  "dpiScope",        "const char*",
+               "%E-mtaskstate", "%E-triggervec", "%E-dly-sched", "%E-trig-sched",   "%E-dyn-sched",
+               "%E-fork",       "IData",         "QData",        "%E-logic-implct", " MAX"};
         return names[m_e];
     }
     static void selfTest() {
@@ -558,6 +542,7 @@ public:
         case TRIGGERVEC: return 0;  // opaque
         case DELAY_SCHEDULER: return 0;  // opaque
         case TRIGGER_SCHEDULER: return 0;  // opaque
+        case DYNAMIC_TRIGGER_SCHEDULER: return 0;  // opaque
         case FORK_SYNC: return 0;  // opaque
         case UINT32: return 32;
         case UINT64: return 64;
@@ -596,7 +581,8 @@ public:
     bool isOpaque() const VL_MT_SAFE {  // IE not a simple number we can bit optimize
         return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
                 || m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DELAY_SCHEDULER
-                || m_e == TRIGGER_SCHEDULER || m_e == FORK_SYNC || m_e == DOUBLE);
+                || m_e == TRIGGER_SCHEDULER || m_e == DYNAMIC_TRIGGER_SCHEDULER || m_e == FORK_SYNC
+                || m_e == DOUBLE);
     }
     bool isDouble() const VL_MT_SAFE { return m_e == DOUBLE; }
     bool isEvent() const { return m_e == EVENT; }
diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h
index f8547b265..010a78d20 100644
--- a/src/V3AstNodeDType.h
+++ b/src/V3AstNodeDType.h
@@ -440,6 +440,9 @@ public:
     bool isTriggerScheduler() const VL_MT_SAFE {
         return keyword() == VBasicDTypeKwd::TRIGGER_SCHEDULER;
     }
+    bool isDynamicTriggerScheduler() const VL_MT_SAFE {
+        return keyword() == VBasicDTypeKwd::DYNAMIC_TRIGGER_SCHEDULER;
+    }
     bool isOpaque() const VL_MT_SAFE { return keyword().isOpaque(); }
     bool isString() const VL_MT_SAFE { return keyword().isString(); }
     bool isZeroInit() const { return keyword().isZeroInit(); }
diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp
index bb0ca080e..92e0b2fce 100644
--- a/src/V3AstNodes.cpp
+++ b/src/V3AstNodes.cpp
@@ -754,6 +754,8 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const {
             info.m_type = "VlDelayScheduler";
         } else if (bdtypep->isTriggerScheduler()) {
             info.m_type = "VlTriggerScheduler";
+        } else if (bdtypep->isDynamicTriggerScheduler()) {
+            info.m_type = "VlDynamicTriggerScheduler";
         } else if (bdtypep->isForkSync()) {
             info.m_type = "VlForkSync";
         } else if (bdtypep->isEvent()) {
diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp
index 7ec97f234..f81998976 100644
--- a/src/V3EmitCFunc.cpp
+++ b/src/V3EmitCFunc.cpp
@@ -694,6 +694,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP
         return "";
     } else if (basicp && basicp->isTriggerScheduler()) {
         return "";
+    } else if (basicp && basicp->isDynamicTriggerScheduler()) {
+        return "";
     } else if (basicp) {
         const bool zeroit
             = (varp->attrFileDescr()  // Zero so we don't do file IO if never $fopen
diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp
index bb48fa171..36a23771f 100644
--- a/src/V3Sched.cpp
+++ b/src/V3Sched.cpp
@@ -363,7 +363,8 @@ public:
 //============================================================================
 // Create a TRIGGERVEC and the related TriggerKit for the given AstSenTree vector
 
-const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
+const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
+                                SenExprBuilder& senExprBuilder,
                                 const std::vector& senTreeps,
                                 const string& name, const ExtraTriggers& extraTriggers,
                                 bool slow = false) {
@@ -463,7 +464,10 @@ const TriggerKit createTriggers(AstNetlist* netlistp, SenExprBuilder& senExprBui
         //
         ++triggerNumber;
     }
-    // Add the update statements
+    // Add the init and update statements
+    for (AstNodeStmt* const nodep : senExprBuilder.getAndClearInits()) {
+        initFuncp->addStmtsp(nodep);
+    }
     for (AstNodeStmt* const nodep : senExprBuilder.getAndClearPostUpdates()) {
         funcp->addStmtsp(nodep);
     }
@@ -604,7 +608,7 @@ std::pair makeEvalLoop(AstNetlist* netlistp, const strin
 //============================================================================
 // Order the combinational logic to create the settle loop
 
-void createSettle(AstNetlist* netlistp, SenExprBuilder& senExprBulider,
+void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilder& senExprBulider,
                   LogicClasses& logicClasses) {
     AstCFunc* const funcp = makeTopFunction(netlistp, "_eval_settle", true);
 
@@ -622,8 +626,8 @@ void createSettle(AstNetlist* netlistp, SenExprBuilder& senExprBulider,
 
     // Gather the relevant sensitivity expressions and create the trigger kit
     const auto& senTreeps = getSenTreesUsedBy({&comb, &hybrid});
-    const TriggerKit& trig
-        = createTriggers(netlistp, senExprBulider, senTreeps, "stl", extraTriggers, true);
+    const TriggerKit& trig = createTriggers(netlistp, initFuncp, senExprBulider, senTreeps, "stl",
+                                            extraTriggers, true);
 
     // Remap sensitivities (comb has none, so only do the hybrid)
     remapSensitivities(hybrid, trig.m_map);
@@ -662,8 +666,8 @@ void createSettle(AstNetlist* netlistp, SenExprBuilder& senExprBulider,
 //============================================================================
 // Order the replicated combinational logic to create the 'ico' region
 
-AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilder,
-                             LogicByScope& logic) {
+AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
+                             SenExprBuilder& senExprBuilder, LogicByScope& logic) {
     // Nothing to do if no combinational logic is sensitive to top level inputs
     if (logic.empty()) return nullptr;
 
@@ -693,7 +697,7 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, SenExprBuilder& senExprBuilde
     // Gather the relevant sensitivity expressions and create the trigger kit
     const auto& senTreeps = getSenTreesUsedBy({&logic});
     const TriggerKit& trig
-        = createTriggers(netlistp, senExprBuilder, senTreeps, "ico", extraTriggers);
+        = createTriggers(netlistp, initFuncp, senExprBuilder, senTreeps, "ico", extraTriggers);
 
     if (dpiExportTriggerVscp) {
         trig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
@@ -904,10 +908,12 @@ void schedule(AstNetlist* netlistp) {
 
     // We pass around a single SenExprBuilder instance, as we only need one set of 'prev' variables
     // for edge/change detection in sensitivity expressions, which this keeps track of.
-    SenExprBuilder senExprBuilder{netlistp, initp};
+    AstTopScope* const topScopep = netlistp->topScopep();
+    AstScope* const scopeTopp = topScopep->scopep();
+    SenExprBuilder senExprBuilder{scopeTopp};
 
     // Step 4: Create 'settle' region that restores the combinational invariant
-    createSettle(netlistp, senExprBuilder, logicClasses);
+    createSettle(netlistp, initp, senExprBuilder, logicClasses);
     if (v3Global.opt.stats()) V3Stats::statsStage("sched-settle");
 
     // Step 5: Partition the clocked and combinational (including hybrid) logic into pre/act/nba.
@@ -932,7 +938,8 @@ void schedule(AstNetlist* netlistp) {
     }
 
     // Step 7: Create input combinational logic loop
-    AstNode* const icoLoopp = createInputCombLoop(netlistp, senExprBuilder, logicReplicas.m_ico);
+    AstNode* const icoLoopp
+        = createInputCombLoop(netlistp, initp, senExprBuilder, logicReplicas.m_ico);
     if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-ico");
 
     // Step 8: Create the pre/act/nba triggers
@@ -949,15 +956,15 @@ void schedule(AstNetlist* netlistp) {
                                                &logicRegions.m_nba,  //
                                                &timingKit.m_lbs});
     const TriggerKit& actTrig
-        = createTriggers(netlistp, senExprBuilder, senTreeps, "act", extraTriggers);
+        = createTriggers(netlistp, initp, senExprBuilder, senTreeps, "act", extraTriggers);
+
+    // Add post updates from the timing kit
+    if (timingKit.m_postUpdates) actTrig.m_funcp->addStmtsp(timingKit.m_postUpdates);
 
     if (dpiExportTriggerVscp) {
         actTrig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex);
     }
 
-    AstTopScope* const topScopep = netlistp->topScopep();
-    AstScope* const scopeTopp = topScopep->scopep();
-
     AstVarScope* const actTrigVscp = actTrig.m_vscp;
     AstVarScope* const preTrigVscp = scopeTopp->createTempLike("__VpreTriggered", actTrigVscp);
     AstVarScope* const nbaTrigVscp = scopeTopp->createTempLike("__VnbaTriggered", actTrigVscp);
diff --git a/src/V3Sched.h b/src/V3Sched.h
index 9846ada1d..efde069af 100644
--- a/src/V3Sched.h
+++ b/src/V3Sched.h
@@ -125,6 +125,7 @@ class TimingKit final {
 
 public:
     LogicByScope m_lbs;  // Actives that resume timing schedulers
+    AstNodeStmt* m_postUpdates = nullptr;  // Post updates for the trigger eval function
 
     // Remaps external domains using the specified trigger map
     std::map>
@@ -135,10 +136,11 @@ public:
     AstCCall* createCommit(AstNetlist* const netlistp);
 
     TimingKit() = default;
-    TimingKit(LogicByScope&& lbs,
+    TimingKit(LogicByScope&& lbs, AstNodeStmt* postUpdates,
               std::map>&& externalDomains)
         : m_externalDomains{externalDomains}
-        , m_lbs{lbs} {}
+        , m_lbs{lbs}
+        , m_postUpdates{postUpdates} {}
     VL_UNCOPYABLE(TimingKit);
     TimingKit(TimingKit&&) = default;
     TimingKit& operator=(TimingKit&&) = default;
diff --git a/src/V3SchedTiming.cpp b/src/V3SchedTiming.cpp
index ac208122a..de8845b43 100644
--- a/src/V3SchedTiming.cpp
+++ b/src/V3SchedTiming.cpp
@@ -89,7 +89,8 @@ AstCCall* TimingKit::createCommit(AstNetlist* const netlistp) {
             UASSERT_OBJ(!resumep->nextp(), resumep, "Should be the only statement here");
             AstVarScope* const schedulerp = VN_AS(resumep->fromp(), VarRef)->varScopep();
             UASSERT_OBJ(schedulerp->dtypep()->basicp()->isDelayScheduler()
-                            || schedulerp->dtypep()->basicp()->isTriggerScheduler(),
+                            || schedulerp->dtypep()->basicp()->isTriggerScheduler()
+                            || schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler(),
                         schedulerp, "Unexpected type");
             if (!schedulerp->dtypep()->basicp()->isTriggerScheduler()) continue;
             // Create the global commit function only if we have trigger schedulers
@@ -143,6 +144,7 @@ TimingKit prepareTiming(AstNetlist* const netlistp) {
         bool m_gatherVars = false;  // Should we gather vars in m_writtenBySuspendable?
         AstScope* const m_scopeTopp;  // Scope at the top
         LogicByScope& m_lbs;  // Timing resume actives
+        AstNodeStmt*& m_postUpdatesr;  // Post updates for the trigger eval function
         // Additional var sensitivities
         std::map>& m_externalDomains;
         std::set m_processDomains;  // Sentrees from the current process
@@ -159,11 +161,15 @@ TimingKit prepareTiming(AstNetlist* const netlistp) {
             // Create a resume() call on the timing scheduler
             auto* const resumep = new AstCMethodHard{
                 flp, new AstVarRef{flp, schedulerp, VAccess::READWRITE}, "resume"};
-            if (schedulerp->dtypep()->basicp()->isTriggerScheduler() && methodp->pinsp()) {
-                resumep->addPinsp(methodp->pinsp()->cloneTree(false));
-            }
             resumep->statement(true);
             resumep->dtypeSetVoid();
+            if (schedulerp->dtypep()->basicp()->isTriggerScheduler()) {
+                if (methodp->pinsp()) resumep->addPinsp(methodp->pinsp()->cloneTree(false));
+            } else if (schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler()) {
+                auto* const postp = resumep->cloneTree(false);
+                postp->name("doPostUpdates");
+                m_postUpdatesr = AstNode::addNext(m_postUpdatesr, postp);
+            }
             // Put it in an active and put that in the global resume function
             auto* const activep = new AstActive{flp, "_timing", sensesp};
             activep->addStmtsp(resumep);
@@ -215,19 +221,21 @@ TimingKit prepareTiming(AstNetlist* const netlistp) {
 
     public:
         // CONSTRUCTORS
-        explicit AwaitVisitor(AstNetlist* nodep, LogicByScope& lbs,
+        explicit AwaitVisitor(AstNetlist* nodep, LogicByScope& lbs, AstNodeStmt*& postUpdatesr,
                               std::map>& externalDomains)
             : m_scopeTopp{nodep->topScopep()->scopep()}
             , m_lbs{lbs}
+            , m_postUpdatesr{postUpdatesr}
             , m_externalDomains{externalDomains} {
             iterate(nodep);
         }
         ~AwaitVisitor() override = default;
     };
     LogicByScope lbs;
+    AstNodeStmt* postUpdates = nullptr;
     std::map> externalDomains;
-    AwaitVisitor{netlistp, lbs, externalDomains};
-    return {std::move(lbs), std::move(externalDomains)};
+    AwaitVisitor{netlistp, lbs, postUpdates, externalDomains};
+    return {std::move(lbs), postUpdates, std::move(externalDomains)};
 }
 
 //============================================================================
diff --git a/src/V3SenExprBuilder.h b/src/V3SenExprBuilder.h
index c1e5ef57c..97b31e5b2 100644
--- a/src/V3SenExprBuilder.h
+++ b/src/V3SenExprBuilder.h
@@ -29,10 +29,10 @@
 
 class SenExprBuilder final {
     // STATE
-    AstCFunc* const m_initp;  // The initialization function
-    AstScope* const m_scopeTopp;  // Top level scope
+    AstScope* const m_scopep;  // The scope
 
     std::vector m_locals;  // Trigger eval local variables
+    std::vector m_inits;  // Initialization statements for prevoius values
     std::vector m_preUpdates;  // Pre update assignments
     std::vector m_postUpdates;  // Post update assignments
 
@@ -76,8 +76,8 @@ class SenExprBuilder final {
                 = new AstVar{flp, VVarType::BLOCKTEMP, m_currNames.get(exprp), exprp->dtypep()};
             varp->funcLocal(true);
             m_locals.push_back(varp);
-            AstVarScope* vscp = new AstVarScope{flp, m_scopeTopp, varp};
-            m_scopeTopp->addVarsp(vscp);
+            AstVarScope* vscp = new AstVarScope{flp, m_scopep, varp};
+            m_scopep->addVarsp(vscp);
             result.first->second = vscp;
         }
         AstVarScope* const currp = result.first->second;
@@ -106,13 +106,23 @@ class SenExprBuilder final {
                 name = m_prevNames.get(exprp);
             }
 
-            AstVarScope* const prevp = m_scopeTopp->createTemp(name, exprp->dtypep());
+            AstVarScope* prevp;
+            if (m_scopep->isTop()) {
+                prevp = m_scopep->createTemp(name, exprp->dtypep());
+            } else {
+                AstVar* const varp = new AstVar{flp, VVarType::BLOCKTEMP, m_prevNames.get(exprp),
+                                                exprp->dtypep()};
+                varp->funcLocal(true);
+                m_locals.push_back(varp);
+                prevp = new AstVarScope{flp, m_scopep, varp};
+                m_scopep->addVarsp(prevp);
+            }
             it = m_prev.emplace(*exprp, prevp).first;
 
             // Add the initializer init
-            AstNode* const initp = exprp->cloneTree(false);
-            m_initp->addStmtsp(
-                new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE}, initp});
+            AstAssign* const initp = new AstAssign{flp, new AstVarRef{flp, prevp, VAccess::WRITE},
+                                                   exprp->cloneTree(false)};
+            m_inits.push_back(initp);
         }
 
         AstVarScope* const prevp = it->second;
@@ -222,6 +232,7 @@ public:
         return {resultp, firedAtInitialization};
     }
 
+    std::vector getAndClearInits() { return std::move(m_inits); }
     std::vector getAndClearLocals() { return std::move(m_locals); }
 
     std::vector getAndClearPreUpdates() {
@@ -235,9 +246,8 @@ public:
     }
 
     // CONSTRUCTOR
-    SenExprBuilder(AstNetlist* netlistp, AstCFunc* initp)
-        : m_initp{initp}
-        , m_scopeTopp{netlistp->topScopep()->scopep()} {}
+    SenExprBuilder(AstScope* scopep)
+        : m_scopep{scopep} {}
 };
 
 #endif  // Guard
diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp
index 02cdee3e5..8f657e0b0 100644
--- a/src/V3Timing.cpp
+++ b/src/V3Timing.cpp
@@ -50,6 +50,7 @@
 #include "V3Const.h"
 #include "V3EmitV.h"
 #include "V3Graph.h"
+#include "V3SenExprBuilder.h"
 #include "V3SenTree.h"
 #include "V3UniqueNames.h"
 
@@ -114,6 +115,7 @@ private:
     V3UniqueNames m_intraLsbNames{"__Vintralsb"};  // Intra assign delay LSB var names
     V3UniqueNames m_forkNames{"__Vfork"};  // Fork name generator
     V3UniqueNames m_trigSchedNames{"__VtrigSched"};  // Trigger scheduler name generator
+    V3UniqueNames m_dynTrigNames{"__VdynTrigger"};  // Dynamic trigger name generator
 
     // DTypes
     AstBasicDType* m_forkDtp = nullptr;  // Fork variable type
@@ -121,7 +123,9 @@ private:
 
     // Timing-related globals
     AstVarScope* m_delaySchedp = nullptr;  // Global delay scheduler
+    AstVarScope* m_dynamicSchedp = nullptr;  // Global dynamic trigger scheduler
     AstSenTree* m_delaySensesp = nullptr;  // Domain to trigger if a delayed coroutine is resumed
+    AstSenTree* m_dynamicSensesp = nullptr;  // Domain to trigger if a dynamic trigger is set
 
     // Other
     V3Graph m_depGraph;  // Dependency graph where a node is a dependency of another if it being
@@ -228,6 +232,44 @@ private:
         m_netlistp->topScopep()->addSenTreesp(m_delaySensesp);
         return m_delaySensesp;
     }
+    // Creates the global dynamic trigger scheduler variable
+    AstVarScope* getCreateDynamicTriggerScheduler() {
+        if (m_dynamicSchedp) return m_dynamicSchedp;
+        auto* const dynSchedDtp
+            = new AstBasicDType{m_scopeTopp->fileline(), VBasicDTypeKwd::DYNAMIC_TRIGGER_SCHEDULER,
+                                VSigning::UNSIGNED};
+        m_netlistp->typeTablep()->addTypesp(dynSchedDtp);
+        m_dynamicSchedp = m_scopeTopp->createTemp("__VdynSched", dynSchedDtp);
+        return m_dynamicSchedp;
+    }
+    // Creates the dynamic trigger sentree
+    AstSenTree* getCreateDynamicTriggerSenTree() {
+        if (m_dynamicSensesp) return m_dynamicSensesp;
+        FileLine* const flp = m_scopeTopp->fileline();
+        auto* const awaitingCurrentTimep = new AstCMethodHard{
+            flp, new AstVarRef{flp, getCreateDynamicTriggerScheduler(), VAccess::READ},
+            "evaluate"};
+        awaitingCurrentTimep->dtypeSetBit();
+        m_dynamicSensesp
+            = new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE, awaitingCurrentTimep}};
+        m_netlistp->topScopep()->addSenTreesp(m_dynamicSensesp);
+        return m_dynamicSensesp;
+    }
+    // Returns true if we are under a class or the given tree has any references to locals. These
+    // are cases where static, globally-evaluated triggers are not suitable.
+    bool needDynamicTrigger(AstNode* const nodep) const {
+        return m_classp || nodep->exists([](const AstNodeVarRef* const refp) {
+            return refp->varp()->isFuncLocal();
+        });
+    }
+    // Returns true if the given trigger expression needs a destructive post update after trigger
+    // evaluation. Currently this only applies to named events.
+    bool destructivePostUpdate(AstNode* const exprp) const {
+        return exprp->exists([](const AstNodeVarRef* const refp) {
+            AstBasicDType* const dtypep = refp->dtypep()->basicp();
+            return dtypep && dtypep->isEvent();
+        });
+    }
     // Creates a trigger scheduler variable
     AstVarScope* getCreateTriggerSchedulerp(AstSenTree* const sensesp) {
         if (!sensesp->user1p()) {
@@ -483,26 +525,90 @@ private:
         VL_DO_DANGLING(nodep->deleteTree(), nodep);
     }
     void visit(AstEventControl* nodep) override {
-        if (m_classp) nodep->v3warn(E_UNSUPPORTED, "Unsupported: event controls in methods");
-        auto* const sensesp = m_finder.getSenTree(nodep->sensesp());
-        nodep->sensesp()->unlinkFrBack()->deleteTree();
-        // Get this sentree's trigger scheduler
-        FileLine* const flp = nodep->fileline();
-        // Replace self with a 'co_await trigSched.trigger()'
-        auto* const triggerMethodp = new AstCMethodHard{
-            flp, new AstVarRef{flp, getCreateTriggerSchedulerp(sensesp), VAccess::WRITE},
-            "trigger"};
-        triggerMethodp->dtypeSetVoid();
-        // Add debug info
-        addEventDebugInfo(triggerMethodp, sensesp);
-        // Create the co_await
-        auto* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp};
-        awaitp->statement(true);
-        // Relink child statements after the co_await
-        if (nodep->stmtsp()) {
-            AstNode::addNext(awaitp, nodep->stmtsp()->unlinkFrBackWithNext());
+        // Do not allow waiting on local named events, as they get enqueued for clearing, but can
+        // go out of scope before that happens
+        if (nodep->sensesp()->exists([](const AstNodeVarRef* refp) {
+                AstBasicDType* const dtypep = refp->dtypep()->skipRefp()->basicp();
+                return dtypep && dtypep->isEvent() && refp->varp()->isFuncLocal();
+            })) {
+            nodep->v3warn(E_UNSUPPORTED, "Unsupported: waiting on local event variables");
+        }
+        FileLine* const flp = nodep->fileline();
+        // Relink child statements after the event control
+        if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBackWithNext());
+        if (needDynamicTrigger(nodep->sensesp())) {
+            // Create the trigger variable and init it with 0
+            AstVarScope* const trigvscp
+                = createTemp(flp, m_dynTrigNames.get(nodep), nodep->findBitDType(), nodep);
+            auto* const initp = new AstAssign{flp, new AstVarRef{flp, trigvscp, VAccess::WRITE},
+                                              new AstConst{flp, AstConst::BitFalse{}}};
+            nodep->addHereThisAsNext(initp);
+            // Await the eval step with the dynamic trigger scheduler. First, create the method
+            // call
+            auto* const evalMethodp = new AstCMethodHard{
+                flp, new AstVarRef{flp, getCreateDynamicTriggerScheduler(), VAccess::WRITE},
+                "evaluation"};
+            evalMethodp->dtypeSetVoid();
+            auto* const sensesp = nodep->sensesp();
+            addEventDebugInfo(evalMethodp, sensesp);
+            // Create the co_await
+            auto* const awaitEvalp
+                = new AstCAwait{flp, evalMethodp, getCreateDynamicTriggerSenTree()};
+            awaitEvalp->statement(true);
+            // Construct the sen expression for this sentree
+            SenExprBuilder senExprBuilder{m_scopep};
+            auto* const assignp = new AstAssign{flp, new AstVarRef{flp, trigvscp, VAccess::WRITE},
+                                                senExprBuilder.build(sensesp).first};
+            // Put all the locals and inits before the trigger eval loop
+            for (AstVar* const varp : senExprBuilder.getAndClearLocals()) {
+                nodep->addHereThisAsNext(varp);
+            }
+            for (AstNodeStmt* const stmtp : senExprBuilder.getAndClearInits()) {
+                nodep->addHereThisAsNext(stmtp);
+            }
+            // Create the trigger eval loop, which will await the evaluation step and check the
+            // trigger
+            auto* const loopp = new AstWhile{
+                flp, new AstLogNot{flp, new AstVarRef{flp, trigvscp, VAccess::READ}}, awaitEvalp};
+            // Put pre updates before the trigger check and assignment
+            for (AstNodeStmt* const stmtp : senExprBuilder.getAndClearPreUpdates()) {
+                loopp->addStmtsp(stmtp);
+            }
+            // Then the trigger check and assignment
+            loopp->addStmtsp(assignp);
+            // If the post update is destructive (e.g. event vars are cleared), create an await for
+            // the post update step
+            if (destructivePostUpdate(sensesp)) {
+                auto* const awaitPostUpdatep = awaitEvalp->cloneTree(false);
+                VN_AS(awaitPostUpdatep->exprp(), CMethodHard)->name("postUpdate");
+                loopp->addStmtsp(awaitPostUpdatep);
+            }
+            // Put the post updates at the end of the loop
+            for (AstNodeStmt* const stmtp : senExprBuilder.getAndClearPostUpdates()) {
+                loopp->addStmtsp(stmtp);
+            }
+            // Finally, await the resumption step in 'act'
+            auto* const awaitResumep = awaitEvalp->cloneTree(false);
+            VN_AS(awaitResumep->exprp(), CMethodHard)->name("resumption");
+            AstNode::addNext(loopp, awaitResumep);
+            // Replace the event control with the loop
+            nodep->replaceWith(loopp);
+        } else {
+            auto* const sensesp = m_finder.getSenTree(nodep->sensesp());
+            nodep->sensesp()->unlinkFrBack()->deleteTree();
+            // Get this sentree's trigger scheduler
+            FileLine* const flp = nodep->fileline();
+            // Replace self with a 'co_await trigSched.trigger()'
+            auto* const triggerMethodp = new AstCMethodHard{
+                flp, new AstVarRef{flp, getCreateTriggerSchedulerp(sensesp), VAccess::WRITE},
+                "trigger"};
+            triggerMethodp->dtypeSetVoid();
+            addEventDebugInfo(triggerMethodp, sensesp);
+            // Create the co_await
+            auto* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp};
+            awaitp->statement(true);
+            nodep->replaceWith(awaitp);
         }
-        nodep->replaceWith(awaitp);
         VL_DO_DANGLING(nodep->deleteTree(), nodep);
     }
     void visit(AstNodeAssign* nodep) override {
@@ -584,22 +690,14 @@ private:
     }
     void visit(AstWait* nodep) override {
         // Wait on changed events related to the vars in the wait statement
-        AstSenItem* const senItemsp = varRefpsToSenItemsp(nodep->condp());
-        AstNode* const condp = nodep->condp()->unlinkFrBack();
+        FileLine* const flp = nodep->fileline();
         AstNode* const stmtsp = nodep->stmtsp();
         if (stmtsp) stmtsp->unlinkFrBackWithNext();
-        FileLine* const flp = nodep->fileline();
-        if (senItemsp) {
-            // Put the event control in a while loop with the wait expression as condition
-            AstNode* const loopp
-                = new AstWhile{flp, new AstLogNot{flp, condp},
-                               new AstEventControl{flp, new AstSenTree{flp, senItemsp}, nullptr}};
-            if (stmtsp) loopp->addNext(stmtsp);
-            nodep->replaceWith(loopp);
-        } else {
+        AstNode* const condp = V3Const::constifyEdit(nodep->condp()->unlinkFrBack());
+        auto* const constp = VN_CAST(condp, Const);
+        if (constp) {
             condp->v3warn(WAITCONST, "Wait statement condition is constant");
-            auto* constCondp = VN_AS(V3Const::constifyEdit(condp), Const);
-            if (constCondp->isZero()) {
+            if (constp->isZero()) {
                 // We have to await forever instead of simply returning in case we're deep in a
                 // callstack
                 auto* const awaitp = new AstCAwait{flp, new AstCStmt{flp, "VlForever{}"}};
@@ -607,10 +705,30 @@ private:
                 nodep->replaceWith(awaitp);
                 if (stmtsp) VL_DO_DANGLING(stmtsp->deleteTree(), stmtsp);
             } else if (stmtsp) {
-                // Just put the body there
+                // Just put the statements there
                 nodep->replaceWith(stmtsp);
             }
-            VL_DO_DANGLING(constCondp->deleteTree(), condp);
+            VL_DO_DANGLING(condp->deleteTree(), condp);
+        } else if (needDynamicTrigger(condp)) {
+            // No point in making a sentree, just use the expression as sensitivity
+            // Put the event control in an if so we only wait if the condition isn't met already
+            auto* const ifp = new AstIf{
+                flp, new AstLogNot{flp, condp},
+                new AstEventControl{flp,
+                                    new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_TRUE,
+                                                                       condp->cloneTree(false)}},
+                                    nullptr}};
+            if (stmtsp) AstNode::addNext(ifp, stmtsp);
+            nodep->replaceWith(ifp);
+        } else {
+            AstSenItem* const senItemsp = varRefpsToSenItemsp(condp);
+            UASSERT_OBJ(senItemsp, nodep, "No varrefs in wait statement condition");
+            // Put the event control in a while loop with the wait expression as condition
+            auto* const loopp
+                = new AstWhile{flp, new AstLogNot{flp, condp},
+                               new AstEventControl{flp, new AstSenTree{flp, senItemsp}, nullptr}};
+            if (stmtsp) AstNode::addNext(loopp, stmtsp);
+            nodep->replaceWith(loopp);
         }
         VL_DO_DANGLING(nodep->deleteTree(), nodep);
     }
diff --git a/test_regress/t/t_mailbox_class.out b/test_regress/t/t_mailbox_class.out
deleted file mode 100644
index da24a9ec0..000000000
--- a/test_regress/t/t_mailbox_class.out
+++ /dev/null
@@ -1,17 +0,0 @@
-%Error-UNSUPPORTED: t/t_mailbox_class.v:21:25: Unsupported: event controls in methods
-                                             : ... In instance $unit::mailbox_cls
-   21 |       if (m_bound != 0) wait (m_q.size() < m_bound);
-      |                         ^~~~
-                    ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
-%Error-UNSUPPORTED: t/t_mailbox_class.v:35:7: Unsupported: event controls in methods
-                                            : ... In instance $unit::mailbox_cls
-   35 |       wait (m_q.size() != 0);
-      |       ^~~~
-%Error-UNSUPPORTED: t/t_mailbox_class.v:49:7: Unsupported: event controls in methods
-                                            : ... In instance $unit::mailbox_cls
-   49 |       wait (m_q.size() != 0);
-      |       ^~~~
-%Error-UNSUPPORTED: t/t_mailbox_class.v:21:31: Unsupported: Cannot detect changes on expression of complex type (see combinational cycles reported by UNOPTFLAT)
-   21 |       if (m_bound != 0) wait (m_q.size() < m_bound);
-      |                               ^~~
-%Error: Exiting due to
diff --git a/test_regress/t/t_mailbox_class.pl b/test_regress/t/t_mailbox_class.pl
index 830c5f044..3329d516e 100755
--- a/test_regress/t/t_mailbox_class.pl
+++ b/test_regress/t/t_mailbox_class.pl
@@ -10,15 +10,14 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
 
 scenarios(simulator => 1);
 
-compile(
-    verilator_flags2 => ["--timing"],
-    fails => $Self->{vlt_all},
-    expect_filename => $Self->{golden_filename},
-    );
-
-execute(
-    check_finished => 1,
-    ) if !$Self->{vlt_all};
+if (!$Self->have_coroutines) {
+    skip("No coroutine support");
+}
+else {
+    compile(
+        verilator_flags2 => ["--timing"],
+        );
+}
 
 ok(1);
 1;
diff --git a/test_regress/t/t_semaphore_class.out b/test_regress/t/t_semaphore_class.out
deleted file mode 100644
index 54be71860..000000000
--- a/test_regress/t/t_semaphore_class.out
+++ /dev/null
@@ -1,6 +0,0 @@
-%Error-UNSUPPORTED: t/t_semaphore_class.v:17:7: Unsupported: event controls in methods
-                                              : ... In instance $unit::semaphore_cls
-   17 |       wait (m_keys >= keyCount);
-      |       ^~~~
-                    ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
-%Error: Exiting due to
diff --git a/test_regress/t/t_semaphore_class.pl b/test_regress/t/t_semaphore_class.pl
index 830c5f044..3329d516e 100755
--- a/test_regress/t/t_semaphore_class.pl
+++ b/test_regress/t/t_semaphore_class.pl
@@ -10,15 +10,14 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
 
 scenarios(simulator => 1);
 
-compile(
-    verilator_flags2 => ["--timing"],
-    fails => $Self->{vlt_all},
-    expect_filename => $Self->{golden_filename},
-    );
-
-execute(
-    check_finished => 1,
-    ) if !$Self->{vlt_all};
+if (!$Self->have_coroutines) {
+    skip("No coroutine support");
+}
+else {
+    compile(
+        verilator_flags2 => ["--timing"],
+        );
+}
 
 ok(1);
 1;
diff --git a/test_regress/t/t_timing_class.v b/test_regress/t/t_timing_class.v
index b79139153..263b2e44f 100644
--- a/test_regress/t/t_timing_class.v
+++ b/test_regress/t/t_timing_class.v
@@ -15,27 +15,95 @@ module t;
     // EVENTS
     class EventClass;
         event e;
+        int trig_count;
 
-        task sleep; /* @e; */ endtask  // Unsupported
-        task wake; ->e; endtask
+        function new;
+            trig_count = 0;
+        endfunction
+
+        task inc_trig_count;
+            trig_count++;
+        endtask;
+
+        task sleep;
+            @e inc_trig_count;
+            `WRITE_VERBOSE(("Event in class triggered at time %0t!\n", $time));
+        endtask
+
+        task wake;
+            ->e;
+        endtask
+    endclass
+
+    class WaitClass;
+        int a;
+        int b;
+        logic ok;
+
+        function new;
+            a = 0;
+            b = 0;
+            ok = 0;
+        endfunction
+
+        task await;
+            wait(a == 4 && b > 16) if (a != 4 || b <= 16) $stop;
+            ok = 1;
+            `WRITE_VERBOSE(("Condition in object met at time %0t!\n", $time));
+        endtask
+    endclass
+
+    class LocalWaitClass;
+        logic ok;
+
+        function new;
+            ok = 0;
+        endfunction
+
+        task await;
+            int a = 0;
+            int b = 100;
+            fork
+                wait(a == 42 || b != 100) if (a != 42 && b == 100) $stop;
+                #10 a = 42;
+            join
+            ok = 1;
+            `WRITE_VERBOSE(("Condition with local variables met at time %0t!\n", $time));
+        endtask
     endclass
 
     EventClass ec = new;
-    int event_trig_count = 0;
+    WaitClass wc = new;
+    LocalWaitClass lc = new;
 
     initial begin
         @ec.e;
         ec.sleep;
+        if (wc.ok) $stop;
+        wc.await;
+        if (lc.ok) $stop;
+        lc.await;
     end
 
-    initial #25 ec.wake;
-    initial #50 ->ec.e;
+    initial #20 ec.wake;
+    initial #40 ->ec.e;
+    initial begin
+        wc.a = #50 4;
+        wc.b = #10 32;
+    end
 
     always @ec.e begin
-        event_trig_count++;
+        ec.inc_trig_count;
         `WRITE_VERBOSE(("Event in class triggered at time %0t!\n", $time));
     end
 
+    initial begin
+        #80
+        if (ec.trig_count != 3) $stop;
+        if (!wc.ok) $stop;
+        if (!lc.ok) $stop;
+    end
+
     // =============================================
     // DELAYS
     virtual class DelayClass;
@@ -120,7 +188,6 @@ module t;
         fork #5 dAsgn.x = 0; join_none
         dAsgn.do_assign;
         if ($time != 80) $stop;
-        if (event_trig_count != 2) $stop;
         if (dAsgn.y != 1) $stop;
         // Test if the object is deleted before do_assign finishes:
         fork dAsgn.do_assign; join_none
diff --git a/test_regress/t/t_timing_class_unsup.out b/test_regress/t/t_timing_class_unsup.out
deleted file mode 100644
index 0cb96600b..000000000
--- a/test_regress/t/t_timing_class_unsup.out
+++ /dev/null
@@ -1,6 +0,0 @@
-%Error-UNSUPPORTED: t/t_timing_class_unsup.v:10:17: Unsupported: event controls in methods
-                                                  : ... In instance $unit::EventClass
-   10 |     task sleep; @e; endtask
-      |                 ^
-                    ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
-%Error: Exiting due to
diff --git a/test_regress/t/t_timing_class_unsup.v b/test_regress/t/t_timing_class_unsup.v
deleted file mode 100644
index 01795aa00..000000000
--- a/test_regress/t/t_timing_class_unsup.v
+++ /dev/null
@@ -1,12 +0,0 @@
-// DESCRIPTION: Verilator: Verilog Test module
-//
-// This file ONLY is placed under the Creative Commons Public Domain, for
-// any use, without warranty, 2022 by Antmicro Ltd.
-// SPDX-License-Identifier: CC0-1.0
-
-class EventClass;
-    event e;
-
-    task sleep; @e; endtask
-    task wake; ->e; endtask
-endclass
diff --git a/test_regress/t/t_timing_debug2.out b/test_regress/t/t_timing_debug2.out
index 4988bab73..6243cc26c 100644
--- a/test_regress/t/t_timing_debug2.out
+++ b/test_regress/t/t_timing_debug2.out
@@ -1,55 +1,109 @@
 -V{t#,#}- Verilated::debug is on. Message prefix indicates {,}.
 -V{t#,#}+    Vt_timing_debug2___024root___ctor_var_reset
+-V{t#,#}+      Vt_timing_debug2_t___ctor_var_reset
+-V{t#,#}+  Vt_timing_debug2_t__03a__03aAssignDelayClass__Vclpkg___ctor_var_reset
+-V{t#,#}+  Vt_timing_debug2_t__03a__03aDelay10__Vclpkg___ctor_var_reset
+-V{t#,#}+  Vt_timing_debug2_t__03a__03aDelay20__Vclpkg___ctor_var_reset
+-V{t#,#}+  Vt_timing_debug2_t__03a__03aDelay40__Vclpkg___ctor_var_reset
+-V{t#,#}+  Vt_timing_debug2_t__03a__03aDelayClass__Vclpkg___ctor_var_reset
+-V{t#,#}+  Vt_timing_debug2_t__03a__03aEventClass__Vclpkg___ctor_var_reset
+-V{t#,#}+  Vt_timing_debug2_t__03a__03aForkClass__Vclpkg___ctor_var_reset
+-V{t#,#}+  Vt_timing_debug2_t__03a__03aForkDelayClass__Vclpkg___ctor_var_reset
+-V{t#,#}+  Vt_timing_debug2_t__03a__03aLocalWaitClass__Vclpkg___ctor_var_reset
+-V{t#,#}+  Vt_timing_debug2_t__03a__03aNoDelay__Vclpkg___ctor_var_reset
+-V{t#,#}+  Vt_timing_debug2_t__03a__03aWaitClass__Vclpkg___ctor_var_reset
 -V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
 -V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
 -V{t#,#}+ Initial
 -V{t#,#}+    Vt_timing_debug2___024root___eval_static
+-V{t#,#}+      Vt_timing_debug2_t___eval_static__TOP__t
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aEventClass::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aEventClass::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aWaitClass::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aWaitClass::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aLocalWaitClass::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aLocalWaitClass::_ctor_var_reset
 -V{t#,#}+    Vt_timing_debug2___024root___eval_initial
--V{t#,#}+    Vt_timing_debug2___024root___eval_initial__TOP__0
--V{t#,#}             Process forked at t/t_timing_fork_join.v:14 finished
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__1
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__2
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__3
-[0] fork..join process 4
--V{t#,#}             Process forked at t/t_timing_fork_join.v:18 finished
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__5
--V{t#,#}             Awaiting join of fork at: t/t_timing_fork_join.v:13
--V{t#,#}+    Vt_timing_debug2___024root___eval_initial__TOP__1
--V{t#,#}+    Vt_timing_debug2___024root___eval_initial__TOP__2
--V{t#,#}             Awaiting join of fork at: t/t_timing_fork_join.v:83
+-V{t#,#}+      Vt_timing_debug2_t___eval_initial__TOP__t__0
+-V{t#,#}         Suspending process waiting for @([event] t.ec.e) at t/t_timing_class.v:80
+-V{t#,#}+      Vt_timing_debug2_t___eval_initial__TOP__t__1
+-V{t#,#}+      Vt_timing_debug2_t___eval_initial__TOP__t__2
+-V{t#,#}+      Vt_timing_debug2_t___eval_initial__TOP__t__3
+-V{t#,#}+      Vt_timing_debug2_t___eval_initial__TOP__t__4
+-V{t#,#}+      Vt_timing_debug2_t___eval_initial__TOP__t__5
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay10::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay10::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay20::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay20::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay40::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay40::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aNoDelay::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aNoDelay::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aAssignDelayClass::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aAssignDelayClass::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay10::__VnoInFunc_do_delay
+-V{t#,#}+      Vt_timing_debug2_t___eval_initial__TOP__t__6
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aForkClass::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aForkClass::_ctor_var_reset
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aForkClass::__VnoInFunc_do_fork
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aForkClass::__Vfork_h########__0__0
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aForkClass::__Vfork_h########__0__1
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aForkClass::__Vfork_h########__0__2
+-V{t#,#}             Awaiting join of fork at: t/t_timing_class.v:209
+-V{t#,#}+      Vt_timing_debug2_t___eval_initial__TOP__t__7
 -V{t#,#}+    Vt_timing_debug2___024root___eval_settle
 -V{t#,#}+ Eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}         Committing processes waiting for @([event] t.ec.e):
+-V{t#,#}           - Process waiting at t/t_timing_class.v:80
 -V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
 -V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
 -V{t#,#}+ Eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___timing_resume
 -V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 2: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 8: Process waiting at t/t_timing_fork_join.v:16
--V{t#,#}             Awaiting time 4: Process waiting at t/t_timing_fork_join.v:17
--V{t#,#}             Awaiting time 16: Process waiting at t/t_timing_fork_join.v:19
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:79
+-V{t#,#}             Awaiting time 10: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 20: Process waiting at t/t_timing_class.v:89
+-V{t#,#}             Awaiting time 10: Process waiting at t/t_timing_class.v:91
+-V{t#,#}             Awaiting time 30: Process waiting at t/t_timing_class.v:101
+-V{t#,#}             Awaiting time 40: Process waiting at t/t_timing_class.v:137
+-V{t#,#}             Awaiting time 50: Process waiting at t/t_timing_class.v:211
+-V{t#,#}             Awaiting time 20: Process waiting at t/t_timing_class.v:215
+-V{t#,#}             Awaiting time 80: Process waiting at t/t_timing_class.v:220
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:236
 -V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:79
-[2] fork..join process 3
--V{t#,#}             Process forked at t/t_timing_fork_join.v:17 finished
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:236
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay10::__VnoInFunc_do_sth_else
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay20::__VnoInFunc_do_delay
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:138
+-V{t#,#}             Process forked at t/t_timing_class.v:210 finished
 -V{t#,#}+    Vt_timing_debug2___024root___eval_act
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___eval_nba
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
@@ -58,26 +112,235 @@
 -V{t#,#}+ Eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___timing_resume
 -V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 4: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 8: Process waiting at t/t_timing_fork_join.v:16
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:17
--V{t#,#}             Awaiting time 16: Process waiting at t/t_timing_fork_join.v:19
+-V{t#,#}             Awaiting time 20: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 20: Process waiting at t/t_timing_class.v:89
+-V{t#,#}             Awaiting time 30: Process waiting at t/t_timing_class.v:91
+-V{t#,#}             Awaiting time 30: Process waiting at t/t_timing_class.v:101
+-V{t#,#}             Awaiting time 40: Process waiting at t/t_timing_class.v:137
+-V{t#,#}             Awaiting time 50: Process waiting at t/t_timing_class.v:211
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:215
+-V{t#,#}             Awaiting time 80: Process waiting at t/t_timing_class.v:220
 -V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:19
-[4] fork..join process 2
--V{t#,#}             Process forked at t/t_timing_fork_join.v:16 finished
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:220
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aForkDelayClass::new
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aForkDelayClass::_ctor_var_reset
+-V{t#,#}             Process forked at t/t_timing_class.v:214 finished
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:215
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aEventClass::__VnoInFunc_wake
 -V{t#,#}+    Vt_timing_debug2___024root___eval_act
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         'act' region trigger index 0 is active: @([event] t.ec.e)
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___timing_resume
+-V{t#,#}         Ready processes waiting for @([event] t.ec.e):
+-V{t#,#}           - Process waiting at t/t_timing_class.v:80
+-V{t#,#}         Resuming processes waiting for @([event] t.ec.e)
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:80
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aEventClass::__VnoInFunc_sleep
+-V{t#,#}         Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29
+-V{t#,#}+    Vt_timing_debug2___024root___eval_act
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         No triggers active
+-V{t#,#}         Doing post updates for processes:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___eval_nba
+-V{t#,#}+      Vt_timing_debug2_t___nba_sequent__TOP__t__0
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aEventClass::__VnoInFunc_inc_trig_count
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         No triggers active
+-V{t#,#}         Doing post updates for processes:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
+-V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
+-V{t#,#}+ Eval
+-V{t#,#}+    Vt_timing_debug2___024root___eval
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}         Doing post updates for processes:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___timing_resume
+-V{t#,#}         Delayed processes:
+-V{t#,#}             Awaiting time 30: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 30: Process waiting at t/t_timing_class.v:89
+-V{t#,#}             Awaiting time 50: Process waiting at t/t_timing_class.v:91
+-V{t#,#}             Awaiting time 80: Process waiting at t/t_timing_class.v:101
+-V{t#,#}             Awaiting time 40: Process waiting at t/t_timing_class.v:137
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:211
+-V{t#,#}         Resuming delayed processes
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:211
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay20::__VnoInFunc_do_sth_else
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay40::__VnoInFunc_do_delay
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:139
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aForkDelayClass::__VnoInFunc_do_delay
+-V{t#,#}+    Vt_timing_debug2___024root___eval_act
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         No triggers active
+-V{t#,#}         Doing post updates for processes:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___eval_nba
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         No triggers active
+-V{t#,#}         Doing post updates for processes:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
+-V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
+-V{t#,#}+ Eval
+-V{t#,#}+    Vt_timing_debug2___024root___eval
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}         Doing post updates for processes:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Suspending process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___timing_resume
+-V{t#,#}         Delayed processes:
+-V{t#,#}             Awaiting time 40: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 70: Process waiting at t/t_timing_class.v:89
+-V{t#,#}             Awaiting time 50: Process waiting at t/t_timing_class.v:91
+-V{t#,#}             Awaiting time 80: Process waiting at t/t_timing_class.v:101
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:137
+-V{t#,#}             Awaiting time 70: Process waiting at t/t_timing_class.v:202
+-V{t#,#}         Resuming delayed processes
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:202
+-V{t#,#}+    Vt_timing_debug2___024root___eval_act
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting the post update step
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         'act' region trigger index 0 is active: @([event] t.ec.e)
+-V{t#,#}         Doing post updates for processes:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}         Process waiting for @([event] t::EventClass.e) at t/t_timing_class.v:29 awaiting resumption
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___timing_resume
+-V{t#,#}         No ready processes waiting for @([event] t.ec.e)
+-V{t#,#}         Resuming processes waiting for @([event] t.ec.e)
+-V{t#,#}+    Vt_timing_debug2___024root___eval_act
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___timing_resume
+-V{t#,#}         Resuming processes:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:29
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:29
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aEventClass::__VnoInFunc_inc_trig_count
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aWaitClass::__VnoInFunc_await
+-V{t#,#}         Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50
+-V{t#,#}+    Vt_timing_debug2___024root___eval_act
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:50
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:50
+-V{t#,#}         Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         No triggers active
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___eval_nba
+-V{t#,#}+      Vt_timing_debug2_t___nba_sequent__TOP__t__0
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aEventClass::__VnoInFunc_inc_trig_count
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:50
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:50
+-V{t#,#}         Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         No triggers active
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
+-V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
+-V{t#,#}+ Eval
+-V{t#,#}+    Vt_timing_debug2___024root___eval
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:50
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:50
+-V{t#,#}         Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___timing_resume
+-V{t#,#}         Delayed processes:
+-V{t#,#}             Awaiting time 50: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 70: Process waiting at t/t_timing_class.v:89
+-V{t#,#}             Awaiting time 70: Process waiting at t/t_timing_class.v:91
+-V{t#,#}             Awaiting time 80: Process waiting at t/t_timing_class.v:101
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:137
+-V{t#,#}         Resuming delayed processes
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:137
+-V{t#,#}+    Vt_timing_debug2___024root___eval_act
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:50
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:50
+-V{t#,#}         Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___eval_nba
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:50
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:50
+-V{t#,#}         Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
@@ -86,25 +349,55 @@
 -V{t#,#}+ Eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:50
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:50
+-V{t#,#}         Suspending process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___timing_resume
 -V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 8: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 16: Process waiting at t/t_timing_fork_join.v:16
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:17
+-V{t#,#}             Awaiting time 60: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 70: Process waiting at t/t_timing_class.v:89
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:91
+-V{t#,#}             Awaiting time 80: Process waiting at t/t_timing_class.v:101
+-V{t#,#}             Awaiting time 70: Process waiting at t/t_timing_class.v:92
 -V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:17
-[8] fork..join process 1
--V{t#,#}             Process forked at t/t_timing_fork_join.v:15 finished
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:92
 -V{t#,#}+    Vt_timing_debug2___024root___eval_act
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:50
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:50
+-V{t#,#}         Process waiting for @([true] ((32'sh4 == t::WaitClass.a) & (32'sh10 < t::WaitClass.b))) at t/t_timing_class.v:50 awaiting resumption
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___timing_resume
+-V{t#,#}         Resuming processes:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:50
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:50
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aLocalWaitClass::__VnoInFunc_await
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aLocalWaitClass::__Vfork_h########__0__0
+-V{t#,#}         Suspending process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:67
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aLocalWaitClass::__Vfork_h########__0__1
+-V{t#,#}             Awaiting join of fork at: t/t_timing_class.v:66
+-V{t#,#}+    Vt_timing_debug2___024root___eval_act
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:67
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:67
+-V{t#,#}         Suspending process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:67
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___eval_nba
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:67
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:67
+-V{t#,#}         Suspending process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:67
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
@@ -113,114 +406,87 @@
 -V{t#,#}+ Eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:67
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:67
+-V{t#,#}         Suspending process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:67
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___timing_resume
 -V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 16: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:16
+-V{t#,#}             Awaiting time 70: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 70: Process waiting at t/t_timing_class.v:89
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:91
+-V{t#,#}             Awaiting time 80: Process waiting at t/t_timing_class.v:101
+-V{t#,#}             Awaiting time 70: Process waiting at t/t_timing_class.v:68
 -V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:16
-[16] fork in fork starts
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__0
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__1
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__2
-[16] fork..join process 8
--V{t#,#}             Process forked at t/t_timing_fork_join.v:25 finished
--V{t#,#}             Awaiting join of fork at: t/t_timing_fork_join.v:21
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___eval_nba
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
--V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
--V{t#,#}+ Eval
--V{t#,#}+    Vt_timing_debug2___024root___eval
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 20: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 24: Process waiting at t/t_timing_fork_join.v:22
--V{t#,#}             Awaiting time 32: Process waiting at t/t_timing_fork_join.v:23
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:24
--V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:24
-[20] fork..join process 7
--V{t#,#}             Process forked at t/t_timing_fork_join.v:24 finished
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___eval_nba
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
--V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
--V{t#,#}+ Eval
--V{t#,#}+    Vt_timing_debug2___024root___eval
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 24: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:22
--V{t#,#}             Awaiting time 32: Process waiting at t/t_timing_fork_join.v:23
--V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:23
-[24] fork..join process 6
--V{t#,#}             Process forked at t/t_timing_fork_join.v:23 finished
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___eval_nba
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
--V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
--V{t#,#}+ Eval
--V{t#,#}+    Vt_timing_debug2___024root___eval
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 32: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:22
--V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:22
-[32] fork..join process 5
--V{t#,#}             Process forked at t/t_timing_fork_join.v:22 finished
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:68
+-V{t#,#}             Process forked at t/t_timing_class.v:219 finished
 -V{t#,#}             Resuming: Process waiting at (null):0
-[32] fork..join in fork ends
--V{t#,#}             Process forked at t/t_timing_fork_join.v:19 finished
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:101
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay40::__VnoInFunc_do_sth_else
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aNoDelay::__VnoInFunc_do_delay
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aNoDelay::__VnoInFunc_do_sth_else
+-V{t#,#}+      Vt_timing_debug2_t____Vfork_h########__0__0
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aAssignDelayClass::__VnoInFunc_do_assign
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:154
+-V{t#,#}             Process forked at t/t_timing_class.v:68 finished
+-V{t#,#}+    Vt_timing_debug2___024root___eval_act
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         Suspended processes waiting for dynamic trigger evaluation:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:67
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:67
+-V{t#,#}         Process waiting for @([true] ((32'sh2a == t::LocalWaitClass.a) | (32'sh64 != t::LocalWaitClass.b))) at t/t_timing_class.v:67 awaiting resumption
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         'act' region trigger index 2 is active: @([true] __VdynSched.evaluate())
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___timing_resume
+-V{t#,#}         Resuming processes:
+-V{t#,#}           - Process waiting at t/t_timing_class.v:67
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:67
+-V{t#,#}             Process forked at t/t_timing_class.v:67 finished
 -V{t#,#}             Resuming: Process waiting at (null):0
 -V{t#,#}+    Vt_timing_debug2___024root___eval_act
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___eval_nba
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         No triggers active
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
+-V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aForkDelayClass::~
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aForkClass::~
+-V{t#,#}+ Eval
+-V{t#,#}+    Vt_timing_debug2___024root___eval
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___timing_resume
+-V{t#,#}         Delayed processes:
+-V{t#,#}             Awaiting time 75: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 80: Process waiting at t/t_timing_class.v:89
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:91
+-V{t#,#}             Awaiting time 80: Process waiting at t/t_timing_class.v:188
+-V{t#,#}         Resuming delayed processes
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:188
+-V{t#,#}+    Vt_timing_debug2___024root___eval_act
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
+-V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
+-V{t#,#}         No triggers active
+-V{t#,#}+    Vt_timing_debug2___024root___timing_commit
+-V{t#,#}+    Vt_timing_debug2___024root___eval_nba
+-V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
@@ -229,31 +495,29 @@
 -V{t#,#}+ Eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___timing_resume
 -V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 64: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:30
+-V{t#,#}             Awaiting time 80: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 80: Process waiting at t/t_timing_class.v:89
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:91
 -V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:30
-[64] main process
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__0
--V{t#,#}         Suspending process waiting for @([event] t.event1) at t/t_timing_fork_join.v:33
-fork..join_any process 2
--V{t#,#}             Process forked at t/t_timing_fork_join.v:37 finished
--V{t#,#}             Awaiting join of fork at: t/t_timing_fork_join.v:31
-back in main process
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:91
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:89
+-V{t#,#}+      Vt_timing_debug2_t____Vfork_h########__0__1
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aAssignDelayClass::__VnoInFunc_do_assign
 -V{t#,#}+    Vt_timing_debug2___024root___eval_act
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}         Committing processes waiting for @([event] t.event1):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:33
 -V{t#,#}+    Vt_timing_debug2___024root___eval_nba
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
@@ -262,42 +526,26 @@ back in main process
 -V{t#,#}+ Eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___timing_resume
 -V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 65: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:40
+-V{t#,#}             Awaiting time 85: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:154
+-V{t#,#}             Awaiting time 90: Process waiting at t/t_timing_class.v:194
 -V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:40
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 1 is active: @([event] t.event1)
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Ready processes waiting for @([event] t.event1):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:33
--V{t#,#}         Resuming processes waiting for @([event] t.event1)
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:33
-fork..join_any process 1
--V{t#,#}             Process forked at t/t_timing_fork_join.v:32 finished
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 1 is active: @([event] t.event1)
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         No ready processes waiting for @([event] t.event1)
--V{t#,#}         Resuming processes waiting for @([event] t.event1)
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:194
 -V{t#,#}+    Vt_timing_debug2___024root___eval_act
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___eval_nba
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
@@ -306,323 +554,67 @@ fork..join_any process 1
 -V{t#,#}+ Eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___timing_resume
 -V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 66: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:41
+-V{t#,#}             Awaiting time 90: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:154
+-V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_class.v:195
 -V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:41
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__0
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__1
--V{t#,#}         Suspending process waiting for @([event] t.event1) at t/t_timing_fork_join.v:44
--V{t#,#}             Awaiting join of fork at: t/t_timing_fork_join.v:41
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}         Committing processes waiting for @([event] t.event1):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:44
--V{t#,#}+    Vt_timing_debug2___024root___eval_nba
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
--V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
--V{t#,#}+ Eval
--V{t#,#}+    Vt_timing_debug2___024root___eval
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 68: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:42
--V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:42
-fork..join_any process 1
--V{t#,#}             Process forked at t/t_timing_fork_join.v:42 finished
--V{t#,#}             Resuming: Process waiting at (null):0
-back in main process
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:195
 -V{t#,#}+    Vt_timing_debug2___024root___eval_act
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___eval_nba
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
 -V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aAssignDelayClass::~
 -V{t#,#}+ Eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
+-V{t#,#}         'act' region trigger index 1 is active: @([true] __VdlySched.awaitingCurrentTime())
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___timing_resume
 -V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 69: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:50
+-V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_class.v:88
+-V{t#,#}             Awaiting time 101: Process waiting at t/t_timing_class.v:154
 -V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:50
--V{t#,#}         Suspending process waiting for @([event] t.event1) at t/t_timing_fork_join.v:51
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 1 is active: @([event] t.event1)
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Ready processes waiting for @([event] t.event1):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:44
--V{t#,#}         Uncommitted processes waiting for @([event] t.event1):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:51
--V{t#,#}         Resuming processes waiting for @([event] t.event1)
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:44
-fork..join_any process 2
--V{t#,#}             Process forked at t/t_timing_fork_join.v:43 finished
--V{t#,#}         Committing processes waiting for @([event] t.event1):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:51
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 1 is active: @([event] t.event1)
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Ready processes waiting for @([event] t.event1):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:51
--V{t#,#}         Resuming processes waiting for @([event] t.event1)
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:51
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__0
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__1
--V{t#,#}         Suspending process waiting for @([event] t.event2) at t/t_timing_fork_join.v:62
--V{t#,#}+    Vt_timing_debug2___024root____Vfork_h########__0__2
--V{t#,#}         Suspending process waiting for @([event] t.event3) at t/t_timing_fork_join.v:68
-in main process
--V{t#,#}         Suspending process waiting for @([event] t.event1) at t/t_timing_fork_join.v:75
--V{t#,#}         Committing processes waiting for @([event] t.event1):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:75
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}         Committing processes waiting for @([event] t.event2):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:62
--V{t#,#}         Committing processes waiting for @([event] t.event3):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:68
--V{t#,#}+    Vt_timing_debug2___024root___eval_nba
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
--V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
--V{t#,#}+ Eval
--V{t#,#}+    Vt_timing_debug2___024root___eval
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 70: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:56
--V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:56
-fork..join_none process 1
--V{t#,#}         Suspending process waiting for @([event] t.event2) at t/t_timing_fork_join.v:58
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 2 is active: @([event] t.event2)
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Ready processes waiting for @([event] t.event2):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:62
--V{t#,#}         Uncommitted processes waiting for @([event] t.event2):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:58
--V{t#,#}         Resuming processes waiting for @([event] t.event2)
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:62
-fork..join_none process 2
--V{t#,#}         Committing processes waiting for @([event] t.event2):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:58
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___eval_nba
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
--V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
--V{t#,#}+ Eval
--V{t#,#}+    Vt_timing_debug2___024root___eval
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 71: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:63
--V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:63
--V{t#,#}         Suspending process waiting for @([event] t.event3) at t/t_timing_fork_join.v:64
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 3 is active: @([event] t.event3)
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Ready processes waiting for @([event] t.event3):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:68
--V{t#,#}         Uncommitted processes waiting for @([event] t.event3):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:64
--V{t#,#}         Resuming processes waiting for @([event] t.event3)
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:68
-fork..join_none process 3
--V{t#,#}         Committing processes waiting for @([event] t.event3):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:64
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___eval_nba
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
--V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
--V{t#,#}+ Eval
--V{t#,#}+    Vt_timing_debug2___024root___eval
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 72: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:69
--V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:69
--V{t#,#}         Suspending process waiting for @([event] t.event3) at t/t_timing_fork_join.v:70
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 3 is active: @([event] t.event3)
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Ready processes waiting for @([event] t.event3):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:64
--V{t#,#}         Uncommitted processes waiting for @([event] t.event3):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:70
--V{t#,#}         Resuming processes waiting for @([event] t.event3)
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:64
-fork..join_none process 2 again
--V{t#,#}         Committing processes waiting for @([event] t.event3):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:70
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___eval_nba
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
--V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
--V{t#,#}+ Eval
--V{t#,#}+    Vt_timing_debug2___024root___eval
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 73: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:65
--V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:65
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 2 is active: @([event] t.event2)
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Ready processes waiting for @([event] t.event2):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:58
--V{t#,#}         Resuming processes waiting for @([event] t.event2)
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:58
-fork..join_none process 1 again
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___eval_nba
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         No triggers active
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+++++TOP Evaluate Vt_timing_debug2::eval_step
--V{t#,#}+    Vt_timing_debug2___024root___eval_debug_assertions
--V{t#,#}+ Eval
--V{t#,#}+    Vt_timing_debug2___024root___eval
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 0 is active: @([true] __VdlySched.awaitingCurrentTime())
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Delayed processes:
--V{t#,#}             Awaiting time 74: Process waiting at t/t_timing_fork_join.v:15
--V{t#,#}             Awaiting time 100: Process waiting at t/t_timing_fork_join.v:59
--V{t#,#}         Resuming delayed processes
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:59
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 3 is active: @([event] t.event3)
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Ready processes waiting for @([event] t.event3):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:70
--V{t#,#}         Resuming processes waiting for @([event] t.event3)
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:70
-fork..join_none process 3 again
--V{t#,#}+    Vt_timing_debug2___024root___eval_act
--V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
--V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
--V{t#,#}         'act' region trigger index 1 is active: @([event] t.event1)
--V{t#,#}+    Vt_timing_debug2___024root___timing_commit
--V{t#,#}+    Vt_timing_debug2___024root___timing_resume
--V{t#,#}         Ready processes waiting for @([event] t.event1):
--V{t#,#}           - Process waiting at t/t_timing_fork_join.v:75
--V{t#,#}         Resuming processes waiting for @([event] t.event1)
--V{t#,#}             Resuming: Process waiting at t/t_timing_fork_join.v:75
+-V{t#,#}             Resuming: Process waiting at t/t_timing_class.v:154
 *-* All Finished *-*
 -V{t#,#}+    Vt_timing_debug2___024root___eval_act
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___eval_nba
 -V{t#,#}+    Vt_timing_debug2___024root___eval_triggers__act
+-V{t#,#}         No suspended processes waiting for dynamic trigger evaluation
 -V{t#,#}+    Vt_timing_debug2___024root___dump_triggers__act
 -V{t#,#}         No triggers active
 -V{t#,#}+    Vt_timing_debug2___024root___timing_commit
 -V{t#,#}+    Vt_timing_debug2___024root___eval_final
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay40::~
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::~
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay20::~
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::~
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelay10::~
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::~
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aNoDelay::~
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aDelayClass::~
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aLocalWaitClass::~
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aWaitClass::~
+-V{t#,#}+        Vt_timing_debug2_t__03a__03aEventClass::~
diff --git a/test_regress/t/t_timing_debug2.pl b/test_regress/t/t_timing_debug2.pl
index c5b6b9286..56ef19876 100755
--- a/test_regress/t/t_timing_debug2.pl
+++ b/test_regress/t/t_timing_debug2.pl
@@ -14,7 +14,7 @@ if (!$Self->have_coroutines) {
     skip("No coroutine support");
 }
 else {
-    top_filename("t/t_timing_fork_join.v");
+    top_filename("t/t_timing_class.v");
 
     compile(
         verilator_flags2 => ["--exe --main --timing"],
diff --git a/test_regress/t/t_timing_localevent_unsup.out b/test_regress/t/t_timing_localevent_unsup.out
new file mode 100644
index 000000000..46578cb94
--- /dev/null
+++ b/test_regress/t/t_timing_localevent_unsup.out
@@ -0,0 +1,6 @@
+%Error-UNSUPPORTED: t/t_timing_localevent_unsup.v:12:17: Unsupported: waiting on local event variables
+                                                       : ... In instance t::Sleeper
+   12 |                 @e;
+      |                 ^
+                    ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
+%Error: Exiting due to
diff --git a/test_regress/t/t_timing_class_unsup.pl b/test_regress/t/t_timing_localevent_unsup.pl
similarity index 100%
rename from test_regress/t/t_timing_class_unsup.pl
rename to test_regress/t/t_timing_localevent_unsup.pl
diff --git a/test_regress/t/t_timing_localevent_unsup.v b/test_regress/t/t_timing_localevent_unsup.v
new file mode 100644
index 000000000..8091e2a4b
--- /dev/null
+++ b/test_regress/t/t_timing_localevent_unsup.v
@@ -0,0 +1,24 @@
+// DESCRIPTION: Verilator: Verilog Test module
+//
+// This file ONLY is placed under the Creative Commons Public Domain, for
+// any use, without warranty, 2022 by Antmicro Ltd.
+// SPDX-License-Identifier: CC0-1.0
+
+module t;
+    class Sleeper;
+        task sleep;
+            event e;
+            fork
+                @e;
+                #1 ->e;
+            join;
+        endtask
+    endclass
+
+    initial begin
+        Sleeper sleeper = new;
+        sleeper.sleep;
+        $write("*-* All Finished *-*\n");
+        $finish;
+    end
+endmodule

From 2df886b2b056b1d0be06f57119be13408bc9a717 Mon Sep 17 00:00:00 2001
From: Wilson Snyder 
Date: Sat, 22 Oct 2022 10:58:10 -0400
Subject: [PATCH 158/177] Tests: Check std:: package for
 mailbox/process/semaphore

---
 test_regress/t/t_mailbox_std.out   | 11 +++++++++++
 test_regress/t/t_mailbox_std.pl    | 26 ++++++++++++++++++++++++++
 test_regress/t/t_process_bad.out   | 12 ++++++------
 test_regress/t/t_process_bad.pl    |  2 --
 test_regress/t/t_process_std.out   | 11 +++++++++++
 test_regress/t/t_process_std.pl    | 26 ++++++++++++++++++++++++++
 test_regress/t/t_semaphore_std.out | 11 +++++++++++
 test_regress/t/t_semaphore_std.pl  | 26 ++++++++++++++++++++++++++
 8 files changed, 117 insertions(+), 8 deletions(-)
 create mode 100644 test_regress/t/t_mailbox_std.out
 create mode 100755 test_regress/t/t_mailbox_std.pl
 create mode 100644 test_regress/t/t_process_std.out
 create mode 100755 test_regress/t/t_process_std.pl
 create mode 100644 test_regress/t/t_semaphore_std.out
 create mode 100755 test_regress/t/t_semaphore_std.pl

diff --git a/test_regress/t/t_mailbox_std.out b/test_regress/t/t_mailbox_std.out
new file mode 100644
index 000000000..d9c517f02
--- /dev/null
+++ b/test_regress/t/t_mailbox_std.out
@@ -0,0 +1,11 @@
+%Error: t/t_mailbox.pl:1:1: syntax error, unexpected '#'
+    1 | #!/usr/bin/env perl
+      | ^
+%Error-UNSUPPORTED: t/t_mailbox.pl:2:19: Unsupported: Verilog 2001-config reserved word not implemented: 'use'
+    2 | if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
+      |                   ^~~
+                    ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
+%Error-UNSUPPORTED: t/t_mailbox.pl:3:47: Unsupported: SystemVerilog 2005 reserved word not implemented: 'expect'
+    3 | # DESCRIPTION: Verilator: Verilog Test driver/expect definition
+      |                                               ^~~~~~
+%Error: Exiting due to
diff --git a/test_regress/t/t_mailbox_std.pl b/test_regress/t/t_mailbox_std.pl
new file mode 100755
index 000000000..207b5f005
--- /dev/null
+++ b/test_regress/t/t_mailbox_std.pl
@@ -0,0 +1,26 @@
+#!/usr/bin/env perl
+if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
+# DESCRIPTION: Verilator: Verilog Test driver/expect definition
+#
+# Copyright 2020 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
+
+scenarios(simulator => 1);
+
+top_filename("t/t_mailbox.pl");
+
+compile(
+    v_flags2 => ["+define+T_MAILBOX+std::mailbox"],
+    fails => $Self->{vlt_all},
+    expect_filename => $Self->{golden_filename},
+    );
+
+execute(
+    check_finished => 1,
+    ) if !$Self->{vlt_all};
+
+ok(1);
+1;
diff --git a/test_regress/t/t_process_bad.out b/test_regress/t/t_process_bad.out
index a4ada3778..3a5b96793 100644
--- a/test_regress/t/t_process_bad.out
+++ b/test_regress/t/t_process_bad.out
@@ -1,10 +1,10 @@
-%Error: t/t_process.v:22:4: Can't find typedef: 'process'
-   22 |    process p;
+%Error: t/t_process_bad.v:8:4: Can't find typedef: 'process'
+    8 |    process p;
       |    ^~~~~~~
-%Error-UNSUPPORTED: t/t_process.v:26:20: Unsupported: 'process'
-   26 |       p = process::self();
+%Error-UNSUPPORTED: t/t_process_bad.v:12:20: Unsupported: 'process'
+   12 |       p = process::self();
       |                    ^~~~
                     ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
-%Error: Internal Error: t/t_process.v:26:11: ../V3LinkDot.cpp:#: Bad package link
-   26 |       p = process::self();
+%Error: Internal Error: t/t_process_bad.v:12:11: ../V3LinkDot.cpp:#: Bad package link
+   12 |       p = process::self();
       |           ^~~~~~~
diff --git a/test_regress/t/t_process_bad.pl b/test_regress/t/t_process_bad.pl
index 7be24ae56..dd3bcbbf3 100755
--- a/test_regress/t/t_process_bad.pl
+++ b/test_regress/t/t_process_bad.pl
@@ -10,8 +10,6 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
 
 scenarios(vlt => 1);
 
-top_filename("t_process.v");
-
 lint(
     verilator_flags2 => ["--xml-only"],
     fails => 1,
diff --git a/test_regress/t/t_process_std.out b/test_regress/t/t_process_std.out
new file mode 100644
index 000000000..80b26b03c
--- /dev/null
+++ b/test_regress/t/t_process_std.out
@@ -0,0 +1,11 @@
+%Error: t/t_process.pl:1:1: syntax error, unexpected '#'
+    1 | #!/usr/bin/env perl
+      | ^
+%Error-UNSUPPORTED: t/t_process.pl:2:19: Unsupported: Verilog 2001-config reserved word not implemented: 'use'
+    2 | if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
+      |                   ^~~
+                    ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
+%Error-UNSUPPORTED: t/t_process.pl:3:47: Unsupported: SystemVerilog 2005 reserved word not implemented: 'expect'
+    3 | # DESCRIPTION: Verilator: Verilog Test driver/expect definition
+      |                                               ^~~~~~
+%Error: Exiting due to
diff --git a/test_regress/t/t_process_std.pl b/test_regress/t/t_process_std.pl
new file mode 100755
index 000000000..462019b41
--- /dev/null
+++ b/test_regress/t/t_process_std.pl
@@ -0,0 +1,26 @@
+#!/usr/bin/env perl
+if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
+# DESCRIPTION: Verilator: Verilog Test driver/expect definition
+#
+# Copyright 2020 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
+
+scenarios(simulator => 1);
+
+top_filename("t/t_process.pl");
+
+compile(
+    v_flags2 => ["+define+T_PROCESS+std::process"],
+    fails => $Self->{vlt_all},
+    expect_filename => $Self->{golden_filename},
+    );
+
+execute(
+    check_finished => 1,
+    ) if !$Self->{vlt_all};
+
+ok(1);
+1;
diff --git a/test_regress/t/t_semaphore_std.out b/test_regress/t/t_semaphore_std.out
new file mode 100644
index 000000000..884a7159b
--- /dev/null
+++ b/test_regress/t/t_semaphore_std.out
@@ -0,0 +1,11 @@
+%Error: t/t_semaphore.pl:1:1: syntax error, unexpected '#'
+    1 | #!/usr/bin/env perl
+      | ^
+%Error-UNSUPPORTED: t/t_semaphore.pl:2:19: Unsupported: Verilog 2001-config reserved word not implemented: 'use'
+    2 | if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
+      |                   ^~~
+                    ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
+%Error-UNSUPPORTED: t/t_semaphore.pl:3:47: Unsupported: SystemVerilog 2005 reserved word not implemented: 'expect'
+    3 | # DESCRIPTION: Verilator: Verilog Test driver/expect definition
+      |                                               ^~~~~~
+%Error: Exiting due to
diff --git a/test_regress/t/t_semaphore_std.pl b/test_regress/t/t_semaphore_std.pl
new file mode 100755
index 000000000..b6c87f81b
--- /dev/null
+++ b/test_regress/t/t_semaphore_std.pl
@@ -0,0 +1,26 @@
+#!/usr/bin/env perl
+if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
+# DESCRIPTION: Verilator: Verilog Test driver/expect definition
+#
+# Copyright 2020 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
+
+scenarios(simulator => 1);
+
+top_filename("t/t_semaphore.pl");
+
+compile(
+    v_flags2 => ["+define+T_SEMAPHORE+std::semaphore"],
+    fails => $Self->{vlt_all},
+    expect_filename => $Self->{golden_filename},
+    );
+
+execute(
+    check_finished => 1,
+    ) if !$Self->{vlt_all};
+
+ok(1);
+1;

From 495d52d30fb9b9d51b9fef582c2d2de99f3026e9 Mon Sep 17 00:00:00 2001
From: Wilson Snyder 
Date: Sat, 22 Oct 2022 12:17:56 -0400
Subject: [PATCH 159/177] Test and fix extra end_keywords

---
 src/verilog.l                        | 11 +++++++----
 test_regress/t/t_preproc_kwd_bad.out |  4 ++++
 test_regress/t/t_preproc_kwd_bad.pl  | 19 +++++++++++++++++++
 test_regress/t/t_preproc_kwd_bad.v   | 11 +++++++++++
 4 files changed, 41 insertions(+), 4 deletions(-)
 create mode 100644 test_regress/t/t_preproc_kwd_bad.out
 create mode 100755 test_regress/t/t_preproc_kwd_bad.pl
 create mode 100644 test_regress/t/t_preproc_kwd_bad.v

diff --git a/src/verilog.l b/src/verilog.l
index ff2479f92..07fb87abf 100644
--- a/src/verilog.l
+++ b/src/verilog.l
@@ -1026,8 +1026,10 @@ vnum    {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
   "`begin_keywords"[ \t]*\"1800-2012\"          { FL_FWD; yy_push_state(S12); PARSEP->lexPushKeywords(YY_START); FL_BRK; }
   "`begin_keywords"[ \t]*\"1800-2017\"          { FL_FWD; yy_push_state(S17); PARSEP->lexPushKeywords(YY_START); FL_BRK; }
   "`begin_keywords"[ \t]*\"1800[+]VAMS\"        { FL_FWD; yy_push_state(SAX); PARSEP->lexPushKeywords(YY_START); FL_BRK; }  /*Latest SV*/
-  "`end_keywords"                               { FL; yy_pop_state();
-                                                  if (!PARSEP->lexPopKeywords()) yylval.fl->v3error("`end_keywords when not inside `begin_keywords block");
+  "`end_keywords"                               { FL;
+                                                  if (!PARSEP->lexPopKeywords()) {
+                                                      yylval.fl->v3error("`end_keywords when not inside `begin_keywords block");
+                                                  } else { yy_pop_state(); }
                                                   FL_BRK; }
 
   /* Verilator */
@@ -1072,8 +1074,9 @@ vnum    {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
 }
 
   /* Catch all - absolutely last */
-<*>.|\n                 { FL; yylval.fl->v3error("Missing verilog.l rule: Default rule invoked in state "
-                                                 << YY_START << " '" << yytext << "'");
+<*>.|\n                 { FL; yylval.fl->v3error(  // LCOV_EXCL_LINE
+                                 "Missing verilog.l rule: Default rule invoked in state "
+                                 << YY_START << " '" << yytext << "'");
                           FL_BRK; }
 %%
 // Avoid code here as cl format misindents
diff --git a/test_regress/t/t_preproc_kwd_bad.out b/test_regress/t/t_preproc_kwd_bad.out
new file mode 100644
index 000000000..3fa950ee7
--- /dev/null
+++ b/test_regress/t/t_preproc_kwd_bad.out
@@ -0,0 +1,4 @@
+%Error: t/t_preproc_kwd_bad.v:8:1: `end_keywords when not inside `begin_keywords block
+    8 | `end_keywords   
+      | ^~~~~~~~~~~~~
+%Error: Exiting due to
diff --git a/test_regress/t/t_preproc_kwd_bad.pl b/test_regress/t/t_preproc_kwd_bad.pl
new file mode 100755
index 000000000..59ba0d6c6
--- /dev/null
+++ b/test_regress/t/t_preproc_kwd_bad.pl
@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
+# DESCRIPTION: Verilator: Verilog Test driver/expect definition
+#
+# Copyright 2003 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
+
+scenarios(linter => 1);
+
+lint(
+    fails => $Self->{vlt_all},
+    expect_filename => $Self->{golden_filename},
+    );
+
+ok(1);
+1;
diff --git a/test_regress/t/t_preproc_kwd_bad.v b/test_regress/t/t_preproc_kwd_bad.v
new file mode 100644
index 000000000..16c423b8b
--- /dev/null
+++ b/test_regress/t/t_preproc_kwd_bad.v
@@ -0,0 +1,11 @@
+// DESCRIPTION: Verilator: Verilog Test module
+//
+// This file ONLY is placed under the Creative Commons Public Domain, for
+// any use, without warranty, 2003 by Wilson Snyder.
+// SPDX-License-Identifier: CC0-1.0
+
+`end_keywords
+`end_keywords  // BAD
+
+module t;
+endmodule

From 63df87e220f84038970f6a39213d54945070017e Mon Sep 17 00:00:00 2001
From: Wilson Snyder 
Date: Sat, 22 Oct 2022 12:18:28 -0400
Subject: [PATCH 160/177] Commentary

---
 docs/guide/extensions.rst | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst
index 3e0161499..fd65e8b0a 100644
--- a/docs/guide/extensions.rst
+++ b/docs/guide/extensions.rst
@@ -185,9 +185,7 @@ or "`ifdef`"'s may break other tools.
 
    The clock_enable attribute will cause the clock gate to be ignored in
    the scheduling algorithm, sometimes required for correct clock behavior,
-   and always improving performance.  It's also a good idea to enable the
-   :option:`IMPERFECTSCH` warning, to ensure all clock enables are properly
-   recognized.
+   and always improving performance.
 
    Same as :option:`clock_enable` configuration file option.
 

From ecfa385f13cccbd28e7c52c605d5622611624a2e Mon Sep 17 00:00:00 2001
From: Wilson Snyder 
Date: Sat, 22 Oct 2022 12:30:44 -0400
Subject: [PATCH 161/177] Test/cleanup endtable without table

---
 src/verilog.l                         |  4 ++--
 test_regress/t/t_udp_tableend_bad.out |  4 ++++
 test_regress/t/t_udp_tableend_bad.pl  | 20 ++++++++++++++++++++
 test_regress/t/t_udp_tableend_bad.v   | 12 ++++++++++++
 test_regress/t/t_udp_tableeof_bad.out |  3 +++
 test_regress/t/t_udp_tableeof_bad.pl  | 20 ++++++++++++++++++++
 test_regress/t/t_udp_tableeof_bad.v   | 14 ++++++++++++++
 7 files changed, 75 insertions(+), 2 deletions(-)
 create mode 100644 test_regress/t/t_udp_tableend_bad.out
 create mode 100755 test_regress/t/t_udp_tableend_bad.pl
 create mode 100644 test_regress/t/t_udp_tableend_bad.v
 create mode 100644 test_regress/t/t_udp_tableeof_bad.out
 create mode 100755 test_regress/t/t_udp_tableeof_bad.pl
 create mode 100644 test_regress/t/t_udp_tableeof_bad.v

diff --git a/src/verilog.l b/src/verilog.l
index 07fb87abf..b2cf6533f 100644
--- a/src/verilog.l
+++ b/src/verilog.l
@@ -318,7 +318,7 @@ vnum    {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
   "endmodule"           { FL; return yENDMODULE; }
   "endprimitive"        { FL; return yENDPRIMITIVE; }
   "endspecify"          { FL; return yENDSPECIFY; }
-  "endtable"            { FL; yylval.fl->v3error("Syntax error: ENDTABLE outside of TABLE"); FL_BRK; }
+  "endtable"            { FL; yylval.fl->v3error("Syntax error: 'endtable' outside of 'table'"); FL_BRK; }
   "endtask"             { FL; return yENDTASK; }
   "event"               { FL; return yEVENT; }
   "for"                 { FL; return yFOR; }
@@ -961,7 +961,7 @@ vnum    {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
 "endtable"       { yy_pop_state(); FL; return yENDTABLE; }
 
"`line"{ws}+[^\n\r]*{crnl} { FL_FWD; PARSEP->lexPpline(yytext); FL_BRK; }
. { yymore(); } -
<> { FL; yylval.fl->v3error("EOF in TABLE"); +
<> { FL; yylval.fl->v3error("EOF in 'table'"); yyleng = 0; yy_pop_state(); FL_BRK; yyterminate(); } /************************************************************************/ diff --git a/test_regress/t/t_udp_tableend_bad.out b/test_regress/t/t_udp_tableend_bad.out new file mode 100644 index 000000000..d673497d0 --- /dev/null +++ b/test_regress/t/t_udp_tableend_bad.out @@ -0,0 +1,4 @@ +%Error: t/t_udp_tableend_bad.v:11:4: Syntax error: 'endtable' outside of 'table' + 11 | endtable + | ^~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_udp_tableend_bad.pl b/test_regress/t/t_udp_tableend_bad.pl new file mode 100755 index 000000000..fc220ea47 --- /dev/null +++ b/test_regress/t/t_udp_tableend_bad.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--lint-only --bbox-unsup"], + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_udp_tableend_bad.v b/test_regress/t/t_udp_tableend_bad.v new file mode 100644 index 000000000..6597e7be6 --- /dev/null +++ b/test_regress/t/t_udp_tableend_bad.v @@ -0,0 +1,12 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2009 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +primitive udp_x (a_bad, b, c_bad); + tri a_bad; + output b; + output c_bad; + endtable // BAD +endprimitive diff --git a/test_regress/t/t_udp_tableeof_bad.out b/test_regress/t/t_udp_tableeof_bad.out new file mode 100644 index 000000000..bf61e32d3 --- /dev/null +++ b/test_regress/t/t_udp_tableeof_bad.out @@ -0,0 +1,3 @@ +%Error: t/t_udp_tableeof_bad.v:16:1: EOF in 'table' +%Error: t/t_udp_tableeof_bad.v:16:1: syntax error, unexpected end of file, expecting TABLE LINE or endtable +%Error: Cannot continue diff --git a/test_regress/t/t_udp_tableeof_bad.pl b/test_regress/t/t_udp_tableeof_bad.pl new file mode 100755 index 000000000..fc220ea47 --- /dev/null +++ b/test_regress/t/t_udp_tableeof_bad.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--lint-only --bbox-unsup"], + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_udp_tableeof_bad.v b/test_regress/t/t_udp_tableeof_bad.v new file mode 100644 index 000000000..064573395 --- /dev/null +++ b/test_regress/t/t_udp_tableeof_bad.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2009 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +primitive udp_x (a_bad, b, c_bad); + tri a_bad; + output b; + output c_bad; + table + //a b + 0 : 1; + 1 : 0; From 8e1901da100859ebf3f03961f0d2e276ccdcfb09 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 22 Oct 2022 13:45:48 -0400 Subject: [PATCH 162/177] Tests: Cover some previously uncovered warnings --- src/V3GraphAcyc.cpp | 2 +- src/V3Unroll.cpp | 2 +- test_regress/t/t_assoc_method_bad.out | 4 + test_regress/t/t_assoc_method_bad.v | 2 + test_regress/t/t_assoc_wildcard_bad.out | 4 + test_regress/t/t_assoc_wildcard_bad.v | 2 + test_regress/t/t_case_inside_bad.out | 4 + test_regress/t/t_case_inside_bad.pl | 19 ++++ test_regress/t/t_case_inside_bad.v | 13 +++ test_regress/t/t_enum_bad_dup.out | 7 ++ test_regress/t/t_enum_bad_dup.pl | 20 ++++ test_regress/t/t_enum_bad_dup.v | 13 +++ test_regress/t/t_enum_bad_wrap.out | 5 + test_regress/t/t_enum_bad_wrap.pl | 20 ++++ test_regress/t/t_enum_bad_wrap.v | 14 +++ test_regress/t/t_enum_type_nomethod_bad.out | 5 + test_regress/t/t_enum_type_nomethod_bad.pl | 19 ++++ test_regress/t/t_enum_type_nomethod_bad.v | 19 ++++ test_regress/t/t_flag_deprecated_bad.out | 7 ++ test_regress/t/t_flag_deprecated_bad.pl | 20 ++++ test_regress/t/t_flag_deprecated_bad.v | 8 ++ test_regress/t/t_foreach_nindex_bad.out | 4 + test_regress/t/t_foreach_nindex_bad.pl | 19 ++++ test_regress/t/t_foreach_nindex_bad.v | 17 ++++ test_regress/t/t_gen_nonconst_bad.out | 17 ++++ test_regress/t/t_gen_nonconst_bad.pl | 19 ++++ test_regress/t/t_gen_nonconst_bad.v | 15 +++ test_regress/t/t_inst_2star_bad.out | 4 + test_regress/t/t_inst_2star_bad.pl | 19 ++++ test_regress/t/t_inst_2star_bad.v | 14 +++ test_regress/t/t_lib_prot_delay_bad.out | 3 + test_regress/t/t_lib_prot_delay_bad.pl | 26 ++++++ test_regress/t/t_lib_prot_delay_bad.v | 11 +++ test_regress/t/t_lint_contassreg_bad.out | 6 ++ test_regress/t/t_lint_contassreg_bad.pl | 20 ++++ test_regress/t/t_lint_contassreg_bad.v | 16 ++++ test_regress/t/t_lint_historical.pl | 18 ++++ test_regress/t/t_lint_historical.v | 92 +++++++++++++++++++ .../t/t_lint_pragma_protected_err.out | 28 +++++- test_regress/t/t_lint_pragma_protected_err.v | 9 ++ test_regress/t/t_pp_defnettype_bad.out | 11 +++ test_regress/t/t_pp_defnettype_bad.pl | 19 ++++ test_regress/t/t_pp_defnettype_bad.v | 9 ++ test_regress/t/t_preproc_nodef_bad.out | 4 + test_regress/t/t_preproc_nodef_bad.pl | 19 ++++ test_regress/t/t_preproc_nodef_bad.v | 10 ++ test_regress/t/t_string_type_methods_bad.out | 4 + test_regress/t/t_string_type_methods_bad.v | 1 + test_regress/t/t_unbounded_bad.out | 11 +++ test_regress/t/t_unbounded_bad.pl | 19 ++++ test_regress/t/t_unbounded_bad.v | 11 +++ 51 files changed, 680 insertions(+), 4 deletions(-) create mode 100644 test_regress/t/t_case_inside_bad.out create mode 100755 test_regress/t/t_case_inside_bad.pl create mode 100644 test_regress/t/t_case_inside_bad.v create mode 100644 test_regress/t/t_enum_bad_dup.out create mode 100755 test_regress/t/t_enum_bad_dup.pl create mode 100644 test_regress/t/t_enum_bad_dup.v create mode 100644 test_regress/t/t_enum_bad_wrap.out create mode 100755 test_regress/t/t_enum_bad_wrap.pl create mode 100644 test_regress/t/t_enum_bad_wrap.v create mode 100644 test_regress/t/t_enum_type_nomethod_bad.out create mode 100755 test_regress/t/t_enum_type_nomethod_bad.pl create mode 100644 test_regress/t/t_enum_type_nomethod_bad.v create mode 100644 test_regress/t/t_flag_deprecated_bad.out create mode 100755 test_regress/t/t_flag_deprecated_bad.pl create mode 100644 test_regress/t/t_flag_deprecated_bad.v create mode 100644 test_regress/t/t_foreach_nindex_bad.out create mode 100755 test_regress/t/t_foreach_nindex_bad.pl create mode 100644 test_regress/t/t_foreach_nindex_bad.v create mode 100644 test_regress/t/t_gen_nonconst_bad.out create mode 100755 test_regress/t/t_gen_nonconst_bad.pl create mode 100644 test_regress/t/t_gen_nonconst_bad.v create mode 100644 test_regress/t/t_inst_2star_bad.out create mode 100755 test_regress/t/t_inst_2star_bad.pl create mode 100644 test_regress/t/t_inst_2star_bad.v create mode 100644 test_regress/t/t_lib_prot_delay_bad.out create mode 100755 test_regress/t/t_lib_prot_delay_bad.pl create mode 100644 test_regress/t/t_lib_prot_delay_bad.v create mode 100644 test_regress/t/t_lint_contassreg_bad.out create mode 100755 test_regress/t/t_lint_contassreg_bad.pl create mode 100644 test_regress/t/t_lint_contassreg_bad.v create mode 100755 test_regress/t/t_lint_historical.pl create mode 100644 test_regress/t/t_lint_historical.v create mode 100644 test_regress/t/t_pp_defnettype_bad.out create mode 100755 test_regress/t/t_pp_defnettype_bad.pl create mode 100644 test_regress/t/t_pp_defnettype_bad.v create mode 100644 test_regress/t/t_preproc_nodef_bad.out create mode 100755 test_regress/t/t_preproc_nodef_bad.pl create mode 100644 test_regress/t/t_preproc_nodef_bad.v create mode 100644 test_regress/t/t_unbounded_bad.out create mode 100755 test_regress/t/t_unbounded_bad.pl create mode 100644 test_regress/t/t_unbounded_bad.v diff --git a/src/V3GraphAcyc.cpp b/src/V3GraphAcyc.cpp index 016c6930b..e2be3e019 100644 --- a/src/V3GraphAcyc.cpp +++ b/src/V3GraphAcyc.cpp @@ -345,7 +345,7 @@ void GraphAcyc::simplifyOut(GraphAcycVertex* avertexp) { nextp = inEdgep->inNextp(); V3GraphVertex* inVertexp = inEdgep->fromp(); if (inVertexp == avertexp) { - if (debug()) v3error("Non-cutable edge forms a loop, vertex=" << avertexp); + if (debug()) v3error("Non-cutable vertex=" << avertexp); // LCOV_EXCL_LINE v3error("Circular logic when ordering code (non-cutable edge loop)"); m_origGraphp->reportLoops( &V3GraphEdge::followNotCutable, diff --git a/src/V3Unroll.cpp b/src/V3Unroll.cpp index 4629f16a5..e0b96f4ae 100644 --- a/src/V3Unroll.cpp +++ b/src/V3Unroll.cpp @@ -438,7 +438,7 @@ private: if (m_generate) { // Ignore for's when expanding genfor's iterateChildren(nodep); } else { - nodep->v3error("V3Begin should have removed standard FORs"); + nodep->v3fatalSrc("V3Begin should have removed standard FORs"); } } diff --git a/test_regress/t/t_assoc_method_bad.out b/test_regress/t/t_assoc_method_bad.out index 6c0ff94cc..76c49e771 100644 --- a/test_regress/t/t_assoc_method_bad.out +++ b/test_regress/t/t_assoc_method_bad.out @@ -50,4 +50,8 @@ : ... In instance t 27 | a.shuffle; | ^~~~~~~ +%Error: t/t_assoc_method_bad.v:29:9: Unknown built-in associative array method 'bad_not_defined' + : ... In instance t + 29 | a.bad_not_defined(); + | ^~~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assoc_method_bad.v b/test_regress/t/t_assoc_method_bad.v index bd4270f7f..9921ef6bd 100644 --- a/test_regress/t/t_assoc_method_bad.v +++ b/test_regress/t/t_assoc_method_bad.v @@ -25,5 +25,7 @@ module t (/*AUTOARG*/); a.rsort; // Not legal on assoc a.reverse; // Not legal on assoc a.shuffle; // Not legal on assoc + + a.bad_not_defined(); end endmodule diff --git a/test_regress/t/t_assoc_wildcard_bad.out b/test_regress/t/t_assoc_wildcard_bad.out index 2bd459ff6..650f4b90c 100644 --- a/test_regress/t/t_assoc_wildcard_bad.out +++ b/test_regress/t/t_assoc_wildcard_bad.out @@ -70,4 +70,8 @@ : ... In instance t 43 | a[x] = "bad"; | ^ +%Error: t/t_assoc_wildcard_bad.v:45:9: Unknown wildcard associative array method 'bad_not_defined' + : ... In instance t + 45 | a.bad_not_defined(); + | ^~~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assoc_wildcard_bad.v b/test_regress/t/t_assoc_wildcard_bad.v index 85ebf83ba..a2325d78c 100644 --- a/test_regress/t/t_assoc_wildcard_bad.v +++ b/test_regress/t/t_assoc_wildcard_bad.v @@ -41,5 +41,7 @@ module t (/*AUTOARG*/); a.find_last_index; // Not legal on wildcard a[x] = "bad"; + + a.bad_not_defined(); end endmodule diff --git a/test_regress/t/t_case_inside_bad.out b/test_regress/t/t_case_inside_bad.out new file mode 100644 index 000000000..b683c04e3 --- /dev/null +++ b/test_regress/t/t_case_inside_bad.out @@ -0,0 +1,4 @@ +%Error: t/t_case_inside_bad.v:9:7: Illegal to have inside on a casex/casez + 9 | casex (1'bx) inside + | ^~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_case_inside_bad.pl b/test_regress/t/t_case_inside_bad.pl new file mode 100755 index 000000000..a60503a1f --- /dev/null +++ b/test_regress/t/t_case_inside_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_case_inside_bad.v b/test_regress/t/t_case_inside_bad.v new file mode 100644 index 000000000..aa4e967c1 --- /dev/null +++ b/test_regress/t/t_case_inside_bad.v @@ -0,0 +1,13 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial begin + casex (1'bx) inside + default: $stop; + endcase + end +endmodule diff --git a/test_regress/t/t_enum_bad_dup.out b/test_regress/t/t_enum_bad_dup.out new file mode 100644 index 000000000..b6dac7521 --- /dev/null +++ b/test_regress/t/t_enum_bad_dup.out @@ -0,0 +1,7 @@ +%Error: t/t_enum_bad_dup.v:10:19: Duplicate declaration of enum value: DUP_VALUE + 10 | DUP_VALUE = 3 + | ^~~~~~~~~ + t/t_enum_bad_dup.v:9:19: ... Location of original declaration + 9 | typedef enum { DUP_VALUE = 2, + | ^~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_enum_bad_dup.pl b/test_regress/t/t_enum_bad_dup.pl new file mode 100755 index 000000000..f1eef1686 --- /dev/null +++ b/test_regress/t/t_enum_bad_dup.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--lint-only -Wwarn-VARHIDDEN"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_enum_bad_dup.v b/test_regress/t/t_enum_bad_dup.v new file mode 100644 index 000000000..94874b91d --- /dev/null +++ b/test_regress/t/t_enum_bad_dup.v @@ -0,0 +1,13 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2003 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + + typedef enum { DUP_VALUE = 2, + DUP_VALUE = 3 + } dup_t; + +endmodule diff --git a/test_regress/t/t_enum_bad_wrap.out b/test_regress/t/t_enum_bad_wrap.out new file mode 100644 index 000000000..72c9403fc --- /dev/null +++ b/test_regress/t/t_enum_bad_wrap.out @@ -0,0 +1,5 @@ +%Error: t/t_enum_bad_wrap.v:11:19: Enum value illegally wrapped around (IEEE 1800-2017 6.19) + : ... In instance t + 11 | WRAPPED + | ^~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_enum_bad_wrap.pl b/test_regress/t/t_enum_bad_wrap.pl new file mode 100755 index 000000000..f1eef1686 --- /dev/null +++ b/test_regress/t/t_enum_bad_wrap.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--lint-only -Wwarn-VARHIDDEN"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_enum_bad_wrap.v b/test_regress/t/t_enum_bad_wrap.v new file mode 100644 index 000000000..9219f08e8 --- /dev/null +++ b/test_regress/t/t_enum_bad_wrap.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2003 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + + typedef enum [1:0] { + PREWRAP = 2'd3, + WRAPPED + } wrap_t; + +endmodule diff --git a/test_regress/t/t_enum_type_nomethod_bad.out b/test_regress/t/t_enum_type_nomethod_bad.out new file mode 100644 index 000000000..bc6d41978 --- /dev/null +++ b/test_regress/t/t_enum_type_nomethod_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_enum_type_nomethod_bad.v:15:9: Unknown built-in enum method 'bad_no_such_method' + : ... In instance t + 15 | e.bad_no_such_method(); + | ^~~~~~~~~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_enum_type_nomethod_bad.pl b/test_regress/t/t_enum_type_nomethod_bad.pl new file mode 100755 index 000000000..3c938d615 --- /dev/null +++ b/test_regress/t/t_enum_type_nomethod_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename} + ); + +ok(1); +1; diff --git a/test_regress/t/t_enum_type_nomethod_bad.v b/test_regress/t/t_enum_type_nomethod_bad.v new file mode 100644 index 000000000..c4aceee41 --- /dev/null +++ b/test_regress/t/t_enum_type_nomethod_bad.v @@ -0,0 +1,19 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + typedef enum [3:0] { + E01 = 1 + } my_t; + + my_t e; + + initial begin + e.bad_no_such_method(); + $stop; + end + +endmodule diff --git a/test_regress/t/t_flag_deprecated_bad.out b/test_regress/t/t_flag_deprecated_bad.out new file mode 100644 index 000000000..e13b7628f --- /dev/null +++ b/test_regress/t/t_flag_deprecated_bad.out @@ -0,0 +1,7 @@ +%Warning-DEPRECATED: Option -O is deprecated. Use -f or -fno- instead. + ... For warning description see https://verilator.org/warn/DEPRECATED?v=latest + ... Use "/* verilator lint_off DEPRECATED */" and lint_on around source to disable this message. +%Warning-DEPRECATED: Option --prof-threads is deprecated. Use --prof-exec and --prof-pgo instead. +%Warning-DEPRECATED: Option --trace-fst-thread is deprecated. Use --trace-fst with --trace-threads > 0. +%Warning-DEPRECATED: Option order-clock-delay is deprecated and has no effect. +%Error: Exiting due to diff --git a/test_regress/t/t_flag_deprecated_bad.pl b/test_regress/t/t_flag_deprecated_bad.pl new file mode 100755 index 000000000..5edf03bb9 --- /dev/null +++ b/test_regress/t/t_flag_deprecated_bad.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vlt => 1); + +lint( + verilator_flags2 => ["-Ox --prof-threads --trace-fst-thread --order-clock-delay"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_flag_deprecated_bad.v b/test_regress/t/t_flag_deprecated_bad.v new file mode 100644 index 000000000..582a47b4a --- /dev/null +++ b/test_regress/t/t_flag_deprecated_bad.v @@ -0,0 +1,8 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2019 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); +endmodule diff --git a/test_regress/t/t_foreach_nindex_bad.out b/test_regress/t/t_foreach_nindex_bad.out new file mode 100644 index 000000000..1433bce46 --- /dev/null +++ b/test_regress/t/t_foreach_nindex_bad.out @@ -0,0 +1,4 @@ +%Error: t/t_foreach_nindex_bad.v:12:34: foreach loop variables exceed number of indices of array + 12 | foreach (array[i, j, badk, badl]); + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_foreach_nindex_bad.pl b/test_regress/t/t_foreach_nindex_bad.pl new file mode 100755 index 000000000..a60503a1f --- /dev/null +++ b/test_regress/t/t_foreach_nindex_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_foreach_nindex_bad.v b/test_regress/t/t_foreach_nindex_bad.v new file mode 100644 index 000000000..d15e75e88 --- /dev/null +++ b/test_regress/t/t_foreach_nindex_bad.v @@ -0,0 +1,17 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + + int array[2][2]; + + initial begin + foreach (array[i, j, badk, badl]); // bad + + $stop; + end + +endmodule diff --git a/test_regress/t/t_gen_nonconst_bad.out b/test_regress/t/t_gen_nonconst_bad.out new file mode 100644 index 000000000..9fb9ebcb3 --- /dev/null +++ b/test_regress/t/t_gen_nonconst_bad.out @@ -0,0 +1,17 @@ +%Error: t/t_gen_nonconst_bad.v:8:8: Expecting expression to be constant, but can't convert a TESTPLUSARGS to constant. + : ... In instance t + 8 | if ($test$plusargs("BAD-non-constant")) begin + | ^~~~~~~~~~~~~~ +%Error: t/t_gen_nonconst_bad.v:8:8: Generate If condition must evaluate to constant + : ... In instance t + 8 | if ($test$plusargs("BAD-non-constant")) begin + | ^~~~~~~~~~~~~~ +%Error: t/t_gen_nonconst_bad.v:12:7: Expecting expression to be constant, but can't convert a TESTPLUSARGS to constant. + : ... In instance t + 12 | $test$plusargs("BAD-non-constant"): initial $stop; + | ^~~~~~~~~~~~~~ +%Error: t/t_gen_nonconst_bad.v:12:41: Generate Case item does not evaluate to constant + : ... In instance t + 12 | $test$plusargs("BAD-non-constant"): initial $stop; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_gen_nonconst_bad.pl b/test_regress/t/t_gen_nonconst_bad.pl new file mode 100755 index 000000000..a5846c699 --- /dev/null +++ b/test_regress/t/t_gen_nonconst_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vlt => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_gen_nonconst_bad.v b/test_regress/t/t_gen_nonconst_bad.v new file mode 100644 index 000000000..d8714fb72 --- /dev/null +++ b/test_regress/t/t_gen_nonconst_bad.v @@ -0,0 +1,15 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2012 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + if ($test$plusargs("BAD-non-constant")) begin + initial $stop; + end + case (1) + $test$plusargs("BAD-non-constant"): initial $stop; + endcase + +endmodule diff --git a/test_regress/t/t_inst_2star_bad.out b/test_regress/t/t_inst_2star_bad.out new file mode 100644 index 000000000..cebc66d7d --- /dev/null +++ b/test_regress/t/t_inst_2star_bad.out @@ -0,0 +1,4 @@ +%Error: t/t_inst_2star_bad.v:9:17: Duplicate .* in an instance + 9 | sub sub (.*, .*); + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_inst_2star_bad.pl b/test_regress/t/t_inst_2star_bad.pl new file mode 100755 index 000000000..35d749208 --- /dev/null +++ b/test_regress/t/t_inst_2star_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003-2009 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 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_inst_2star_bad.v b/test_regress/t/t_inst_2star_bad.v new file mode 100644 index 000000000..c9e6858e9 --- /dev/null +++ b/test_regress/t/t_inst_2star_bad.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + + sub sub (.*, .*); + +endmodule + +module sub (input foo); +endmodule diff --git a/test_regress/t/t_lib_prot_delay_bad.out b/test_regress/t/t_lib_prot_delay_bad.out new file mode 100644 index 000000000..37f5d76b7 --- /dev/null +++ b/test_regress/t/t_lib_prot_delay_bad.out @@ -0,0 +1,3 @@ +%Error-UNSUPPORTED: Unsupported: --lib-create with --timing and delays + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_lib_prot_delay_bad.pl b/test_regress/t/t_lib_prot_delay_bad.pl new file mode 100755 index 000000000..7bb3853cf --- /dev/null +++ b/test_regress/t/t_lib_prot_delay_bad.pl @@ -0,0 +1,26 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2019 by Todd Strader. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +compile ( + verilator_flags2 => ["--protect-lib", + "secret", + "--protect-key", + "secret-key", + "--timing", + ], + verilator_make_gcc => 0, + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_lib_prot_delay_bad.v b/test_regress/t/t_lib_prot_delay_bad.v new file mode 100644 index 000000000..832e60f2e --- /dev/null +++ b/test_regress/t/t_lib_prot_delay_bad.v @@ -0,0 +1,11 @@ +// DESCRIPTION: Verilator: Verilog Test module +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2019 by Todd Strader. +// SPDX-License-Identifier: CC0-1.0 + +module secret_impl; + initial begin + #10; + $stop; + end +endmodule diff --git a/test_regress/t/t_lint_contassreg_bad.out b/test_regress/t/t_lint_contassreg_bad.out new file mode 100644 index 000000000..4921a2787 --- /dev/null +++ b/test_regress/t/t_lint_contassreg_bad.out @@ -0,0 +1,6 @@ +%Error-CONTASSREG: t/t_lint_contassreg_bad.v:14:11: Continuous assignment to reg, perhaps intended wire (IEEE 1364-2005 6.1; Verilog only, legal in SV): 'r' + : ... In instance t + 14 | assign r = 1'b0; + | ^ + ... For error description see https://verilator.org/warn/CONTASSREG?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_lint_contassreg_bad.pl b/test_regress/t/t_lint_contassreg_bad.pl new file mode 100755 index 000000000..46010fd1c --- /dev/null +++ b/test_regress/t/t_lint_contassreg_bad.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(vlt => 1); + +lint( + verilator_flags2 => ['--language 1364-2001'], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_lint_contassreg_bad.v b/test_regress/t/t_lint_contassreg_bad.v new file mode 100644 index 000000000..585c37c0d --- /dev/null +++ b/test_regress/t/t_lint_contassreg_bad.v @@ -0,0 +1,16 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2012 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + + +module t(r); + + output r; + + reg r; + + assign r = 1'b0; // Bad + +endmodule diff --git a/test_regress/t/t_lint_historical.pl b/test_regress/t/t_lint_historical.pl new file mode 100755 index 000000000..a6e8d4033 --- /dev/null +++ b/test_regress/t/t_lint_historical.pl @@ -0,0 +1,18 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(vlt => 1); + +lint( + verilator_flags2 => ["--lint-only"], + ); + +ok(1); +1; diff --git a/test_regress/t/t_lint_historical.v b/test_regress/t/t_lint_historical.v new file mode 100644 index 000000000..8647a070c --- /dev/null +++ b/test_regress/t/t_lint_historical.v @@ -0,0 +1,92 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + // Test all warnings, including those that are historically removed still parse + // verilator lint_off ALWCOMBORDER + // verilator lint_off ASSIGNDLY + // verilator lint_off ASSIGNIN + // verilator lint_off BADSTDPRAGMA + // verilator lint_off BLKANDNBLK + // verilator lint_off BLKLOOPINIT + // verilator lint_off BLKSEQ + // verilator lint_off BSSPACE + // verilator lint_off CASEINCOMPLETE + // verilator lint_off CASEOVERLAP + // verilator lint_off CASEWITHX + // verilator lint_off CASEX + // verilator lint_off CASTCONST + // verilator lint_off CDCRSTLOGIC + // verilator lint_off CLKDATA + // verilator lint_off CMPCONST + // verilator lint_off COLONPLUS + // verilator lint_off COMBDLY + // verilator lint_off CONTASSREG + // verilator lint_off DEFPARAM + // verilator lint_off DECLFILENAME + // verilator lint_off DEPRECATED + // verilator lint_off RISEFALLDLY + // verilator lint_off MINTYPMAXDLY + // verilator lint_off ENDLABEL + // verilator lint_off EOFNEWLINE + // verilator lint_off GENCLK + // verilator lint_off HIERBLOCK + // verilator lint_off IFDEPTH + // verilator lint_off IGNOREDRETURN + // verilator lint_off IMPERFECTSCH + // verilator lint_off IMPLICIT + // verilator lint_off IMPORTSTAR + // verilator lint_off IMPURE + // verilator lint_off INCABSPATH + // verilator lint_off INFINITELOOP + // verilator lint_off INITIALDLY + // verilator lint_off INSECURE + // verilator lint_off LATCH + // verilator lint_off LITENDIAN + // verilator lint_off MODDUP + // verilator lint_off MULTIDRIVEN + // verilator lint_off MULTITOP + // verilator lint_off NOLATCH + // verilator lint_off NULLPORT + // verilator lint_off PINCONNECTEMPTY + // verilator lint_off PINMISSING + // verilator lint_off PINNOCONNECT + // verilator lint_off PINNOTFOUND + // verilator lint_off PKGNODECL + // verilator lint_off PROCASSWIRE + // verilator lint_off PROFOUTOFDATE + // verilator lint_off PROTECTED + // verilator lint_off RANDC + // verilator lint_off REALCVT + // verilator lint_off REDEFMACRO + // verilator lint_off SELRANGE + // verilator lint_off SHORTREAL + // verilator lint_off SPLITVAR + // verilator lint_off STMTDLY + // verilator lint_off SYMRSVDWORD + // verilator lint_off SYNCASYNCNET + // verilator lint_off TICKCOUNT + // verilator lint_off TIMESCALEMOD + // verilator lint_off UNDRIVEN + // verilator lint_off UNOPT + // verilator lint_off UNOPTFLAT + // verilator lint_off UNOPTTHREADS + // verilator lint_off UNPACKED + // verilator lint_off UNSIGNED + // verilator lint_off UNUSEDGENVAR + // verilator lint_off UNUSEDPARAM + // verilator lint_off UNUSEDSIGNAL + // verilator lint_off USERERROR + // verilator lint_off USERFATAL + // verilator lint_off USERINFO + // verilator lint_off USERWARN + // verilator lint_off VARHIDDEN + // verilator lint_off WAITCONST + // verilator lint_off WIDTH + // verilator lint_off WIDTHCONCAT + // verilator lint_off ZERODLY + +endmodule diff --git a/test_regress/t/t_lint_pragma_protected_err.out b/test_regress/t/t_lint_pragma_protected_err.out index 2e5922f37..6ce5430b8 100644 --- a/test_regress/t/t_lint_pragma_protected_err.out +++ b/test_regress/t/t_lint_pragma_protected_err.out @@ -11,7 +11,31 @@ | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ %Warning-PROTECTED: t/t_lint_pragma_protected_err.v:44:17: A '`pragma protected data_block' encrypted section was detected and will be skipped. ... Use "/* verilator lint_off PROTECTED */" and lint_on around source to disable this message. -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:58:1: `pragma is missing a pragma_expression. - 58 | `pragma +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:51:17: Illegal encoding type for `pragma protected encoding + 51 | `pragma protect encoding = (enctype = "A-bad-not-BASE64", line_length = 1, bytes = 295) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_lint_pragma_protected_err.v:51:17: Unsupported: only BASE64 is recognized for `pragma protected encoding + 51 | `pragma protect encoding = (enctype = "A-bad-not-BASE64", line_length = 1, bytes = 295) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%Warning-PROTECTED: t/t_lint_pragma_protected_err.v:53:17: A '`pragma protected data_block' encrypted section was detected and will be skipped. +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:54:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block + 54 | c2lvbiAzIG9mIHRoZSBHTlUgTGVzc2VyCkdlbmVyYWwgUHVibGljIExpY2Vuc2UsIGFuZCB0aGUg + | ^ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:55:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block + 55 | IkdOVSBHUEwiIHJlZmVycyB0byB2ZXJzaW9uIDMgb2YgdGhlIEdOVQpHZW5lcmFsIFB1YmxpYyBM + | ^ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:56:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block + 56 | aWNlbnNlLgoKICAiVGhlIExpYnJhcnkiIHJlZmVycyB0byBhIGNvdmVyZWQgd29yayBnb3Zlcm5l + | ^ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:57:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block + 57 | ZCBieSB0aGlzIExpY2Vuc2UsCm90aGVyIHRoYW4gYW4gQXBwbGljYXRpb24gb3IgYSBDb21iaW5l + | ^ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:58:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block + 58 | ZCBXb3JrIGFzIG== + | ^ +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:59:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:59:1: BASE64 encoding length mismatch in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:67:1: `pragma is missing a pragma_expression. + 67 | `pragma | ^~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_lint_pragma_protected_err.v b/test_regress/t/t_lint_pragma_protected_err.v index 5e761c364..3e07811a3 100644 --- a/test_regress/t/t_lint_pragma_protected_err.v +++ b/test_regress/t/t_lint_pragma_protected_err.v @@ -48,6 +48,15 @@ aWNlbnNlLgoKICAiVGhlIExpYnJhcnkiIHJlZmVycyB0byBhIGNvdmVyZWQgd29yayBnb3Zlcm5l ZCBieSB0aGlzIExpY2Vuc2UsCm90aGVyIHRoYW4gYW4gQXBwbGljYXRpb24gb3IgYSBDb21iaW5l ZCBXb3JrIGFzIG== +`pragma protect encoding = (enctype = "A-bad-not-BASE64", line_length = 1, bytes = 295) +`pragma protect data_block +aW5pdGlvbnMuCgogIEFzIHVzZWQgaGVyZWluLCAidGhpcyBMaWNlbnNlIiByZWZlcnMgdG8gdmVy +c2lvbiAzIG9mIHRoZSBHTlUgTGVzc2VyCkdlbmVyYWwgUHVibGljIExpY2Vuc2UsIGFuZCB0aGUg +IkdOVSBHUEwiIHJlZmVycyB0byB2ZXJzaW9uIDMgb2YgdGhlIEdOVQpHZW5lcmFsIFB1YmxpYyBM +aWNlbnNlLgoKICAiVGhlIExpYnJhcnkiIHJlZmVycyB0byBhIGNvdmVyZWQgd29yayBnb3Zlcm5l +ZCBieSB0aGlzIExpY2Vuc2UsCm90aGVyIHRoYW4gYW4gQXBwbGljYXRpb24gb3IgYSBDb21iaW5l +ZCBXb3JrIGFzIG== + `pragma protect end_protected diff --git a/test_regress/t/t_pp_defnettype_bad.out b/test_regress/t/t_pp_defnettype_bad.out new file mode 100644 index 000000000..e9d6d411e --- /dev/null +++ b/test_regress/t/t_pp_defnettype_bad.out @@ -0,0 +1,11 @@ +%Error-UNSUPPORTED: t/t_pp_defnettype_bad.v:7:1: Unsupported: `default_nettype of other than none or wire: '`default_nettype bad' + 7 | `default_nettype bad_none_such + | ^~~~~~~~~~~~~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_pp_defnettype_bad.v:9:1: Unsupported: Verilog optional directive not implemented: '`default_trireg_strength this_is_optional' + 9 | `default_trireg_strength this_is_optional + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%Error: t/t_pp_defnettype_bad.v:7:21: syntax error, unexpected IDENTIFIER + 7 | `default_nettype bad_none_such + | ^~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_pp_defnettype_bad.pl b/test_regress/t/t_pp_defnettype_bad.pl new file mode 100755 index 000000000..a60503a1f --- /dev/null +++ b/test_regress/t/t_pp_defnettype_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_pp_defnettype_bad.v b/test_regress/t/t_pp_defnettype_bad.v new file mode 100644 index 000000000..fc8aa9c1a --- /dev/null +++ b/test_regress/t/t_pp_defnettype_bad.v @@ -0,0 +1,9 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2019 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`default_nettype bad_none_such + +`default_trireg_strength this_is_optional diff --git a/test_regress/t/t_preproc_nodef_bad.out b/test_regress/t/t_preproc_nodef_bad.out new file mode 100644 index 000000000..1ee339862 --- /dev/null +++ b/test_regress/t/t_preproc_nodef_bad.out @@ -0,0 +1,4 @@ +%Error: t/t_preproc_nodef_bad.v:7:1: Define or directive not defined: '`not_defined' + 7 | `not_defined + | ^~~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_preproc_nodef_bad.pl b/test_regress/t/t_preproc_nodef_bad.pl new file mode 100755 index 000000000..59ba0d6c6 --- /dev/null +++ b/test_regress/t/t_preproc_nodef_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(linter => 1); + +lint( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_preproc_nodef_bad.v b/test_regress/t/t_preproc_nodef_bad.v new file mode 100644 index 000000000..3c0c23d5b --- /dev/null +++ b/test_regress/t/t_preproc_nodef_bad.v @@ -0,0 +1,10 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2003 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`not_defined + +module t; +endmodule diff --git a/test_regress/t/t_string_type_methods_bad.out b/test_regress/t/t_string_type_methods_bad.out index 69015ba0f..0c3389838 100644 --- a/test_regress/t/t_string_type_methods_bad.out +++ b/test_regress/t/t_string_type_methods_bad.out @@ -10,4 +10,8 @@ : ... In instance t 17 | s.itoa(1,2,3); | ^~~~ +%Error: t/t_string_type_methods_bad.v:18:9: Unknown built-in string method 'bad_no_such_method' + : ... In instance t + 18 | s.bad_no_such_method(); + | ^~~~~~~~~~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_string_type_methods_bad.v b/test_regress/t/t_string_type_methods_bad.v index 45c845d0b..ca6f819e7 100644 --- a/test_regress/t/t_string_type_methods_bad.v +++ b/test_regress/t/t_string_type_methods_bad.v @@ -15,6 +15,7 @@ module t (/*AUTOARG*/); i = s.len(0); // BAD s.itoa; // BAD s.itoa(1,2,3); // BAD + s.bad_no_such_method(); // BAD end endmodule diff --git a/test_regress/t/t_unbounded_bad.out b/test_regress/t/t_unbounded_bad.out new file mode 100644 index 000000000..bd98ae872 --- /dev/null +++ b/test_regress/t/t_unbounded_bad.out @@ -0,0 +1,11 @@ +%Error-UNSUPPORTED: t/t_unbounded_bad.v:9:11: Unsupported/illegal unbounded ('$') in this context. + : ... In instance t + 9 | if ($) $stop; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Warning-WIDTH: t/t_unbounded_bad.v:9:7: Logical operator IF expects 1 bit on the If, but If's UNBOUNDED generates 32 bits. + : ... In instance t + 9 | if ($) $stop; + | ^~ + ... Use "/* verilator lint_off WIDTH */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_unbounded_bad.pl b/test_regress/t/t_unbounded_bad.pl new file mode 100755 index 000000000..a60503a1f --- /dev/null +++ b/test_regress/t/t_unbounded_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_unbounded_bad.v b/test_regress/t/t_unbounded_bad.v new file mode 100644 index 000000000..7927e7bfb --- /dev/null +++ b/test_regress/t/t_unbounded_bad.v @@ -0,0 +1,11 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial begin + if ($) $stop; + end +endmodule From 0716a28816bca9461ac618673f5a4da4d3a4f210 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 22 Oct 2022 13:46:49 -0400 Subject: [PATCH 163/177] Tests: Check for v3warn/v3error without a test --- test_regress/t/t_dist_warn_coverage.pl | 258 +++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100755 test_regress/t/t_dist_warn_coverage.pl diff --git a/test_regress/t/t_dist_warn_coverage.pl b/test_regress/t/t_dist_warn_coverage.pl new file mode 100755 index 000000000..4eb63cc6b --- /dev/null +++ b/test_regress/t/t_dist_warn_coverage.pl @@ -0,0 +1,258 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +use IO::File; +use strict; +use vars qw($Self); + +scenarios(dist => 1); + +my $root = ".."; +my $Debug = $Self->{verbose}; +my %Messages; +my %Outputs; + +my %Suppressed; +foreach my $s ( + ' exited with ', # driver.pl filters out + 'EOF in unterminated string', # Instead get normal unterminated + # Not yet analyzed + ' is not an in/out/inout/param/interface: ', + ' loading non-variable', + '$display-like format of %c format of > 8 bit value', + '$fopen mode should be <= 4 characters', + '\'foreach\' loop variable expects simple variable name', + '--coverage and --savable not supported together', + '--output-split-cfuncs must be >= 0: ', + '--output-split-ctrace must be >= 0: ', + '--pipe-filter protocol error, unexpected: ', + '--reloop-limit must be >= 2: ', + '-j requires a non-negative integer argument, but \'', + '/*verilator sformat*/ can only be applied to last argument of ', + 'Argument needed for string.', + 'Array initialization has too few elements, need element ', + 'Assert not allowed under another assert', + 'Assigned pin is neither input nor output', + 'Assignment pattern key used multiple times: ', + 'Assignment pattern with no members', + 'Assignment pattern with too many elements', + 'Attempted parameter setting of non-parameter: Param ', + 'Attempting to extend using a non-class ', + 'BASE64 line too long in `pragma protect key_bloock/data_block', + 'Can\'t find varpin scope of ', + 'Can\'t resolve module reference: \'', + 'Cannot mix DPI import, DPI export, class methods, and/or public ', + 'Cannot write preprocessor output: ', + 'Circular logic when ordering code (non-cutable edge loop)', + 'Connect by position is illegal in .* connected instances', + 'Deferred assertions must use \'#0\' (IEEE 1800-2017 16.4)', + 'Define or directive not defined: `', + 'Duplicate declaration of member name: ', + 'EOF in (*', + 'Enum names without values only allowed on numeric types', + 'Enum ranges must be integral, per spec', + 'Exceeded limit of ', + 'Extern declaration\'s scope is not a defined class', + 'Format to $display-like function must have constant format string', + 'Forward typedef used as class/package does not resolve to class/package: ', + 'Genvars may not be arrayed: ', + 'Illegal +: or -: select; type already selected, or bad dimension: ', + 'Illegal base character: ', + 'Illegal bit or array select; type already selected, or bad dimension: ', + 'Illegal character in decimal constant: ', + 'Illegal character in hex constant: ', + 'Illegal range select; type already selected, or bad dimension: ', + 'In defparam, instance ', + 'Interface port ', + 'Member selection of non-struct/union object \'', + 'Modport item is not a function/task: ', + 'Modport item is not a variable: ', + 'Modport item not found: ', + 'Modport not referenced as .', + 'Modport not referenced from underneath an interface: ', + 'Multiple \'{ default: } clauses', + 'Non-interface used as an interface: ', + 'Not marked as function return var', + 'Parameter not found in sub-module: Param ', + 'Parameter type pin value isn\'t a type: Param ', + 'Parameter type variable isn\'t a type: Param ', + 'Pattern replication value of 0 is not legal.', + 'Real not allowed as operand to in ?== operator', + 'Replication value isn\'t a constant.', + 'Replication value of 0 is only legal under a concatenation (IEEE ', + 'Return with return value isn\'t underneath a function', + 'Select from non-array ', + 'Signals inside functions/tasks cannot be marked forceable', + 'Size-changing cast to zero or negative size', + 'Slice size cannot be zero.', + 'Slices of arrays in assignments have different unpacked dimensions, ', + 'String of ', + 'Symbol matching ', + 'Syntax Error: Range \':\', \'+:\' etc are not allowed in the instance ', + 'Syntax error parsing real: \'', + 'Syntax error: \'virtual\' not allowed before var declaration', + 'This may be because there\'s no search path specified with -I.', + 'Thread scheduler is unable to provide requested ', + 'Unexpected connection to arrayed port', + 'Unhandled attribute type', + 'Unknown Error Code: ', + 'Unknown `pragma', + 'Unknown built-in event method ', + 'Unsized numbers/parameters not allowed in streams.', + 'Unsupported LHS tristate construct: ', + 'Unsupported RHS tristate construct: ', + 'Unsupported or syntax error: Unsized range in instance or other declaration', + 'Unsupported pullup/down (weak driver) construct.', + 'Unsupported tristate construct (not in propagation graph): ', + 'Unsupported tristate port expression: ', + 'Unsupported/Illegal: Assignment pattern', + 'Unsupported/unknown built-in dynamic array method ', + 'Unsupported: $bits for queue', + 'Unsupported: $c can\'t generate wider than 64 bits', + 'Unsupported: %l in $fscanf', + 'Unsupported: %m in $fscanf', + 'Unsupported: --no-structs-packed', + 'Unsupported: 4-state numbers in this context', + 'Unsupported: Concatenation to form ', + 'Unsupported: Non-variable on LHS of built-in method \'', + 'Unsupported: Only one PSL clock allowed per assertion', + 'Unsupported: Per-bit array instantiations ', + 'Unsupported: Public functions with >64 bit outputs; ', + 'Unsupported: RHS of ==? or !=? must be ', + 'Unsupported: Ranges ignored in port-lists', + 'Unsupported: Replication to form ', + 'Unsupported: Shifting of by over 32-bit number isn\'t supported.', + 'Unsupported: Signal strengths are unsupported ', + 'Unsupported: Size-changing cast on non-basic data type', + 'Unsupported: Slice of non-constant bounds', + 'Unsupported: Unclocked assertion', + 'Unsupported: don\'t know how to deal with ', + 'Unsupported: event arrays', + 'Unsupported: left < right of bit extract: ', + 'Unsupported: modport export', + 'Unsupported: static cast to ', + 'Unsupported: super', + 'Unterminated /* comment inside -f file.', + 'Width of :+ or :- is < 0: ', + 'Width of :+ or :- is huge; vector of over 1billion bits: ', + 'Width of bit extract isn\'t a constant', + 'Width of bit range is huge; vector of over 1billion bits: 0x', + 'dynamic new() not expected in this context (data type must be dynamic array)', + 'dynamic new() not expected in this context (expected under an assign)', + 'line_length must be multiple of 4 for BASE64', + 'missing -module', + 'missing -var', + 'new() not expected in this context', + 'no_inline not supported for tasks', + 'of %c format of > 8 bit value', + ) { $Suppressed{$s} = 1; } + +if (!-r "$root/.git") { + skip("Not in a git repository"); +} else { + check(); +} + +ok(1); +1; + +sub check { + read_messages(); + read_outputs(); + + print "Number of suppressions = ", scalar(keys %Suppressed), "\n"; + print "Coverage = ", 100 - int(100 * scalar(keys %Suppressed) / scalar(keys %Messages)), "%\n"; + print "\n"; + + print "Checking for v3error/v3warn messages in sources without coverage in test_regress/t/*.out:\n"; + print "\n"; + + my %used_suppressed; + msg: + for my $msg (sort {$Messages{$a}{fileline} cmp $Messages{$b}{fileline}} keys %Messages) { + my $fileline = $Messages{$msg}{fileline}; + for my $output (keys %Outputs) { + if (index($output, $msg) != -1) { + # print "$fileline: M '$msg' HIT '$output'\n"; + next msg; + } + } + # Some exceptions + next msg if ($msg =~ /internal:/i); + + my $line = $Messages{$msg}{line}; + chomp $line; + $line =~ s/^\s+//; + + if (%Suppressed{$msg}) { + $used_suppressed{$msg} = 1; + print "$fileline: Suppressed check for message in source: '$msg'\n" if $Debug; + } else { + error("$fileline: Missing test_regress/t/*.out test for message in source: '$msg'"); + print(" Line is: ", $line, "\n") if $Debug; + } + } + + for my $msg (sort keys %Suppressed) { + if (!$used_suppressed{$msg}) { + print "Suppression not used: '$msg'\n"; + } + } +} + +sub read_messages { + foreach my $filename (glob "$root/src/*") { + my $fh = IO::File->new("<$filename") + or error("$! $filename"); + my $lineno = 0; + my $read_next; + line: + while (my $origline = ($fh && $fh->getline)) { + my $line = $origline; + ++$lineno; + if ($line =~ /\b(v3error|v3warn)\b\($/g) { + $read_next = 1 if $line !~ /LCOV_EXCL_LINE/; + next line; + } + if ($line =~ s/.*\b(v3error|v3warn)\b//g) { + $read_next = 1 if $line !~ /LCOV_EXCL_LINE/; + } + if ($read_next) { + $read_next = 0; + next if $line =~ /LCOV_EXCL_LINE/; + next if $line =~ /\\/; # \" messes up next part + if ($line =~ /"([^"]*)"/) { + my $msg = $1; + my $fileline = $filename . ":" . $lineno; + # print "FFFF $fileline: $msg LL $line\n"; + $Messages{$msg}{fileline} = $fileline; + $Messages{$msg}{line} = $origline; + } + } + } + } + print "Number of messages = ",scalar(keys %Messages), "\n"; +} + +sub read_outputs { + file: + foreach my $filename (glob ("$root/test_regress/t/*.out $root/docs/gen/*.rst")) { + my $fh = IO::File->new("<$filename") + or error("$! $filename"); + while (my $line = ($fh && $fh->getline)) { + if ($line =~ /^\$date/) { + # Assume it is a VCD file + next file; + } + $Outputs{$line} = 1; + } + } + print "Number of outputs = ",scalar(keys %Outputs), "\n"; +} From d33a3d09f7bf2548e7b122198fb492e9d419a05e Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 22 Oct 2022 14:35:36 -0400 Subject: [PATCH 164/177] Tests: Fix bison-dependent .out --- test_regress/t/t_dist_warn_coverage.pl | 6 ++++-- test_regress/t/t_udp_tableeof_bad.out | 3 --- test_regress/t/t_udp_tableeof_bad.pl | 3 ++- 3 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 test_regress/t/t_udp_tableeof_bad.out diff --git a/test_regress/t/t_dist_warn_coverage.pl b/test_regress/t/t_dist_warn_coverage.pl index 4eb63cc6b..09231ecb7 100755 --- a/test_regress/t/t_dist_warn_coverage.pl +++ b/test_regress/t/t_dist_warn_coverage.pl @@ -99,7 +99,6 @@ foreach my $s ( 'Syntax error parsing real: \'', 'Syntax error: \'virtual\' not allowed before var declaration', 'This may be because there\'s no search path specified with -I.', - 'Thread scheduler is unable to provide requested ', 'Unexpected connection to arrayed port', 'Unhandled attribute type', 'Unknown Error Code: ', @@ -243,7 +242,10 @@ sub read_messages { sub read_outputs { file: - foreach my $filename (glob ("$root/test_regress/t/*.out $root/docs/gen/*.rst")) { + foreach my $filename (glob ("$root/test_regress/t/*.pl" + . " $root/test_regress/t/*.out" + . " $root/docs/gen/*.rst")) { + next if $filename =~ /t_dist_warn_coverage/; # Avoid our own suppressions my $fh = IO::File->new("<$filename") or error("$! $filename"); while (my $line = ($fh && $fh->getline)) { diff --git a/test_regress/t/t_udp_tableeof_bad.out b/test_regress/t/t_udp_tableeof_bad.out deleted file mode 100644 index bf61e32d3..000000000 --- a/test_regress/t/t_udp_tableeof_bad.out +++ /dev/null @@ -1,3 +0,0 @@ -%Error: t/t_udp_tableeof_bad.v:16:1: EOF in 'table' -%Error: t/t_udp_tableeof_bad.v:16:1: syntax error, unexpected end of file, expecting TABLE LINE or endtable -%Error: Cannot continue diff --git a/test_regress/t/t_udp_tableeof_bad.pl b/test_regress/t/t_udp_tableeof_bad.pl index fc220ea47..677258838 100755 --- a/test_regress/t/t_udp_tableeof_bad.pl +++ b/test_regress/t/t_udp_tableeof_bad.pl @@ -13,7 +13,8 @@ scenarios(linter => 1); lint( verilator_flags2 => ["--lint-only --bbox-unsup"], fails => $Self->{vlt_all}, - expect_filename => $Self->{golden_filename}, + # Cannot use .out, get "$end" or "end of file" depending on bison version + expect => qr/EOF in 'table'/, ); ok(1); From 3487701b04370652539be785719671363168d85b Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 22 Oct 2022 16:03:42 -0400 Subject: [PATCH 165/177] Tests: Cover some previously uncovered warnings --- src/V3Config.cpp | 2 +- src/V3LinkCells.cpp | 8 ++-- src/V3Number.cpp | 13 +++++-- src/V3Options.cpp | 10 +---- src/V3ParseGrammar.cpp | 3 +- src/V3Width.cpp | 8 +++- test_regress/t/t_display_cwide_bad.out | 6 +++ test_regress/t/t_display_cwide_bad.pl | 19 ++++++++++ test_regress/t/t_display_cwide_bad.v | 14 +++++++ test_regress/t/t_dist_warn_coverage.pl | 22 ++--------- test_regress/t/t_flag_f_bad_cmt.out | 2 + test_regress/t/t_flag_f_bad_cmt.pl | 20 ++++++++++ test_regress/t/t_flag_f_bad_cmt.v | 34 +++++++++++++++++ test_regress/t/t_flag_f_bad_cmt.vc | 1 + test_regress/t/t_flag_values_bad.out | 4 ++ test_regress/t/t_flag_values_bad.pl | 22 +++++++++++ test_regress/t/t_inst_2star_bad.out | 7 +++- test_regress/t/t_inst_2star_bad.v | 4 ++ ...rr.out => t_lint_pragma_protected_bad.out} | 37 ++++++++++--------- ..._err.pl => t_lint_pragma_protected_bad.pl} | 0 ...ed_err.v => t_lint_pragma_protected_bad.v} | 5 +++ test_regress/t/t_math_eq_bad.out | 5 +++ test_regress/t/t_math_eq_bad.pl | 19 ++++++++++ test_regress/t/t_math_eq_bad.v | 16 ++++++++ test_regress/t/t_math_repl_bad.out | 22 +++++++++++ test_regress/t/t_math_repl_bad.pl | 19 ++++++++++ test_regress/t/t_math_repl_bad.v | 16 ++++++++ test_regress/t/t_vlt_syntax_bad.out | 6 +++ test_regress/t/t_vlt_syntax_bad.vlt | 4 ++ 29 files changed, 294 insertions(+), 54 deletions(-) create mode 100644 test_regress/t/t_display_cwide_bad.out create mode 100755 test_regress/t/t_display_cwide_bad.pl create mode 100644 test_regress/t/t_display_cwide_bad.v create mode 100644 test_regress/t/t_flag_f_bad_cmt.out create mode 100755 test_regress/t/t_flag_f_bad_cmt.pl create mode 100644 test_regress/t/t_flag_f_bad_cmt.v create mode 100644 test_regress/t/t_flag_f_bad_cmt.vc create mode 100644 test_regress/t/t_flag_values_bad.out create mode 100755 test_regress/t/t_flag_values_bad.pl rename test_regress/t/{t_lint_pragma_protected_err.out => t_lint_pragma_protected_bad.out} (61%) rename test_regress/t/{t_lint_pragma_protected_err.pl => t_lint_pragma_protected_bad.pl} (100%) rename test_regress/t/{t_lint_pragma_protected_err.v => t_lint_pragma_protected_bad.v} (93%) create mode 100644 test_regress/t/t_math_eq_bad.out create mode 100755 test_regress/t/t_math_eq_bad.pl create mode 100644 test_regress/t/t_math_eq_bad.v create mode 100644 test_regress/t/t_math_repl_bad.out create mode 100755 test_regress/t/t_math_repl_bad.pl create mode 100644 test_regress/t/t_math_repl_bad.v diff --git a/src/V3Config.cpp b/src/V3Config.cpp index 9419a6e6b..73d1ed934 100644 --- a/src/V3Config.cpp +++ b/src/V3Config.cpp @@ -564,7 +564,7 @@ void V3Config::addVarAttr(FileLine* fl, const string& module, const string& ftas } else { if (attr == VAttrType::VAR_FORCEABLE) { if (module.empty()) { - fl->v3error("missing -module"); + fl->v3error("forceable missing -module"); } else if (!ftask.empty()) { fl->v3error("Signals inside functions/tasks cannot be marked forceable"); } else { diff --git a/src/V3LinkCells.cpp b/src/V3LinkCells.cpp index c92a41a10..e22497eb9 100644 --- a/src/V3LinkCells.cpp +++ b/src/V3LinkCells.cpp @@ -355,7 +355,7 @@ private: for (AstPin *nextp, *pinp = nodep->pinsp(); pinp; pinp = nextp) { nextp = VN_AS(pinp->nextp(), Pin); if (pinp->dotStar()) { - if (pinStar) pinp->v3error("Duplicate .* in an instance"); + if (pinStar) pinp->v3error("Duplicate .* in an instance (IEEE 1800-2017 23.3.2)"); pinStar = true; // Done with this fake pin VL_DO_DANGLING(pinp->unlinkFrBack()->deleteTree(), pinp); @@ -374,8 +374,10 @@ private: // Note what pins exist std::unordered_set ports; // Symbol table of all connected port names for (AstPin* pinp = nodep->pinsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) { - if (pinp->name() == "") - pinp->v3error("Connect by position is illegal in .* connected instances"); + if (pinStar && pinp->name().substr(0, 11) == "__pinNumber") { + pinp->v3error("Connect by position is illegal in .* connected instances" + " (IEEE 1800-2017 23.3.2)"); + } if (!pinp->exprp()) { if (pinp->name().substr(0, 11) == "__pinNumber") { pinp->v3warn(PINNOCONNECT, diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 4ce1c1423..63c0c381d 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -269,7 +269,8 @@ void V3Number::create(const char* sourcep) { } case '_': break; default: { - v3error("Illegal character in decimal constant: " << *cp); + // Likely impossible as parser prevents hitting it + v3error("Illegal character in decimal constant: " << *cp); // LCOV_EXCL_LINE break; } } @@ -346,11 +347,17 @@ void V3Number::create(const char* sourcep) { case 'x': setBit(obit++,'x'); setBit(obit++,'x'); setBit(obit++,'x'); setBit(obit++,'x'); break; // clang-format on case '_': break; - default: v3error("Illegal character in hex constant: " << *cp); + default: + // Likely impossible as parser prevents hitting it + v3error("Illegal character in hex constant: " << *cp); // LCOV_EXCL_LINE + break; } break; } - default: v3error("Illegal base character: " << base); + default: + // Likely impossible as parser prevents hitting it + v3error("Illegal base character: " << base); // LCOV_EXCL_LINE + break; } } } diff --git a/src/V3Options.cpp b/src/V3Options.cpp index deb565700..f009a5f2d 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1594,14 +1594,8 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char ++i; int val = 0; if (i < argc && isdigit(argv[i][0])) { - val = atoi(argv[i]); - if (val < 0) { - fl->v3error("-j requires a non-negative integer argument, but '" - << argv[i] << "' was passed"); - val = 1; // Fall-back value, though we will exit on error. - } else if (val == 0) { - val = std::thread::hardware_concurrency(); - } + val = atoi(argv[i]); // Can't be negative due to isdigit above + if (val == 0) val = std::thread::hardware_concurrency(); ++i; } if (m_buildJobs == -1) m_buildJobs = val; diff --git a/src/V3ParseGrammar.cpp b/src/V3ParseGrammar.cpp index 984544d82..7781f6eb8 100644 --- a/src/V3ParseGrammar.cpp +++ b/src/V3ParseGrammar.cpp @@ -184,7 +184,8 @@ AstVar* V3ParseGrammar::createVariable(FileLine* fileline, const string& name, } } if (type == VVarType::GENVAR) { - if (arrayp) fileline->v3error("Genvars may not be arrayed: " << name); + // Should be impossible as the grammer blocks this, but... + if (arrayp) fileline->v3error("Genvars may not be arrayed: " << name); // LCOV_EXCL_LINE } // Split RANGE0-RANGE1-RANGE2 into diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 8e1a0ffa4..247063bb8 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -5382,7 +5382,13 @@ private: userIterateAndNext(nodep->lhsp(), WidthVP(CONTEXT, PRELIM).p()); userIterateAndNext(nodep->rhsp(), WidthVP(CONTEXT, PRELIM).p()); if (nodep->lhsp()->isDouble() || nodep->rhsp()->isDouble()) { - if (!realok) nodep->v3error("Real not allowed as operand to in ?== operator"); + if (!realok) { + nodep->v3error("Real is illegal operand to ?== operator"); + AstNode* const newp = new AstConst{nodep->fileline(), AstConst::BitFalse{}}; + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } if (AstNodeBiop* const newp = replaceWithDVersion(nodep)) { VL_DANGLING(nodep); nodep = newp; // Process new node instead diff --git a/test_regress/t/t_display_cwide_bad.out b/test_regress/t/t_display_cwide_bad.out new file mode 100644 index 000000000..f054ce1b7 --- /dev/null +++ b/test_regress/t/t_display_cwide_bad.out @@ -0,0 +1,6 @@ +%Warning-WIDTH: t/t_display_cwide_bad.v:10:7: $display-like format of %c format of > 8 bit value + 10 | $display("%c", 32'h1234); + | ^~~~~~~~ + ... For warning description see https://verilator.org/warn/WIDTH?v=latest + ... Use "/* verilator lint_off WIDTH */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_display_cwide_bad.pl b/test_regress/t/t_display_cwide_bad.pl new file mode 100755 index 000000000..a5846c699 --- /dev/null +++ b/test_regress/t/t_display_cwide_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vlt => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_display_cwide_bad.v b/test_regress/t/t_display_cwide_bad.v new file mode 100644 index 000000000..0cd07beb4 --- /dev/null +++ b/test_regress/t/t_display_cwide_bad.v @@ -0,0 +1,14 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2003 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + initial begin + // Display formatting + $display("%c", 32'h1234); // Bad wide %c + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_dist_warn_coverage.pl b/test_regress/t/t_dist_warn_coverage.pl index 09231ecb7..52cf64281 100755 --- a/test_regress/t/t_dist_warn_coverage.pl +++ b/test_regress/t/t_dist_warn_coverage.pl @@ -26,15 +26,10 @@ foreach my $s ( # Not yet analyzed ' is not an in/out/inout/param/interface: ', ' loading non-variable', - '$display-like format of %c format of > 8 bit value', '$fopen mode should be <= 4 characters', '\'foreach\' loop variable expects simple variable name', '--coverage and --savable not supported together', - '--output-split-cfuncs must be >= 0: ', - '--output-split-ctrace must be >= 0: ', '--pipe-filter protocol error, unexpected: ', - '--reloop-limit must be >= 2: ', - '-j requires a non-negative integer argument, but \'', '/*verilator sformat*/ can only be applied to last argument of ', 'Argument needed for string.', 'Array initialization has too few elements, need element ', @@ -45,13 +40,11 @@ foreach my $s ( 'Assignment pattern with too many elements', 'Attempted parameter setting of non-parameter: Param ', 'Attempting to extend using a non-class ', - 'BASE64 line too long in `pragma protect key_bloock/data_block', 'Can\'t find varpin scope of ', 'Can\'t resolve module reference: \'', 'Cannot mix DPI import, DPI export, class methods, and/or public ', 'Cannot write preprocessor output: ', 'Circular logic when ordering code (non-cutable edge loop)', - 'Connect by position is illegal in .* connected instances', 'Deferred assertions must use \'#0\' (IEEE 1800-2017 16.4)', 'Define or directive not defined: `', 'Duplicate declaration of member name: ', @@ -62,12 +55,8 @@ foreach my $s ( 'Extern declaration\'s scope is not a defined class', 'Format to $display-like function must have constant format string', 'Forward typedef used as class/package does not resolve to class/package: ', - 'Genvars may not be arrayed: ', 'Illegal +: or -: select; type already selected, or bad dimension: ', - 'Illegal base character: ', 'Illegal bit or array select; type already selected, or bad dimension: ', - 'Illegal character in decimal constant: ', - 'Illegal character in hex constant: ', 'Illegal range select; type already selected, or bad dimension: ', 'In defparam, instance ', 'Interface port ', @@ -84,9 +73,6 @@ foreach my $s ( 'Parameter type pin value isn\'t a type: Param ', 'Parameter type variable isn\'t a type: Param ', 'Pattern replication value of 0 is not legal.', - 'Real not allowed as operand to in ?== operator', - 'Replication value isn\'t a constant.', - 'Replication value of 0 is only legal under a concatenation (IEEE ', 'Return with return value isn\'t underneath a function', 'Select from non-array ', 'Signals inside functions/tasks cannot be marked forceable', @@ -138,7 +124,6 @@ foreach my $s ( 'Unsupported: modport export', 'Unsupported: static cast to ', 'Unsupported: super', - 'Unterminated /* comment inside -f file.', 'Width of :+ or :- is < 0: ', 'Width of :+ or :- is huge; vector of over 1billion bits: ', 'Width of bit extract isn\'t a constant', @@ -146,11 +131,8 @@ foreach my $s ( 'dynamic new() not expected in this context (data type must be dynamic array)', 'dynamic new() not expected in this context (expected under an assign)', 'line_length must be multiple of 4 for BASE64', - 'missing -module', - 'missing -var', 'new() not expected in this context', 'no_inline not supported for tasks', - 'of %c format of > 8 bit value', ) { $Suppressed{$s} = 1; } if (!-r "$root/.git") { @@ -258,3 +240,7 @@ sub read_outputs { } print "Number of outputs = ",scalar(keys %Outputs), "\n"; } + +# Local Variables: +# compile-command:"./t_dist_warn_coverage.pl" +# End: diff --git a/test_regress/t/t_flag_f_bad_cmt.out b/test_regress/t/t_flag_f_bad_cmt.out new file mode 100644 index 000000000..b214fc6c3 --- /dev/null +++ b/test_regress/t/t_flag_f_bad_cmt.out @@ -0,0 +1,2 @@ +%Error: Unterminated /* comment inside -f file. +%Error: Exiting due to diff --git a/test_regress/t/t_flag_f_bad_cmt.pl b/test_regress/t/t_flag_f_bad_cmt.pl new file mode 100755 index 000000000..24d8b0de5 --- /dev/null +++ b/test_regress/t/t_flag_f_bad_cmt.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2008 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 + +scenarios(vlt => 1); + +lint( + v_flags2 => ["-f t/t_flag_f_bad_cmt.vc"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_flag_f_bad_cmt.v b/test_regress/t/t_flag_f_bad_cmt.v new file mode 100644 index 000000000..e5ec47252 --- /dev/null +++ b/test_regress/t/t_flag_f_bad_cmt.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2020 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`include "t_flag_f_tsub_inc.v" + +module t; + initial begin +`ifndef GOT_DEF1 + $write("%%Error: NO GOT_DEF1\n"); $stop; +`endif +`ifndef GOT_DEF2 + $write("%%Error: NO GOT_DEF2\n"); $stop; +`endif +`ifndef GOT_DEF3 + $write("%%Error: NO GOT_DEF3\n"); $stop; +`endif +`ifndef GOT_DEF4 + $write("%%Error: NO GOT_DEF4\n"); $stop; +`endif +`ifndef GOT_DEF5 + $write("%%Error: NO GOT_DEF5\n"); $stop; +`endif +`ifndef GOT_DEF6 + $write("%%Error: NO GOT_DEF6\n"); $stop; +`endif +`ifdef NON_DEF + $write("%%Error: NON_DEF\n"); $stop; +`endif + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_flag_f_bad_cmt.vc b/test_regress/t/t_flag_f_bad_cmt.vc new file mode 100644 index 000000000..bc253b215 --- /dev/null +++ b/test_regress/t/t_flag_f_bad_cmt.vc @@ -0,0 +1 @@ +/* Multiline unterminated comment diff --git a/test_regress/t/t_flag_values_bad.out b/test_regress/t/t_flag_values_bad.out new file mode 100644 index 000000000..8febf43ce --- /dev/null +++ b/test_regress/t/t_flag_values_bad.out @@ -0,0 +1,4 @@ +%Error: --output-split-cfuncs must be >= 0: -1 +%Error: --output-split-ctrace must be >= 0: -1 +%Error: --reloop-limit must be >= 2: -1 +%Error: Exiting due to diff --git a/test_regress/t/t_flag_values_bad.pl b/test_regress/t/t_flag_values_bad.pl new file mode 100755 index 000000000..a5f3d7743 --- /dev/null +++ b/test_regress/t/t_flag_values_bad.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vlt => 1); + +lint( + verilator_flags2 => ["--output-split-cfuncs -1", + "--output-split-ctrace -1", + "--reloop-limit -1",], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_inst_2star_bad.out b/test_regress/t/t_inst_2star_bad.out index cebc66d7d..fc7cc9140 100644 --- a/test_regress/t/t_inst_2star_bad.out +++ b/test_regress/t/t_inst_2star_bad.out @@ -1,4 +1,7 @@ -%Error: t/t_inst_2star_bad.v:9:17: Duplicate .* in an instance - 9 | sub sub (.*, .*); +%Error: t/t_inst_2star_bad.v:11:17: Duplicate .* in an instance (IEEE 1800-2017 23.3.2) + 11 | sub sub (.*, .*); | ^~ +%Error: t/t_inst_2star_bad.v:13:13: Connect by position is illegal in .* connected instances (IEEE 1800-2017 23.3.2) + 13 | sub sub (foo, .*); + | ^~~ %Error: Exiting due to diff --git a/test_regress/t/t_inst_2star_bad.v b/test_regress/t/t_inst_2star_bad.v index c9e6858e9..5c91aaac5 100644 --- a/test_regress/t/t_inst_2star_bad.v +++ b/test_regress/t/t_inst_2star_bad.v @@ -6,8 +6,12 @@ module t (/*AUTOARG*/); + wire foo; + sub sub (.*, .*); + sub sub (foo, .*); + endmodule module sub (input foo); diff --git a/test_regress/t/t_lint_pragma_protected_err.out b/test_regress/t/t_lint_pragma_protected_bad.out similarity index 61% rename from test_regress/t/t_lint_pragma_protected_err.out rename to test_regress/t/t_lint_pragma_protected_bad.out index 6ce5430b8..bd33b755d 100644 --- a/test_regress/t/t_lint_pragma_protected_err.out +++ b/test_regress/t/t_lint_pragma_protected_bad.out @@ -1,41 +1,44 @@ -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:8:17: Unknown '`pragma protect' error +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:8:17: Unknown '`pragma protect' error 8 | `pragma protect encrypt_agent=123 | ^~~~~~~~~~~~~~~~~ ... For error description see https://verilator.org/warn/BADSTDPRAGMA?v=latest -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:10:17: Unknown '`pragma protect' error +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:10:17: Unknown '`pragma protect' error 10 | `pragma protect encrypt_agent_info | ^~~~~~~~~~~~~~~~~~ -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:23:1: BASE64 encoding length mismatch in `pragma protect key_bloock/data_block -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:27:17: multiple `pragma protected encoding sections +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:23:1: BASE64 encoding length mismatch in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:27:17: multiple `pragma protected encoding sections 27 | `pragma protect encoding = (enctype = "BASE64", line_length = 76, bytes = 128) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -%Warning-PROTECTED: t/t_lint_pragma_protected_err.v:44:17: A '`pragma protected data_block' encrypted section was detected and will be skipped. +%Warning-PROTECTED: t/t_lint_pragma_protected_bad.v:44:17: A '`pragma protected data_block' encrypted section was detected and will be skipped. ... Use "/* verilator lint_off PROTECTED */" and lint_on around source to disable this message. -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:51:17: Illegal encoding type for `pragma protected encoding +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:51:17: Illegal encoding type for `pragma protected encoding 51 | `pragma protect encoding = (enctype = "A-bad-not-BASE64", line_length = 1, bytes = 295) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_lint_pragma_protected_err.v:51:17: Unsupported: only BASE64 is recognized for `pragma protected encoding +%Error-UNSUPPORTED: t/t_lint_pragma_protected_bad.v:51:17: Unsupported: only BASE64 is recognized for `pragma protected encoding 51 | `pragma protect encoding = (enctype = "A-bad-not-BASE64", line_length = 1, bytes = 295) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -%Warning-PROTECTED: t/t_lint_pragma_protected_err.v:53:17: A '`pragma protected data_block' encrypted section was detected and will be skipped. -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:54:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block +%Warning-PROTECTED: t/t_lint_pragma_protected_bad.v:53:17: A '`pragma protected data_block' encrypted section was detected and will be skipped. +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:54:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block 54 | c2lvbiAzIG9mIHRoZSBHTlUgTGVzc2VyCkdlbmVyYWwgUHVibGljIExpY2Vuc2UsIGFuZCB0aGUg | ^ -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:55:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:55:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block 55 | IkdOVSBHUEwiIHJlZmVycyB0byB2ZXJzaW9uIDMgb2YgdGhlIEdOVQpHZW5lcmFsIFB1YmxpYyBM | ^ -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:56:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:56:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block 56 | aWNlbnNlLgoKICAiVGhlIExpYnJhcnkiIHJlZmVycyB0byBhIGNvdmVyZWQgd29yayBnb3Zlcm5l | ^ -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:57:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:57:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block 57 | ZCBieSB0aGlzIExpY2Vuc2UsCm90aGVyIHRoYW4gYW4gQXBwbGljYXRpb24gb3IgYSBDb21iaW5l | ^ -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:58:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:58:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block 58 | ZCBXb3JrIGFzIG== | ^ -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:59:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:59:1: BASE64 encoding length mismatch in `pragma protect key_bloock/data_block -%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_err.v:67:1: `pragma is missing a pragma_expression. - 67 | `pragma +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:59:1: BASE64 encoding (too short) in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:59:1: BASE64 encoding length mismatch in `pragma protect key_bloock/data_block +%Warning-PROTECTED: t/t_lint_pragma_protected_bad.v:63:17: A '`pragma protected data_block' encrypted section was detected and will be skipped. +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:64:1: BASE64 line too long in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:64:1: BASE64 encoding length mismatch in `pragma protect key_bloock/data_block +%Error-BADSTDPRAGMA: t/t_lint_pragma_protected_bad.v:72:1: `pragma is missing a pragma_expression. + 72 | `pragma | ^~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_lint_pragma_protected_err.pl b/test_regress/t/t_lint_pragma_protected_bad.pl similarity index 100% rename from test_regress/t/t_lint_pragma_protected_err.pl rename to test_regress/t/t_lint_pragma_protected_bad.pl diff --git a/test_regress/t/t_lint_pragma_protected_err.v b/test_regress/t/t_lint_pragma_protected_bad.v similarity index 93% rename from test_regress/t/t_lint_pragma_protected_err.v rename to test_regress/t/t_lint_pragma_protected_bad.v index 3e07811a3..ca0099638 100644 --- a/test_regress/t/t_lint_pragma_protected_err.v +++ b/test_regress/t/t_lint_pragma_protected_bad.v @@ -58,6 +58,11 @@ ZCBieSB0aGlzIExpY2Vuc2UsCm90aGVyIHRoYW4gYW4gQXBwbGljYXRpb24gb3IgYSBDb21iaW5l ZCBXb3JrIGFzIG== +`pragma protect encoding = (enctype = "BASE64", line_length = 76, bytes = 76) +`pragma protect data_block +aW5pdGlvbnMuCgogIEFzIHVzZWQgaGVyZWluLCAidGhpcyBMaWNlbnNlIiByZWZlcnMgdG8gdmVyTOOLONG + + `pragma protect end_protected // Should trigger unknown pragma warning, although in principle unknown pragmas should be safely ignored. diff --git a/test_regress/t/t_math_eq_bad.out b/test_regress/t/t_math_eq_bad.out new file mode 100644 index 000000000..d428d8f50 --- /dev/null +++ b/test_regress/t/t_math_eq_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_math_eq_bad.v:13:13: Real is illegal operand to ?== operator + : ... In instance t + 13 | if (a ==? 1.0) $stop; + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_math_eq_bad.pl b/test_regress/t/t_math_eq_bad.pl new file mode 100755 index 000000000..bce2416ed --- /dev/null +++ b/test_regress/t/t_math_eq_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2010 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 + +scenarios(linter => 1); + +lint( + expect_filename => $Self->{golden_filename}, + fails => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_math_eq_bad.v b/test_regress/t/t_math_eq_bad.v new file mode 100644 index 000000000..a54b3acae --- /dev/null +++ b/test_regress/t/t_math_eq_bad.v @@ -0,0 +1,16 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + + logic [31:0] a; + + initial begin + a = 1234; + if (a ==? 1.0) $stop; // Bad + end + +endmodule diff --git a/test_regress/t/t_math_repl_bad.out b/test_regress/t/t_math_repl_bad.out new file mode 100644 index 000000000..49249a807 --- /dev/null +++ b/test_regress/t/t_math_repl_bad.out @@ -0,0 +1,22 @@ +%Error: t/t_math_repl_bad.v:12:14: Replication value of 0 is only legal under a concatenation (IEEE 1800-2017 11.4.12.1) + : ... In instance t + 12 | o = {0 {1'b1}}; + | ^ +%Warning-WIDTH: t/t_math_repl_bad.v:12:9: Operator ASSIGN expects 32 bits on the Assign RHS, but Assign RHS's REPLICATE generates 1 bits. + : ... In instance t + 12 | o = {0 {1'b1}}; + | ^ + ... For warning description see https://verilator.org/warn/WIDTH?v=latest + ... Use "/* verilator lint_off WIDTH */" and lint_on around source to disable this message. +%Error: t/t_math_repl_bad.v:13:12: Expecting expression to be constant, but can't convert a TESTPLUSARGS to constant. + : ... In instance t + 13 | o = {$test$plusargs("NON-CONSTANT") {1'b1}}; + | ^~~~~~~~~~~~~~ +%Error: t/t_math_repl_bad.v:13:43: Replication value isn't a constant. + : ... In instance t + 13 | o = {$test$plusargs("NON-CONSTANT") {1'b1}}; + | ^ +%Error: Internal Error: t/t_math_repl_bad.v:13:9: ../V3Width.cpp:#: Node has no type + : ... In instance t + 13 | o = {$test$plusargs("NON-CONSTANT") {1'b1}}; + | ^ diff --git a/test_regress/t/t_math_repl_bad.pl b/test_regress/t/t_math_repl_bad.pl new file mode 100755 index 000000000..9c9fb65a0 --- /dev/null +++ b/test_regress/t/t_math_repl_bad.pl @@ -0,0 +1,19 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2010 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 + +scenarios(linter => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_math_repl_bad.v b/test_regress/t/t_math_repl_bad.v new file mode 100644 index 000000000..788c7239c --- /dev/null +++ b/test_regress/t/t_math_repl_bad.v @@ -0,0 +1,16 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/); + + logic [31:0] o; + + initial begin + o = {0 {1'b1}}; // Bad 0 rep + o = {$test$plusargs("NON-CONSTANT") {1'b1}}; // Bad non-constant rep + $stop; + end +endmodule diff --git a/test_regress/t/t_vlt_syntax_bad.out b/test_regress/t/t_vlt_syntax_bad.out index ebe31f2ce..5f5cd457d 100644 --- a/test_regress/t/t_vlt_syntax_bad.out +++ b/test_regress/t/t_vlt_syntax_bad.out @@ -19,4 +19,10 @@ %Error: t/t_vlt_syntax_bad.vlt:18:1: Argument -scope only supported for tracing_on/off_off 18 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 | ^~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:20:1: forceable missing -module + 20 | forceable -module "" -var "net_*" + | ^~~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:22:1: missing -var + 22 | forceable -module "top" -var "" + | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_vlt_syntax_bad.vlt b/test_regress/t/t_vlt_syntax_bad.vlt index 7f1d2d092..129141465 100644 --- a/test_regress/t/t_vlt_syntax_bad.vlt +++ b/test_regress/t/t_vlt_syntax_bad.vlt @@ -16,3 +16,7 @@ lint_off --rule UNOPTFLAT -scope "top*" lint_off --rule UNOPTFLAT -scope "top*" -levels 0 lint_on --rule UNOPTFLAT -scope "top*" lint_on --rule UNOPTFLAT -scope "top*" -levels 0 +// bad, --module missing +forceable -module "" -var "net_*" +// bad, --var missing +forceable -module "top" -var "" From ed93a111c285ce36e185ed45a45cf9e0d33c9b6a Mon Sep 17 00:00:00 2001 From: Kamil Rakoczy Date: Wed, 26 Oct 2022 13:50:28 +0200 Subject: [PATCH 166/177] Fix deadlock in ``timeprecision`` when using systemC (#3707) --- include/verilated.cpp | 36 ++++++++++++++++------------- test_regress/t/t_time_sc_bad_mt.out | 2 ++ test_regress/t/t_time_sc_bad_mt.pl | 30 ++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 test_regress/t/t_time_sc_bad_mt.out create mode 100755 test_regress/t/t_time_sc_bad_mt.pl diff --git a/include/verilated.cpp b/include/verilated.cpp index 506c1e5f7..6428c4ee5 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -2426,23 +2426,25 @@ void VerilatedContext::timeunit(int value) VL_MT_SAFE { } void VerilatedContext::timeprecision(int value) VL_MT_SAFE { if (value < 0) value = -value; // Stored as 0..15 - const VerilatedLockGuard lock{m_mutex}; - m_s.m_timeprecision = value; -#ifdef SYSTEMC_VERSION - const sc_time sc_res = sc_get_time_resolution(); int sc_prec = 99; - if (sc_res == sc_time(1, SC_SEC)) { - sc_prec = 0; - } else if (sc_res == sc_time(1, SC_MS)) { - sc_prec = 3; - } else if (sc_res == sc_time(1, SC_US)) { - sc_prec = 6; - } else if (sc_res == sc_time(1, SC_NS)) { - sc_prec = 9; - } else if (sc_res == sc_time(1, SC_PS)) { - sc_prec = 12; - } else if (sc_res == sc_time(1, SC_FS)) { - sc_prec = 15; + { + const VerilatedLockGuard lock{m_mutex}; + m_s.m_timeprecision = value; +#ifdef SYSTEMC_VERSION + const sc_time sc_res = sc_get_time_resolution(); + if (sc_res == sc_time(1, SC_SEC)) { + sc_prec = 0; + } else if (sc_res == sc_time(1, SC_MS)) { + sc_prec = 3; + } else if (sc_res == sc_time(1, SC_US)) { + sc_prec = 6; + } else if (sc_res == sc_time(1, SC_NS)) { + sc_prec = 9; + } else if (sc_res == sc_time(1, SC_PS)) { + sc_prec = 12; + } else if (sc_res == sc_time(1, SC_FS)) { + sc_prec = 15; + } } if (value != sc_prec) { std::ostringstream msg; @@ -2454,6 +2456,8 @@ void VerilatedContext::timeprecision(int value) VL_MT_SAFE { const std::string msgs = msg.str(); VL_FATAL_MT("", 0, "", msgs.c_str()); } +#else + } #endif } const char* VerilatedContext::timeunitString() const VL_MT_SAFE { return vl_time_str(timeunit()); } diff --git a/test_regress/t/t_time_sc_bad_mt.out b/test_regress/t/t_time_sc_bad_mt.out new file mode 100644 index 000000000..86603162d --- /dev/null +++ b/test_regress/t/t_time_sc_bad_mt.out @@ -0,0 +1,2 @@ +%Error: SystemC's sc_set_time_resolution is 10^-9, which does not match Verilog timeprecision 10^-12. Suggest use 'sc_set_time_resolution(1s)', or Verilator '--timescale-override 1s/1s' +Aborting... diff --git a/test_regress/t/t_time_sc_bad_mt.pl b/test_regress/t/t_time_sc_bad_mt.pl new file mode 100755 index 000000000..6b6f6d637 --- /dev/null +++ b/test_regress/t/t_time_sc_bad_mt.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(vltmt => 1); + +top_filename("t/t_time_sc.v"); + +$Self->{sc_time_resolution} = 'SC_NS'; + +compile( + verilator_flags2 => ['-sc', '-timescale 1ps/1ps', # Mismatch w/sc_time_resolution + '+define+TEST_EXPECT=2us'], + threads => 2, + ); + +execute( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); + +1; From 65290afe0adb928b9836c2f15fbc0159d9969889 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 27 Oct 2022 17:39:53 +0100 Subject: [PATCH 167/177] Fix VString::endsWith when suffix is longer than input string. --- src/V3String.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/V3String.cpp b/src/V3String.cpp index 4b118a537..af6122e7d 100644 --- a/src/V3String.cpp +++ b/src/V3String.cpp @@ -184,6 +184,7 @@ bool VString::startsWith(const string& str, const string& prefix) { } bool VString::endsWith(const string& str, const string& suffix) { + if (str.length() < suffix.length()) return false; return str.compare(str.length() - suffix.length(), suffix.length(), suffix) == 0; } From 5f9b0929b40f9022aadc4b2253e0bd46e13b1873 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Thu, 27 Oct 2022 21:35:09 -0400 Subject: [PATCH 168/177] Commentary: Changes update --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 14ab87065..3b473e5df 100644 --- a/Changes +++ b/Changes @@ -50,6 +50,8 @@ Verilator 5.001 devel * Fix linker errors in user-facing timing functions (#3657). [Krzysztof Bieganski, Antmicro Ltd] * Fix null access on optimized-out fork statements (#3658). [Krzysztof Bieganski, Antmicro Ltd] * Fix VPI inline module naming mismatch (#3690) (#3694). [Jiuyang Liu] +* Fix deadlock in timeprecision when using systemC (#3707). [Kamil Rakoczy, Antmicro Ltd] + Verilator 4.228 2022-10-01 ========================== From 0ed7aaeabd0f193801bfee546d7955b5cd263866 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Thu, 27 Oct 2022 21:37:09 -0400 Subject: [PATCH 169/177] Update include/gtkwave/fstapi.c from upstream. --- include/gtkwave/fstapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/gtkwave/fstapi.c b/include/gtkwave/fstapi.c index b19e0bcaa..5e9529d44 100644 --- a/include/gtkwave/fstapi.c +++ b/include/gtkwave/fstapi.c @@ -140,7 +140,7 @@ void **JenkinsIns(void *base_i, const unsigned char *mem, uint32_t length, uint3 #include #endif -#if defined(FST_MACOSX) || defined(__MINGW32__) || defined(__OpenBSD__) || defined(__FreeBSD__) +#if defined(FST_MACOSX) || defined(__MINGW32__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__) #define FST_UNBUFFERED_IO #endif From 5c658f8cd5650305f7eced91f01e7e297c43f244 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Fri, 28 Oct 2022 06:38:49 -0400 Subject: [PATCH 170/177] Fix width mismatch on inside operator (#3714). --- Changes | 1 + src/V3Width.cpp | 8 +++++--- test_regress/t/t_inside2.pl | 17 +++++++++++++++++ test_regress/t/t_inside2.v | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100755 test_regress/t/t_inside2.pl create mode 100644 test_regress/t/t_inside2.v diff --git a/Changes b/Changes index 3b473e5df..1c7e6e6a1 100644 --- a/Changes +++ b/Changes @@ -51,6 +51,7 @@ Verilator 5.001 devel * Fix null access on optimized-out fork statements (#3658). [Krzysztof Bieganski, Antmicro Ltd] * Fix VPI inline module naming mismatch (#3690) (#3694). [Jiuyang Liu] * Fix deadlock in timeprecision when using systemC (#3707). [Kamil Rakoczy, Antmicro Ltd] +* Fix width mismatch on inside operator (#3714). [Alex Torregrosa] Verilator 4.228 2022-10-01 diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 247063bb8..e5a7050fd 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2351,7 +2351,7 @@ private: void visit(AstInside* nodep) override { userIterateAndNext(nodep->exprp(), WidthVP(CONTEXT, PRELIM).p()); for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) { - nextip = itemp->nextp(); // Prelim may cause the node to get replaced + nextip = itemp->nextp(); // iterate may cause the node to get replaced VL_DO_DANGLING(userIterate(itemp, WidthVP(CONTEXT, PRELIM).p()), itemp); } // Take width as maximum across all items @@ -2366,7 +2366,8 @@ private: = nodep->findLogicDType(width, mwidth, nodep->exprp()->dtypep()->numeric()); iterateCheck(nodep, "Inside expression", nodep->exprp(), CONTEXT, FINAL, subDTypep, EXTEND_EXP); - for (AstNode* itemp = nodep->itemsp(); itemp; itemp = itemp->nextp()) { + for (AstNode *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) { + nextip = itemp->nextp(); // iterate may cause the node to get replaced iterateCheck(nodep, "Inside Item", itemp, CONTEXT, FINAL, subDTypep, EXTEND_EXP); } nodep->dtypeSetBit(); @@ -5741,7 +5742,8 @@ private: // node, while the output dtype is the *expected* sign. // It is reasonable to have sign extension with unsigned output, // for example $unsigned(a)+$signed(b), the SIGNED(B) will be unsigned dtype out - UINFO(4, " widthExtend_(r=" << extendRule << ") old: " << nodep << endl); + UINFO(4, + " widthExtend_(r=" << static_cast(extendRule) << ") old: " << nodep << endl); if (extendRule == EXTEND_OFF) return; AstConst* const constp = VN_CAST(nodep, Const); const int expWidth = expDTypep->width(); diff --git a/test_regress/t/t_inside2.pl b/test_regress/t/t_inside2.pl new file mode 100755 index 000000000..552bd97db --- /dev/null +++ b/test_regress/t/t_inside2.pl @@ -0,0 +1,17 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + ); + +ok(1); +1; diff --git a/test_regress/t/t_inside2.v b/test_regress/t/t_inside2.v new file mode 100644 index 000000000..4cea304f6 --- /dev/null +++ b/test_regress/t/t_inside2.v @@ -0,0 +1,37 @@ +// DESCRIPTION::Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/ + // Inputs + clk + ); + + input clk; + + typedef struct packed { + logic signed [63:0] b; + } a_t; + + a_t a_r; + a_t a_n; + logic signed [63:0] b; + logic res; + + assign b = a_r.b; + + always_comb begin + a_n = a_r; + res = '0; + if (b inside {1, 2}) begin + res = 1'b1; + end + end + + always_ff @(posedge clk) begin + a_r <= a_n; + end + +endmodule From ef2776034eca1f6c41743c56b80764200bd89d5c Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 27 Oct 2022 16:47:42 +0100 Subject: [PATCH 171/177] Adjust instruction count estimates for AstCMethodHard The cost of an AstCMethodHard right now is generally unknown. However, VlTriggerVec::at is used a lot in conditions, so we make an effort to estimate this correctly via 2 changes: - In general when an AstVarRef appears as the target of an AstCMethodHard, we cost it as a simple address computation (an add) - Check for VlTriggerVec::at explicitly when costing AstCMethodHard, which is essentially a load. This can have a significant effect when there are a lot of unique triggers in the design. --- src/V3AstNodeMath.h | 4 +--- src/V3AstNodeOther.h | 1 + src/V3AstNodes.cpp | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/V3AstNodeMath.h b/src/V3AstNodeMath.h index 398e57832..ef635a14a 100644 --- a/src/V3AstNodeMath.h +++ b/src/V3AstNodeMath.h @@ -4265,9 +4265,7 @@ public: bool same(const AstNode* samep) const override; inline bool same(const AstVarRef* samep) const; inline bool sameNoLvalue(AstVarRef* samep) const; - int instrCount() const override { - return widthInstrs() * (access().isReadOrRW() ? INSTR_COUNT_LD : 1); - } + int instrCount() const override; string emitVerilog() override { V3ERROR_NA_RETURN(""); } string emitC() override { V3ERROR_NA_RETURN(""); } bool cleanOut() const override { return true; } diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 3e2fd6430..a0b3e991f 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2851,6 +2851,7 @@ public: statement(true); dtypeSetVoid(); } + int instrCount() const override; }; class AstCReset final : public AstNodeStmt { // Reset variable at startup diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 92e0b2fce..3487e1814 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2047,6 +2047,14 @@ void AstVarRef::dump(std::ostream& str) const { bool AstVarRef::same(const AstNode* samep) const { return same(static_cast(samep)); } +int AstVarRef::instrCount() const { + // Account for the target of hard-coded method calls as just an address computation + if (const AstCMethodHard* const callp = VN_CAST(backp(), CMethodHard)) { + if (callp->fromp() == this) return 1; + } + // Otherwise as a load/store + return widthInstrs() * (access().isReadOrRW() ? INSTR_COUNT_LD : 1); +} void AstVar::dump(std::ostream& str) const { this->AstNode::dump(str); if (isSc()) str << " [SC]"; @@ -2247,6 +2255,18 @@ void AstCAwait::dump(std::ostream& str) const { sensesp()->dump(str); } } +int AstCMethodHard::instrCount() const { + if (AstBasicDType* const basicp = fromp()->dtypep()->basicp()) { + // TODO: add a more structured description of library methods, rather than using string + // matching. See #3715. + if (basicp->isTriggerVec() && m_name == "at") { + // This is an important special case for scheduling so we compute it precisely, + // it is simply a load. + return INSTR_COUNT_LD; + } + } + return AstNodeStmt::instrCount(); +} const char* AstCFunc::broken() const { BROKEN_RTN((m_scopep && !m_scopep->brokeExists())); return nullptr; From e504e9aced15a3aeedc983b24d56e7ed552ec66e Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 27 Oct 2022 18:32:10 +0100 Subject: [PATCH 172/177] Optimize clocked processes to comb when referencing const variables In V3Active, we try hard to turn `always @(a or b or c)` into an `always_comb` if the only variables read in the block are also in the sensitivity list. In addition, also allow this optimization when reading variables that are not in the sensitivity list, but are known to be constant/never changing after initialization. In particular lookup tables introduced by V3Table are covered by this. This can have a significant impact on designs that use the `always @(a or b or c)` style for combinational logic. --- src/V3Active.cpp | 7 ++++--- test_regress/t/t_case_huge.pl | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 06a1d7f51..0350609cc 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -571,10 +571,11 @@ private: if (nodep->access().isWriteOnly()) { vscp->user2(true); } else { - // If the variable is read before it is written, and is not in the sensitivity list, - // then this cannot be optimized into a combinational process + // If the variable is read before it is written (and is not a never-changing value), + // and is not in the sensitivity list, then this cannot be optimized into a + // combinational process // TODO: live variable analysis would be more precise - if (!vscp->user2() && !vscp->user1()) m_canBeComb = false; + if (!vscp->user2() && !vscp->varp()->valuep() && !vscp->user1()) m_canBeComb = false; } } void visit(AstAssignDly* nodep) override { diff --git a/test_regress/t/t_case_huge.pl b/test_regress/t/t_case_huge.pl index 79a6a7976..c8266ccbc 100755 --- a/test_regress/t/t_case_huge.pl +++ b/test_regress/t/t_case_huge.pl @@ -14,9 +14,12 @@ compile( verilator_flags2 => ["--stats"], ); -if ($Self->{vlt_all}) { +if ($Self->{vlt}) { file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 10); - file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, 0); + file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, 8); +} elsif ($Self->{vltmt}) { + file_grep($Self->{stats}, qr/Optimizations, Tables created\s+(\d+)/i, 10); + file_grep($Self->{stats}, qr/Optimizations, Combined CFuncs\s+(\d+)/i, 9); } execute( From 54c4351c39680464891933bb9ce5ea2563eddb8f Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 28 Oct 2022 13:41:13 +0100 Subject: [PATCH 173/177] V3Const: Do not introduce redundant AstExtend Fixes #3716 --- src/V3Const.cpp | 21 +++++++++++++++------ test_regress/t/t_const_sel_sel_extend.pl | 16 ++++++++++++++++ test_regress/t/t_const_sel_sel_extend.v | 22 ++++++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100755 test_regress/t/t_const_sel_sel_extend.pl create mode 100644 test_regress/t/t_const_sel_sel_extend.v diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 158561f07..9d1b7f7f8 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2411,12 +2411,21 @@ private: VL_DO_DANGLING(lsb1p->deleteTree(), lsb1p); VL_DO_DANGLING(lsb2p->deleteTree(), lsb2p); } else { - // Width is important, we need the width of the fromp's - // expression, not the potentially smaller lsb1p's width - newlsbp - = new AstAdd(lsb1p->fileline(), lsb2p, new AstExtend(lsb1p->fileline(), lsb1p)); - newlsbp->dtypeFrom(lsb2p); // Unsigned - VN_AS(newlsbp, Add)->rhsp()->dtypeFrom(lsb2p); + // Width is important, we need the width of the fromp's expression, not the + // potentially smaller lsb1p's width, but don't insert a redundant AstExtend. + // Note that due to some sloppiness in earlier passes, lsb1p might actually be wider, + // so extend to the wider type. + AstNode* const widep = lsb1p->width() > lsb2p->width() ? lsb1p : lsb2p; + AstNode* const lhsp = widep->width() > lsb2p->width() + ? new AstExtend{lsb2p->fileline(), lsb2p} + : lsb2p; + AstNode* const rhsp = widep->width() > lsb1p->width() + ? new AstExtend{lsb1p->fileline(), lsb1p} + : lsb1p; + lhsp->dtypeFrom(widep); + rhsp->dtypeFrom(widep); + newlsbp = new AstAdd{lsb1p->fileline(), lhsp, rhsp}; + newlsbp->dtypeFrom(widep); } AstSel* const newp = new AstSel(nodep->fileline(), fromp, newlsbp, widthp); nodep->replaceWith(newp); diff --git a/test_regress/t/t_const_sel_sel_extend.pl b/test_regress/t/t_const_sel_sel_extend.pl new file mode 100755 index 000000000..84ae125be --- /dev/null +++ b/test_regress/t/t_const_sel_sel_extend.pl @@ -0,0 +1,16 @@ +#!/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 => 1); + +compile(); + +ok(1); +1; diff --git a/test_regress/t/t_const_sel_sel_extend.v b/test_regress/t/t_const_sel_sel_extend.v new file mode 100644 index 000000000..c695de4f2 --- /dev/null +++ b/test_regress/t/t_const_sel_sel_extend.v @@ -0,0 +1,22 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Geza Lore. +// SPDX-License-Identifier: CC0-1.0 + +module t( + output wire res +); + + function automatic logic foo(logic bar); + foo = '0; + endfunction + + logic a, b; + logic [0:0][1:0] array; + + assign b = 0; + assign a = foo(b); + assign res = array[a][a]; + +endmodule From ee263783949240a8f391d9e772c2df0d0026bf8f Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Fri, 28 Oct 2022 10:31:36 -0400 Subject: [PATCH 174/177] Tests: Fix instability (#3717) --- test_regress/t/t_timing_protect.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test_regress/t/t_timing_protect.pl b/test_regress/t/t_timing_protect.pl index 96606c188..bd2ed322d 100755 --- a/test_regress/t/t_timing_protect.pl +++ b/test_regress/t/t_timing_protect.pl @@ -17,7 +17,8 @@ else { top_filename("t/t_timing_fork_join.v"); # Contains all relevant constructs compile( - verilator_flags2 => ["--exe --main --timing --protect-ids"], + verilator_flags2 => ["--exe --main --timing --protect-ids", + "--protect-key SECRET_KEY"], make_main => 0, ); From 99791ac8b3e2279cebaebd67630640c5c601b83e Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 28 Oct 2022 16:35:53 +0100 Subject: [PATCH 175/177] Reduce verbosity of DFG debug --- src/V3DfgOptimizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index db8fef6a1..b162f6819 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -259,7 +259,7 @@ void V3DfgOptimizer::optimize(AstNetlist* netlistp, const string& label) { AstModule* const modp = VN_CAST(nodep, Module); if (!modp) continue; - UINFO(3, "Applying DFG optimization to module'" << modp->name() << "'" << endl); + UINFO(4, "Applying DFG optimization to module '" << modp->name() << "'" << endl); ++ctx.m_modules; // Build the DFG of this module From 0675510eb90115bceb2f13416192d7f70f677fa6 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 28 Oct 2022 17:00:32 +0100 Subject: [PATCH 176/177] DFG: Fix attempted evaluation of constants wider than 32 bits Fixes #3718 --- src/V3DfgPeephole.cpp | 8 ++++---- src/V3DfgVertices.h | 13 +++++++++++-- test_regress/t/t_dfg_peephole.v | 2 ++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index af98ca8da..bb0fe31b8 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -1072,7 +1072,7 @@ class V3DfgPeephole final : public DfgVisitor { void visit(DfgArraySel* vtxp) override { if (DfgConst* const idxp = vtxp->bitp()->cast()) { if (DfgVarArray* const varp = vtxp->fromp()->cast()) { - const uint32_t idx = idxp->toU32(); + const size_t idx = idxp->toSizeT(); if (DfgVertex* const driverp = varp->driverAt(idx)) { APPLYING(INLINE_ARRAYSEL) { vtxp->replaceWith(driverp); @@ -1368,7 +1368,7 @@ class V3DfgPeephole final : public DfgVisitor { return; } } - if (vtxp->dtypep() == m_bitDType && rConstp->toU32() == 1) { + if (vtxp->dtypep() == m_bitDType && rConstp->hasValue(1)) { APPLYING(REPLACE_SUB_WITH_NOT) { DfgNot* const replacementp = new DfgNot{m_dfg, vtxp->fileline(), m_bitDType}; replacementp->srcp(lhsp); @@ -1454,7 +1454,7 @@ class V3DfgPeephole final : public DfgVisitor { // 'cond ? a + 1 : a' -> 'a + cond' if (DfgAdd* const thenAddp = thenp->cast()) { if (DfgConst* const constp = thenAddp->lhsp()->cast()) { - if (constp->toI32() == 1) { + if (constp->hasValue(1)) { if (thenAddp->rhsp() == elsep) { APPLYING(REPLACE_COND_INC) { DfgConcat* const extp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; @@ -1474,7 +1474,7 @@ class V3DfgPeephole final : public DfgVisitor { // 'cond ? a - 1 : a' -> 'a - cond' if (DfgSub* const thenSubp = thenp->cast()) { if (DfgConst* const constp = thenSubp->rhsp()->cast()) { - if (constp->toI32() == 1) { + if (constp->hasValue(1)) { if (thenSubp->lhsp() == elsep) { APPLYING(REPLACE_COND_DEC) { DfgConcat* const extp = new DfgConcat{m_dfg, flp, vtxp->dtypep()}; diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index 8b003ace0..5c3700308 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -108,12 +108,21 @@ public: V3Number& num() { return m_num; } const V3Number& num() const { return m_num; } - uint32_t toU32() const { return num().toUInt(); } - int32_t toI32() const { return num().toSInt(); } + size_t toSizeT() const { + if VL_CONSTEXPR_CXX17 (sizeof(size_t) > sizeof(uint32_t)) { + return static_cast(num().toUQuad()); + } + return static_cast(num().toUInt()); + } bool isZero() const { return num().isEqZero(); } bool isOnes() const { return num().isEqAllOnes(width()); } + // Does this DfgConst have the given value? Note this is not easy to answer if wider than 32. + bool hasValue(uint32_t value) const { + return !num().isFourState() && num().edataWord(0) == value && num().mostSetBitP1() <= 32; + } + std::pair sourceEdges() override { return {nullptr, 0}; } std::pair sourceEdges() const override { return {nullptr, 0}; } const string srcName(size_t) const override { // LCOV_EXCL_START diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 675fd9194..e3e3862bd 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -175,6 +175,8 @@ module t ( `signal(REMOVE_XOR_WITH_ONES, -64'd1 ^ rand_a); `signal(REPLACE_COND_DEC, randbit_a ? rand_b - 64'b1 : rand_b); `signal(REPLACE_COND_INC, randbit_a ? rand_b + 64'b1 : rand_b); + `signal(NO_REPLACE_COND_DEC, randbit_a ? rand_b - 64'hf000000000000000 : rand_b); + `signal(NO_REPLACE_COND_INC, randbit_a ? rand_b + 64'hf000000000000000 : rand_b); `signal(RIGHT_LEANING_ASSOC, (((rand_a + rand_b) + rand_a) + rand_b)); `signal(RIGHT_LEANING_CONCET, {{{rand_a, rand_b}, rand_a}, rand_b}); From 52d29d238c04e32b0c8573a3f92bf59e232442fd Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 29 Oct 2022 17:45:54 -0400 Subject: [PATCH 177/177] Version bump --- Changes | 2 +- configure.ac | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Changes b/Changes index 1c7e6e6a1..7a64aee94 100644 --- a/Changes +++ b/Changes @@ -8,7 +8,7 @@ The changes in each Verilator version are described below. The contributors that suggested a given feature are shown in []. Thanks! -Verilator 5.001 devel +Verilator 5.002 2022-10-29 ========================== **Major:** diff --git a/configure.ac b/configure.ac index 19d022983..7994e3554 100644 --- a/configure.ac +++ b/configure.ac @@ -10,7 +10,7 @@ # Then 'make maintainer-dist' #AC_INIT([Verilator],[#.### YYYY-MM-DD]) #AC_INIT([Verilator],[#.### devel]) -AC_INIT([Verilator],[5.001 devel], +AC_INIT([Verilator],[5.002 2022-10-29], [https://verilator.org], [verilator],[https://verilator.org])