diff --git a/docs/gen/ex_FINALDLY_faulty.rst b/docs/gen/ex_FINALDLY_faulty.rst new file mode 100644 index 000000000..78c456ad4 --- /dev/null +++ b/docs/gen/ex_FINALDLY_faulty.rst @@ -0,0 +1,7 @@ +.. comment: generated by t_finaldly_bad +.. code-block:: sv + :linenos: + :emphasize-lines: 2 + + bit foo; + final foo <= 1; // <--- Error diff --git a/docs/gen/ex_FINALDLY_msg.rst b/docs/gen/ex_FINALDLY_msg.rst new file mode 100644 index 000000000..4178e2669 --- /dev/null +++ b/docs/gen/ex_FINALDLY_msg.rst @@ -0,0 +1,6 @@ +.. comment: generated by t_finaldly_bad +.. code-block:: + + %Error-FINALDLY: example.v:1:13 Non-blocking assignment '<=' in final block + 9 | final foo <= 1; + | ^~ diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 209a52ebb..ce3a0d58c 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -883,6 +883,22 @@ List Of Warnings with a newline." +.. option:: FINALDLY + + Error issued when a non-blocking assignment `<=` is used in a + `final` block. + + This error can be disabled. If disabled, the assignment will be + executed as a `=` blocking assignment. + + Faulty example: + + .. include:: ../../docs/gen/ex_FINALDLY_faulty.rst + + Results in: + + .. include:: ../../docs/gen/ex_FINALDLY_msg.rst + .. option:: FSMMULTI Warns that the same always block contains multiple enum-typed case @@ -1179,7 +1195,7 @@ List Of Warnings .. option:: INITIALDLY - .. TODO better example + Historical, never issued since version 5.050. Warns that the code has a delayed assignment inside of an ``initial`` or ``final`` block. If this message is suppressed, Verilator will convert diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 995f4fd5c..13cd270b4 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -351,7 +351,7 @@ public: class ActiveDlyVisitor final : public VNVisitor { public: - enum CheckType : uint8_t { CT_SEQ, CT_COMB, CT_INITIAL, CT_SUSPENDABLE }; + enum CheckType : uint8_t { CT_COMB, CT_FINAL }; private: // MEMBERS @@ -359,15 +359,9 @@ private: // VISITORS void visit(AstAssignDly* nodep) override { - // Non-blocking assignments are OK in sequential processes - if (m_check == CT_SEQ || m_check == CT_SUSPENDABLE) return; - // Issue appropriate warning - if (m_check == CT_INITIAL) { - nodep->v3warn(INITIALDLY, - "Non-blocking assignment '<=' in initial/final block\n" - << nodep->warnMore() - << "... This will be executed as a blocking assignment '='!"); + if (m_check == CT_FINAL) { + nodep->v3warn(FINALDLY, "Non-blocking assignment '<=' in final block"); } else { nodep->v3warn(COMBDLY, "Non-blocking assignment '<=' in combinational logic process\n" @@ -465,11 +459,7 @@ class ActiveVisitor final : public VNVisitor { wantactivep->addStmtsp(nodep); // Warn and convert any delayed assignments - { - ActiveDlyVisitor{nodep, !m_clockedProcess ? ActiveDlyVisitor::CT_COMB - : oldsentreep ? ActiveDlyVisitor::CT_SEQ - : ActiveDlyVisitor::CT_SUSPENDABLE}; - } + if (!m_clockedProcess) ActiveDlyVisitor{nodep, ActiveDlyVisitor::CT_COMB}; // Delete sensitivity list if (oldsentreep) VL_DO_DANGLING(oldsentreep->deleteTree(), oldsentreep); @@ -509,17 +499,11 @@ class ActiveVisitor final : public VNVisitor { void visit(AstInitialStatic* nodep) override { moveUnderSpecial(nodep); } void visit(AstInitial* nodep) override { - const bool timedInitial - = v3Global.opt.timing().isSetTrue() && nodep->exists([](const AstNode* const subp) { - return VN_IS(subp, Delay) || VN_IS(subp, EventControl); - }); - const ActiveDlyVisitor dlyvisitor{nodep, timedInitial ? ActiveDlyVisitor::CT_SUSPENDABLE - : ActiveDlyVisitor::CT_INITIAL}; visitSenItems(nodep); moveUnderSpecial(nodep); } void visit(AstFinal* nodep) override { - const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_INITIAL}; + const ActiveDlyVisitor dlyvisitor{nodep, ActiveDlyVisitor::CT_FINAL}; moveUnderSpecial(nodep); } void visit(AstCoverToggle* nodep) override { moveUnderSpecial(nodep); } diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 096923300..afca1bcc2 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -1428,6 +1428,7 @@ public: ET_EVENT, // VlEventBase::isFired // Involving an expression ET_TRUE, + ET_INITIAL_NBA, // Event that is fired initially and never again // ET_COMBO, // Sensitive to all combo inputs to this block ET_COMBO_STAR, // Sensitive to all combo inputs to this block (from .*) @@ -1446,6 +1447,7 @@ public: true, // ET_NEGEDGE true, // ET_EVENT true, // ET_TRUE + true, // ET_INITIAL_NBA false, // ET_COMBO false, // ET_COMBO_STAR @@ -1469,14 +1471,14 @@ public: } const char* ascii() const { static const char* const names[] - = {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", "COMBO", - "COMBO_STAR", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; + = {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", "ET_INITIAL_NBA", + "COMBO", "COMBO_STAR", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; return names[m_e]; } const char* verilogKwd() const { - static const char* const names[] - = {"[changed]", "edge", "posedge", "negedge", "[event]", "[true]", "*", - "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; + static const char* const names[] = { + "[changed]", "edge", "posedge", "negedge", "[event]", "[true]", "[initial_nba]", + "*", "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; return names[m_e]; } // Return true iff this and the other have mutually exclusive transitions diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index e3bbcde14..19f4d07ee 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1753,6 +1753,7 @@ public: class Combo {}; // for constructor type-overload selection class Static {}; // for constructor type-overload selection class Initial {}; // for constructor type-overload selection + class InitialNBA {}; // for constructor type-overload selection class Final {}; // for constructor type-overload selection class Never {}; // for constructor type-overload selection AstSenItem(FileLine* fl, VEdgeType edgeType, AstNodeExpr* senp, AstNodeExpr* condp = nullptr) @@ -1770,6 +1771,9 @@ public: AstSenItem(FileLine* fl, Initial) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_INITIAL} {} + AstSenItem(FileLine* fl, InitialNBA) + : ASTGEN_SUPER_SenItem(fl) + , m_edgeType{VEdgeType::ET_INITIAL_NBA} {} AstSenItem(FileLine* fl, Final) : ASTGEN_SUPER_SenItem(fl) , m_edgeType{VEdgeType::ET_FINAL} {} diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index 1f6758629..f2ed71ebc 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -275,6 +275,7 @@ class DelayedVisitor final : public VNVisitor { bool m_inSuspendableOrFork = false; // True in suspendable processes and forks bool m_ignoreBlkAndNBlk = false; // Suppress delayed assignment BLKANDNBLK bool m_inNonCombLogic = false; // We are in non-combinational logic + bool m_needsInitialTrigger = false; // Whether a NodeProcedure needs a initial trigger AstVarRef* m_currNbaLhsRefp = nullptr; // Current NBA LHS variable reference // STATE - during NBA conversion (after visit) @@ -291,6 +292,7 @@ class DelayedVisitor final : public VNVisitor { VDouble0 m_nSchemeValueQueuesWhole; // Number of variables using Scheme::ValueQueueWhole VDouble0 m_nSchemeValueQueuesPartial; // Number of variables using Scheme::ValueQueuePartial VDouble0 m_nSharedSetFlags; // "Set" flags actually shared by Scheme::FlagShared variables + VDouble0 m_nInitialNBA; // Number of procedural blocks with initial NBA // METHODS @@ -999,6 +1001,12 @@ class DelayedVisitor final : public VNVisitor { m_writeRefs(nodep->varScopep()).emplace_back(nodep, nonBlocking, m_inNonCombLogic); } + template + static bool isProcedureWithSentreep(const AstNodeProcedure* const nodep) { + const Procedure_T* const procedurep = AstNode::cast(nodep); + return procedurep && procedurep->sentreep(); + } + // VISITORS void visit(AstNetlist* nodep) override { iterateChildren(nodep); @@ -1112,21 +1120,36 @@ class DelayedVisitor final : public VNVisitor { iterateChildren(nodep); } void visit(AstNodeProcedure* nodep) override { + VL_RESTORER(m_needsInitialTrigger); const size_t firstNBAAddedIndex = m_nbas.size(); { VL_RESTORER(m_inSuspendableOrFork); VL_RESTORER(m_procp); VL_RESTORER(m_ignoreBlkAndNBlk); VL_RESTORER(m_inNonCombLogic); - m_inSuspendableOrFork = nodep->isSuspendable(); + // When we are dealing with initial block we need to + // treat it as suspendable when we meet a NBA + m_inSuspendableOrFork = nodep->isSuspendable() || VN_IS(nodep, Initial); m_procp = nodep; - if (m_inSuspendableOrFork) { + if (nodep->isSuspendable()) { m_ignoreBlkAndNBlk = false; m_inNonCombLogic = true; } iterateChildren(nodep); } - if (m_timingDomains.empty()) return; + auto containsClocled = [](const AstSenItem* itemp) { + while (itemp) { + if (itemp->edgeType().clockedStmt()) return true; + itemp = VN_AS(itemp->nextp(), SenItem); + } + return false; + }; + const bool addInitialTrigger = m_needsInitialTrigger + && !(isProcedureWithSentreep(nodep) + || isProcedureWithSentreep(nodep) + || isProcedureWithSentreep(nodep)) + && !containsClocled(m_activep->sentreep()->sensesp()); + if (m_timingDomains.empty() && !addInitialTrigger) return; // There were some timing domains involved in the process. Add all of them as sensitivities // of all NBA targets in this process. Note this is a bit of a sledgehammer, we should only @@ -1135,6 +1158,11 @@ class DelayedVisitor final : public VNVisitor { // First gather all senItems AstSenItem* senItemp = nullptr; + if (addInitialTrigger) { + senItemp = new AstSenItem{nodep->fileline(), AstSenItem::InitialNBA{}}; + ++m_nInitialNBA; + } + for (const AstSenTree* const domainp : m_timingDomains) { if (domainp->sensesp()) senItemp = AstNode::addNext(senItemp, domainp->sensesp()->cloneTree(true)); @@ -1218,6 +1246,8 @@ class DelayedVisitor final : public VNVisitor { UASSERT_OBJ(m_inSuspendableOrFork || m_activep->hasClocked(), nodep, "<= assignment in non-clocked block, should have been converted in V3Active"); + m_needsInitialTrigger |= m_timingDomains.empty(); + // Record scope of this NBA nodep->user2p(m_scopep); @@ -1327,6 +1357,7 @@ public: V3Stats::addStat("NBA, variables using ValueQueuePartial scheme", m_nSchemeValueQueuesPartial); V3Stats::addStat("Optimizations, NBA flags shared", m_nSharedSetFlags); + V3Stats::addStat("Procedures needing initial NBA trigger", m_nInitialNBA); } }; diff --git a/src/V3Error.h b/src/V3Error.h index 6d4914329..01e7d6447 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -107,6 +107,7 @@ public: ENUMITEMWIDTH, // Error: enum item width mismatch ENUMVALUE, // Error: enum type needs explicit cast EOFNEWLINE, // End-of-file missing newline + FINALDLY, // Final delayed statement FSMMULTI, // Multiple FSM candidates in one always block FUNCTIMECTL, // Functions cannot have timing/delay/wait FUTURE, // Feature is under development and not yet supported @@ -227,8 +228,8 @@ public: "BSSPACE", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CONTASSREG", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED", - "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FSMMULTI", - "FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", + "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FINALDLY", + "FSMMULTI", "FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IEEEMAYDEPRECATE", "IFDEPTH", "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH", "LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", @@ -269,11 +270,11 @@ public: bool pretendError() const VL_MT_SAFE { return (m_e == ASSIGNIN || m_e == BADSTDPRAGMA || m_e == BADVLTPRAGMA || m_e == BLKANDNBLK || m_e == BLKLOOPINIT || m_e == CONTASSREG || m_e == ENCAPSULATED - || m_e == ENDLABEL || m_e == ENUMITEMWIDTH || m_e == ENUMVALUE || m_e == HIERPARAM - || m_e == FUNCTIMECTL || m_e == IMPURE || m_e == MODMISSING || m_e == NOTREDOP - || m_e == PARAMNODEFAULT || m_e == PINNOTFOUND || m_e == PKGNODECL - || m_e == PROCASSWIRE || m_e == PROTOTYPEMIS || m_e == SUPERNFIRST - || m_e == ZEROREPL); + || m_e == ENDLABEL || m_e == ENUMITEMWIDTH || m_e == ENUMVALUE || m_e == FINALDLY + || m_e == HIERPARAM || m_e == FUNCTIMECTL || m_e == IMPURE || m_e == MODMISSING + || m_e == NOTREDOP || m_e == PARAMNODEFAULT || m_e == PINNOTFOUND + || m_e == PKGNODECL || m_e == PROCASSWIRE || m_e == PROTOTYPEMIS + || m_e == SUPERNFIRST || m_e == ZEROREPL); } // Warnings to mention manual bool mentionManual() const VL_MT_SAFE { diff --git a/src/V3SenExprBuilder.h b/src/V3SenExprBuilder.h index 861f8a905..ac9abf196 100644 --- a/src/V3SenExprBuilder.h +++ b/src/V3SenExprBuilder.h @@ -78,7 +78,7 @@ private: // Check if expression contains a class member access that could be null // (e.g., accessing an event through a class reference that may not be initialized) static bool hasClassMemberAccess(const AstNode* const exprp) { - return exprp->exists([](const AstNode* const nodep) { + return exprp && exprp->exists([](const AstNode* const nodep) { if (const AstMemberSel* const mselp = VN_CAST(nodep, MemberSel)) { // Check if the base expression is a class reference return mselp->fromp()->dtypep() @@ -294,6 +294,8 @@ private: } case VEdgeType::ET_TRUE: // return {currp(), false}; + case VEdgeType::ET_INITIAL_NBA: // + return {new AstConst{flp, AstConst::BitFalse{}}, true}; default: // LCOV_EXCL_START senItemp->v3fatalSrc("Unknown edge type"); return {nullptr, false}; diff --git a/test_regress/t/t_altera_lpm.v b/test_regress/t/t_altera_lpm.v index 967cd1dea..19d445364 100644 --- a/test_regress/t/t_altera_lpm.v +++ b/test_regress/t/t_altera_lpm.v @@ -49,7 +49,7 @@ //See also: https://github.com/twosigma/verilator_support // verilog_format: off -// verilator lint_off COMBDLY,INITIALDLY,LATCH,MULTIDRIVEN,UNSIGNED,WIDTH +// verilator lint_off COMBDLY,LATCH,MULTIDRIVEN,UNSIGNED,WIDTH // BEGINNING OF MODULE `timescale 1 ps / 1 ps diff --git a/test_regress/t/t_finaldly_bad.out b/test_regress/t/t_finaldly_bad.out new file mode 100644 index 000000000..01c50dacb --- /dev/null +++ b/test_regress/t/t_finaldly_bad.out @@ -0,0 +1,5 @@ +%Error-FINALDLY: t/t_finaldly_bad.v:9:13: Non-blocking assignment '<=' in final block + 9 | final foo <= 1; + | ^~ + ... For error description see https://verilator.org/warn/FINALDLY?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_initial_dlyass_bad.py b/test_regress/t/t_finaldly_bad.py similarity index 50% rename from test_regress/t/t_initial_dlyass_bad.py rename to test_regress/t/t_finaldly_bad.py index f8feb4a77..09d3c8e81 100755 --- a/test_regress/t/t_initial_dlyass_bad.py +++ b/test_regress/t/t_finaldly_bad.py @@ -4,14 +4,21 @@ # 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-FileCopyrightText: 2024 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap test.scenarios('linter') -test.top_filename = "t/t_initial_dlyass.v" -test.lint(fails=True, expect_filename=test.golden_filename) +test.compile(fails=True, expect_filename=test.golden_filename) + +test.extract(in_filename=test.top_filename, + out_filename=test.root + "/docs/gen/ex_FINALDLY_faulty.rst", + lines="8-9") + +test.extract(in_filename=test.golden_filename, + out_filename=test.root + "/docs/gen/ex_FINALDLY_msg.rst", + lines="1-3") test.passes() diff --git a/test_regress/t/t_finaldly_bad.v b/test_regress/t/t_finaldly_bad.v new file mode 100644 index 000000000..81b5223ff --- /dev/null +++ b/test_regress/t/t_finaldly_bad.v @@ -0,0 +1,10 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + bit foo; + final foo <= 1; // <--- Error +endmodule diff --git a/test_regress/t/t_fork_dynscope_out.py b/test_regress/t/t_fork_dynscope_out.py index f478cb764..7ded63f3a 100755 --- a/test_regress/t/t_fork_dynscope_out.py +++ b/test_regress/t/t_fork_dynscope_out.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--binary -Wno-INITIALDLY"]) +test.compile(verilator_flags2=["--binary"]) test.execute() diff --git a/test_regress/t/t_initial_dlyass.py b/test_regress/t/t_initial_dlyass.py index 99d3b795b..3cc73805c 100755 --- a/test_regress/t/t_initial_dlyass.py +++ b/test_regress/t/t_initial_dlyass.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=['-Wno-INITIALDLY']) +test.compile() test.execute() diff --git a/test_regress/t/t_initial_dlyass_bad.out b/test_regress/t/t_initial_dlyass_bad.out deleted file mode 100644 index 54d3da81c..000000000 --- a/test_regress/t/t_initial_dlyass_bad.out +++ /dev/null @@ -1,11 +0,0 @@ -%Warning-INITIALDLY: t/t_initial_dlyass.v:17:7: Non-blocking assignment '<=' in initial/final block - : ... This will be executed as a blocking assignment '='! - 17 | a <= 22; - | ^~ - ... For warning description see https://verilator.org/warn/INITIALDLY?v=latest - ... Use "/* verilator lint_off INITIALDLY */" and lint_on around source to disable this message. -%Warning-INITIALDLY: t/t_initial_dlyass.v:18:7: Non-blocking assignment '<=' in initial/final block - : ... This will be executed as a blocking assignment '='! - 18 | b <= 33; - | ^~ -%Error: Exiting due to diff --git a/test_regress/t/t_lint_historical.v b/test_regress/t/t_lint_historical.v index 014c059a9..d92949dc9 100644 --- a/test_regress/t/t_lint_historical.v +++ b/test_regress/t/t_lint_historical.v @@ -40,6 +40,7 @@ module t; // verilator lint_off ENUMITEMWIDTH // verilator lint_off ENUMVALUE // verilator lint_off EOFNEWLINE + // verilator lint_off FINALDLY // verilator lint_off FUNCTIMECTL // verilator lint_off GENCLK // verilator lint_off GENUNNAMED diff --git a/test_regress/t/t_procedure_always_nba.py b/test_regress/t/t_procedure_always_nba.py new file mode 100755 index 000000000..a68ed3ea9 --- /dev/null +++ b/test_regress/t/t_procedure_always_nba.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.top_filename = "t/t_procedure_nba.v" + +test.compile(verilator_flags2=["--binary", "--stats", "-DPROCEDURE=always"]) + +test.file_grep(test.stats, r'Scheduling, \'act\' extra triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' pre triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' sense triggers\s+(\d+)', 3) +test.file_grep(test.stats, r'Procedures needing initial NBA trigger\s+(\d+)', 100) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_procedure_initial_nba.py b/test_regress/t/t_procedure_initial_nba.py new file mode 100755 index 000000000..528290717 --- /dev/null +++ b/test_regress/t/t_procedure_initial_nba.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.top_filename = "t/t_procedure_nba.v" + +test.compile(verilator_flags2=["--binary", "--stats", "-DPROCEDURE=initial"]) + +test.file_grep(test.stats, r'Scheduling, \'act\' extra triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' pre triggers\s+(\d+)', 0) +test.file_grep(test.stats, r'Scheduling, \'act\' sense triggers\s+(\d+)', 3) +test.file_grep(test.stats, r'Procedures needing initial NBA trigger\s+(\d+)', 100) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_procedure_nba.v b/test_regress/t/t_procedure_nba.v new file mode 100644 index 000000000..a5cbf19a5 --- /dev/null +++ b/test_regress/t/t_procedure_nba.v @@ -0,0 +1,37 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk; + initial clk = 0; + always #5 clk = ~clk; + bit [99:0][2:0] foo; + bit bar; + + always @(posedge clk) begin + bar <= ~bar; + #1; + end + + genvar i; + for (i = 0; i < 100; i=i+1) + `PROCEDURE begin + foo[i] <= 3; + if (foo[i] !== 0) $stop; + @(posedge clk); + if (foo[i] !== 3) $stop; + foo[i] = 2; + if (foo[i] !== 2) $stop; + #1; + if (foo[i] !== 2) $stop; + foo[i] = 0; + end + + initial #100 begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule