From c90f9e53b7779625e8095bd0645796b2832ae4bd Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Mon, 18 Aug 2025 12:00:53 -0400 Subject: [PATCH] Add ALWNEVER warning, for `always @*` that never execute (#6291) (#6303) --- Changes | 1 + docs/gen/ex_ALWNEVER_faulty.rst | 4 +++ docs/gen/ex_ALWNEVER_msg.rst | 4 +++ docs/guide/warnings.rst | 21 +++++++++++++ src/V3Ast.h | 8 +++-- src/V3AstNodeOther.h | 9 +++++- src/V3AstNodes.cpp | 2 +- src/V3Clock.cpp | 6 ++-- src/V3Delayed.cpp | 3 +- src/V3Error.h | 25 ++++++++-------- src/V3LinkParse.cpp | 2 +- src/V3SchedPartition.cpp | 8 +++-- src/V3Timing.cpp | 5 +++- src/V3Width.cpp | 1 + src/V3WidthCommit.cpp | 30 +++++++++++++++++++ src/verilog.y | 24 ++++++++------- test_regress/t/t_event_control_star.out | 7 ++--- test_regress/t/t_event_control_star_never.py | 18 +++++++++++ test_regress/t/t_event_control_star_never.v | 14 +++++++++ .../t/t_event_control_star_never_bad.out | 8 +++++ .../t/t_event_control_star_never_bad.py | 30 +++++++++++++++++++ test_regress/t/t_lint_blksync_bad.v | 2 +- test_regress/t/t_math_vgen.v | 2 +- 23 files changed, 192 insertions(+), 42 deletions(-) create mode 100644 docs/gen/ex_ALWNEVER_faulty.rst create mode 100644 docs/gen/ex_ALWNEVER_msg.rst create mode 100755 test_regress/t/t_event_control_star_never.py create mode 100644 test_regress/t/t_event_control_star_never.v create mode 100644 test_regress/t/t_event_control_star_never_bad.out create mode 100755 test_regress/t/t_event_control_star_never_bad.py diff --git a/Changes b/Changes index 21e601503..157f59d8c 100644 --- a/Changes +++ b/Changes @@ -20,6 +20,7 @@ Verilator 5.039 devel * Add enum base data type, wire data type, and I/O versus data declaration checking per IEEE. * Add PROTOTYPEMIS error on missing and mismatching prototypes (#6206) (#6207). [Alex Solomatnikov] * Add error when trying to assign class object to variable of non-class types (#6237). [Igor Zaworski, Antmicro Ltd.] +* Add ALWNEVER warning, for `always @*` that never execute (#6291). * Add error on class 'function static'. * Add `-DVERILATOR=1` definition to compiler flags when using verilated.mk. * Support member-level triggers for virtual interfaces (#5166) (#6148). [Yilou Wang] diff --git a/docs/gen/ex_ALWNEVER_faulty.rst b/docs/gen/ex_ALWNEVER_faulty.rst new file mode 100644 index 000000000..6080a1065 --- /dev/null +++ b/docs/gen/ex_ALWNEVER_faulty.rst @@ -0,0 +1,4 @@ +.. comment: generated by t_event_control_star_never_bad +.. code-block:: sv + + always @* a = 100; diff --git a/docs/gen/ex_ALWNEVER_msg.rst b/docs/gen/ex_ALWNEVER_msg.rst new file mode 100644 index 000000000..f1c8470f8 --- /dev/null +++ b/docs/gen/ex_ALWNEVER_msg.rst @@ -0,0 +1,4 @@ +.. comment: generated by t_event_control_star_never_bad +.. code-block:: + + %Warning-ALWNEVER: example.v:1:3 'always @*' will never execute as expression list is empty (no variables read) diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 24bfd9a56..ee50f6894 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -111,6 +111,27 @@ List Of Warnings simulate correctly. +.. option:: ALWNEVER + + Warning that an `always @*` statement has no variables being read, + therefore the event list is empty, and as there are no events to wake + the process up, the always will never execute. + + Faulty example: + + .. include:: ../../docs/gen/ex_ALWNEVER_faulty.rst + + Results in: + + .. include:: ../../docs/gen/ex_ALWNEVER_msg.rst + + To repair, assuming the intent was to execute the statements at e.g. + time zero, instead use an `always_comb` statement. + + Ignoring this warning will only suppress the lint check; it will + simulate correctly. + + .. option:: ASCRANGE .. TODO better example diff --git a/src/V3Ast.h b/src/V3Ast.h index 217ff2fa7..32b1ceb99 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -406,6 +406,7 @@ public: ET_TRUE, // ET_COMBO, // Sensitive to all combo inputs to this block + ET_COMBO_STAR, // Sensitive to all combo inputs to this block (from .*) 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 @@ -423,6 +424,7 @@ public: true, // ET_TRUE false, // ET_COMBO + false, // ET_COMBO_STAR false, // ET_HYBRID false, // ET_STATIC false, // ET_INITIAL @@ -443,13 +445,13 @@ public: } const char* ascii() const { static const char* const names[] - = {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", - "COMBO", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; + = {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", "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]", + = {"[changed]", "edge", "posedge", "negedge", "[event]", "[true]", "*", "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; return names[m_e]; } diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 05468ab53..84aef1b07 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1533,7 +1533,10 @@ public: AstNodeVarRef* varrefp() const { return VN_CAST(sensp(), NodeVarRef); } // bool isClocked() const { return edgeType().clockedStmt(); } - bool isCombo() const { return edgeType() == VEdgeType::ET_COMBO; } + bool isComboOrStar() const { + return edgeType() == VEdgeType::ET_COMBO || edgeType() == VEdgeType::ET_COMBO_STAR; + } + bool isComboStar() const { return edgeType() == VEdgeType::ET_COMBO_STAR; } bool isHybrid() const { return edgeType() == VEdgeType::ET_HYBRID; } bool isStatic() const { return edgeType() == VEdgeType::ET_STATIC; } bool isInitial() const { return edgeType() == VEdgeType::ET_INITIAL; } @@ -1550,6 +1553,10 @@ public: addSensesp(sensesp); } ASTGEN_MEMBERS_AstSenTree; + bool sameNode(const AstNode* samep) const override { + const AstSenTree* const asamep = VN_DBG_AS(samep, SenTree); + return m_multi == asamep->m_multi; + } void dump(std::ostream& str) const override; void dumpJson(std::ostream& str) const override; bool maybePointedTo() const override VL_MT_SAFE { return true; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 821fd09a1..a4f223e32 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1254,7 +1254,7 @@ bool AstSenTree::hasFinal() const { 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)) { - if (senp->isCombo()) return true; + if (senp->isComboOrStar()) return true; } return false; } diff --git a/src/V3Clock.cpp b/src/V3Clock.cpp index 3627187b8..10c10d057 100644 --- a/src/V3Clock.cpp +++ b/src/V3Clock.cpp @@ -80,8 +80,10 @@ class ClockVisitor final : public VNVisitor { AstNodeExpr* senEqnp = nullptr; for (AstSenItem* senp = nodesp; senp; senp = VN_AS(senp->nextp(), SenItem)) { UASSERT_OBJ(senp->edgeType() == VEdgeType::ET_TRUE, senp, "Should have been lowered"); - AstNodeExpr* const senOnep = senp->sensp()->cloneTree(false); - senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep; + if (senp->sensp()) { + AstNodeExpr* const senOnep = senp->sensp()->cloneTree(false); + senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep; + } } return senEqnp; } diff --git a/src/V3Delayed.cpp b/src/V3Delayed.cpp index c812f83c4..a1c4f565a 100644 --- a/src/V3Delayed.cpp +++ b/src/V3Delayed.cpp @@ -1119,7 +1119,8 @@ class DelayedVisitor final : public VNVisitor { // First gather all senItems AstSenItem* senItemp = nullptr; for (AstSenTree* const domainp : m_timingDomains) { - senItemp = AstNode::addNext(senItemp, domainp->sensesp()->cloneTree(true)); + if (domainp->sensesp()) + senItemp = AstNode::addNext(senItemp, domainp->sensesp()->cloneTree(true)); } m_timingDomains.clear(); // Add them to all nba targets we gathered in this process diff --git a/src/V3Error.h b/src/V3Error.h index 224f6f987..aed9db65c 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -71,6 +71,7 @@ public: EC_FIRST_WARN, // Just a code so the program knows where to start warnings // ALWCOMBORDER, // Always_comb with unordered statements + ALWNEVER, // always will never execute ASCRANGE, // Ascending bit range vector ASSIGNDLY, // Assignment delays ASSIGNIN, // Assigning to input @@ -203,18 +204,18 @@ public: // Errors "LIFETIME", "NEEDTIMINGOPT", "NOTIMING", "PORTSHORT", "TASKNSVAR", "UNSUPPORTED", // Warnings - " EC_FIRST_WARN", "ALWCOMBORDER", "ASCRANGE", "ASSIGNDLY", "ASSIGNIN", "BADSTDPRAGMA", - "BADVLTPRAGMA", "BLKANDNBLK", "BLKLOOPINIT", "BLKSEQ", "BSSPACE", "CASEINCOMPLETE", - "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA", "CMPCONST", - "COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CONTASSREG", "COVERIGN", "DECLFILENAME", - "DEFOVERRIDE", "DEFPARAM", "DEPRECATED", "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", - "ENUMVALUE", "EOFNEWLINE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "IFDEPTH", - "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", - "INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "LATCH", "LITENDIAN", - "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN", "MULTITOP", - "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NULLPORT", "PARAMNODEFAULT", - "PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL", - "PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED", + " EC_FIRST_WARN", "ALWCOMBORDER", "ALWNEVER", "ASCRANGE", "ASSIGNDLY", "ASSIGNIN", + "BADSTDPRAGMA", "BADVLTPRAGMA", "BLKANDNBLK", "BLKLOOPINIT", "BLKSEQ", "BSSPACE", + "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", + "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CONTASSREG", + "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED", "ENCAPSULATED", + "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "GENCLK", "GENUNNAMED", + "HIERBLOCK", "IFDEPTH", "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", + "IMPORTSTAR", "IMPURE", "INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", + "LATCH", "LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", + "MULTIDRIVEN", "MULTITOP", "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NULLPORT", + "PARAMNODEFAULT", "PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND", + "PKGNODECL", "PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED", "PROTOTYPEMIS", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY", "SELRANGE", "SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR", "STMTDLY", "SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN", "UNOPT", diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index f98396b24..0e3901af1 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -835,7 +835,7 @@ class LinkParseVisitor final : public VNVisitor { if (alwaysp && alwaysp->keyword() == VAlwaysKwd::ALWAYS_COMB) { alwaysp->v3error("Event control statements not legal under always_comb " "(IEEE 1800-2023 9.2.2.2.2)\n" - << nodep->warnMore() << "... Suggest use a normal 'always'"); + << alwaysp->warnMore() << "... Suggest use a normal 'always'"); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (alwaysp && !alwaysp->sentreep()) { // If the event control is at the top, move the sentree to the always diff --git a/src/V3SchedPartition.cpp b/src/V3SchedPartition.cpp index eeeee8cb4..28048f098 100644 --- a/src/V3SchedPartition.cpp +++ b/src/V3SchedPartition.cpp @@ -162,9 +162,11 @@ class SchedGraphBuilder final : public VNVisitor { SchedSenVertex* const vtxp = new SchedSenVertex{m_graphp, senItemp}; // Connect up the variable references - senItemp->sensp()->foreach([&](AstVarRef* refp) { - new V3GraphEdge{m_graphp, getVarVertex(refp->varScopep()), vtxp, 1}; - }); + if (senItemp->sensp()) { + senItemp->sensp()->foreach([&](AstVarRef* refp) { + new V3GraphEdge{m_graphp, getVarVertex(refp->varScopep()), vtxp, 1}; + }); + } // Store back to hash map so we can find it next time pair.first->second = vtxp; diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 48dd9e3f5..d70f3ed21 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -905,8 +905,11 @@ class TimingControlVisitor final : public VNVisitor { void visit(AstEventControl* nodep) override { // 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->sentreep()) + if (!nodep->sentreep()) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: no sense equation (@*)"); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } FileLine* const flp = nodep->fileline(); // Relink child statements after the event control if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBackWithNext()); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 834e4b165..6a7fd368c 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -6556,6 +6556,7 @@ class WidthVisitor final : public VNVisitor { userIterateChildren(nodep, nullptr); } void visit(AstSenItem* nodep) override { + if (nodep->isComboStar()) return; 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 diff --git a/src/V3WidthCommit.cpp b/src/V3WidthCommit.cpp index 64e35b3ea..20cc4677d 100644 --- a/src/V3WidthCommit.cpp +++ b/src/V3WidthCommit.cpp @@ -168,6 +168,36 @@ private: } } } + void visit(AstAlways* nodep) override { + // As have not optimized SenTrees yet, an 'always .*' will be on first and only SenItem + if (nodep->sentreep() && nodep->sentreep()->sensesp() + && nodep->sentreep()->sensesp()->isComboStar()) { + const bool noReads = nodep->forall( + [&](const AstNodeVarRef* refp) { return !refp->access().isReadOrRW(); }); + if (noReads) { + nodep->v3warn(ALWNEVER, "'always @*' will never execute as expression list is " + "empty (no variables read)\n" + << nodep->warnMore() + << "... Suggest use 'always_comb'"); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } + } + // Iterate will delete ComboStar sentrees, so after above + iterateChildren(nodep); + editDType(nodep); + } + void visit(AstSenTree* nodep) override { + if (nodep->sensesp() && nodep->sensesp()->isComboStar()) { + UASSERT_OBJ(!nodep->sensesp()->nextp(), nodep, "Shouldn't be senitems after .*"); + // Make look like standalone always + // (Rest of code assumed this before .* existed) + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } + iterateChildren(nodep); + editDType(nodep); + } void visit(AstAttrOf* nodep) override { switch (nodep->attrType()) { case VAttrType::FUNC_ARG_PROTO: // FALLTHRU diff --git a/src/verilog.y b/src/verilog.y index 8f9051fdf..d2fd145c0 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -160,14 +160,17 @@ public: return new AstGatePin{rangep->fileline(), exprp, rangep->cloneTree(true)}; } } - AstSenTree* createClockSenTree(FileLine* fl, AstNodeExpr* exprp) { + AstSenTree* createSenTreeChanged(FileLine* fl, AstNodeExpr* exprp) { return new AstSenTree{fl, new AstSenItem{fl, VEdgeType::ET_CHANGED, exprp}}; } + AstSenTree* createSenTreeDotStar(FileLine* fl) { + return new AstSenTree{fl, new AstSenItem{fl, VEdgeType::ET_COMBO_STAR, nullptr}}; + } AstNodeExpr* createGlobalClockParseRef(FileLine* fl) { return new AstParseRef{fl, VParseRefExp::PX_TEXT, "__024global_clock", nullptr, nullptr}; } AstSenTree* createGlobalClockSenTree(FileLine* fl) { - return createClockSenTree(fl, createGlobalClockParseRef(fl)); + return createSenTreeChanged(fl, createGlobalClockParseRef(fl)); } AstNode* createNettype(FileLine* fl, const string& name) { // As nettypes are unsupported, we just alias to logic @@ -2817,7 +2820,6 @@ module_common_item: // ==IEEE: module_common_item ; always_construct: // IEEE: == always_construct - // // Verilator only - event_control attached to always yALWAYS stmtBlock { $$ = new AstAlways{$1, VAlwaysKwd::ALWAYS, nullptr, $2}; } | yALWAYS_FF stmtBlock { $$ = new AstAlways{$1, VAlwaysKwd::ALWAYS_FF, nullptr, $2}; } | yALWAYS_LATCH stmtBlock { $$ = new AstAlways{$1, VAlwaysKwd::ALWAYS_LATCH, nullptr, $2}; } @@ -3492,14 +3494,14 @@ attr_event_controlE: attr_event_control: // ==IEEE: event_control '@' '(' event_expression ')' { $$ = new AstSenTree{$1, $3}; } - | '@' '(' '*' ')' { $$ = nullptr; } - | '@' '*' { $$ = nullptr; } + | '@' '(' '*' ')' { $$ = GRAMMARP->createSenTreeDotStar($1); } + | '@' '*' { $$ = GRAMMARP->createSenTreeDotStar($1); } ; event_control: // ==IEEE: event_control // UNSUP: Needs alignment with IEEE event_control and clocking_event - '@' '(' '*' ')' { $$ = nullptr; } - | '@' '*' { $$ = nullptr; } + '@' '(' '*' ')' { $$ = GRAMMARP->createSenTreeDotStar($1); } + | '@' '*' { $$ = GRAMMARP->createSenTreeDotStar($1); } // // IEEE: clocking_event | '@' '(' event_expression ')' { $$ = new AstSenTree{$1, $3}; } // // IEEE: hierarchical_event_identifier @@ -4464,7 +4466,7 @@ system_f_call_or_t: // IEEE: part of system_tf_call (can be task | yD_CEIL '(' expr ')' { $$ = new AstCeilD{$1, $3}; } | yD_CHANGED '(' expr ')' { $$ = new AstLogNot{$1, new AstStable{$1, $3, nullptr}}; } | yD_CHANGED '(' expr ',' expr ')' - { $$ = new AstLogNot{$1, new AstStable{$1, $3, GRAMMARP->createClockSenTree($1, $5)}}; } + { $$ = new AstLogNot{$1, new AstStable{$1, $3, GRAMMARP->createSenTreeChanged($1, $5)}}; } | yD_CHANGED_GCLK '(' expr ')' { $$ = new AstLogNot{$1, new AstStable{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}}; } | yD_CLOG2 '(' expr ')' { $$ = new AstCLog2{$1, $3}; } @@ -4487,7 +4489,7 @@ system_f_call_or_t: // IEEE: part of system_tf_call (can be task | yD_DIST_UNIFORM '(' expr ',' expr ',' expr ')' { $$ = new AstDistUniform{$1, $3, $5, $7}; } | yD_EXP '(' expr ')' { $$ = new AstExpD{$1, $3}; } | yD_FELL '(' expr ')' { $$ = new AstFell{$1, $3, nullptr}; } - | yD_FELL '(' expr ',' expr ')' { $$ = new AstFell{$1, $3, GRAMMARP->createClockSenTree($1, $5)}; } + | yD_FELL '(' expr ',' expr ')' { $$ = new AstFell{$1, $3, GRAMMARP->createSenTreeChanged($1, $5)}; } | yD_FELL_GCLK '(' expr ')' { $$ = new AstFell{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}; } | yD_FEOF '(' expr ')' { $$ = new AstFEof{$1, $3}; } | yD_FERROR '(' expr ',' idClassSel ')' { $$ = new AstFError{$1, $3, $5}; } @@ -4539,7 +4541,7 @@ system_f_call_or_t: // IEEE: part of system_tf_call (can be task | yD_RIGHT '(' exprOrDataType ')' { $$ = new AstAttrOf{$1, VAttrType::DIM_RIGHT, $3, nullptr}; } | yD_RIGHT '(' exprOrDataType ',' expr ')' { $$ = new AstAttrOf{$1, VAttrType::DIM_RIGHT, $3, $5}; } | yD_ROSE '(' expr ')' { $$ = new AstRose{$1, $3, nullptr}; } - | yD_ROSE '(' expr ',' expr ')' { $$ = new AstRose{$1, $3, GRAMMARP->createClockSenTree($1, $5)}; } + | yD_ROSE '(' expr ',' expr ')' { $$ = new AstRose{$1, $3, GRAMMARP->createSenTreeChanged($1, $5)}; } | yD_ROSE_GCLK '(' expr ')' { $$ = new AstRose{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}; } | yD_RTOI '(' expr ')' { $$ = new AstRToIS{$1, $3}; } | yD_SAMPLED '(' expr ')' { $$ = new AstSampled{$1, $3}; } @@ -4555,7 +4557,7 @@ system_f_call_or_t: // IEEE: part of system_tf_call (can be task | yD_STIME parenE { $$ = new AstSel{$1, new AstTime{$1, VTimescale{VTimescale::NONE}}, 0, 32}; } | yD_STABLE '(' expr ')' { $$ = new AstStable{$1, $3, nullptr}; } - | yD_STABLE '(' expr ',' expr ')' { $$ = new AstStable{$1, $3, GRAMMARP->createClockSenTree($1, $5)}; } + | yD_STABLE '(' expr ',' expr ')' { $$ = new AstStable{$1, $3, GRAMMARP->createSenTreeChanged($1, $5)}; } | yD_STABLE_GCLK '(' expr ')' { $$ = new AstStable{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}; } | yD_TAN '(' expr ')' { $$ = new AstTanD{$1, $3}; } | yD_TANH '(' expr ')' { $$ = new AstTanhD{$1, $3}; } diff --git a/test_regress/t/t_event_control_star.out b/test_regress/t/t_event_control_star.out index c8682b945..d33d0e424 100644 --- a/test_regress/t/t_event_control_star.out +++ b/test_regress/t/t_event_control_star.out @@ -1,6 +1,5 @@ -%Error-UNSUPPORTED: t/t_event_control_star.v:19:14: Unsupported: no sense equation (@*) +%Error-UNSUPPORTED: t/t_event_control_star.v:19:6: Unsupported: no sense equation (@*) 19 | @* a = c; - | ^ + | ^ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Verilator internal fault, sorry. Suggest trying --debug --gdbbt -%Error: Command Failed +%Error: Exiting due to diff --git a/test_regress/t/t_event_control_star_never.py b/test_regress/t/t_event_control_star_never.py new file mode 100755 index 000000000..3c530ac0d --- /dev/null +++ b/test_regress/t/t_event_control_star_never.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=['--binary', '-Wno-ALWNEVER']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_event_control_star_never.v b/test_regress/t/t_event_control_star_never.v new file mode 100644 index 000000000..e0e62bf94 --- /dev/null +++ b/test_regress/t/t_event_control_star_never.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, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +module t; + int a; + always @* a = 100; + initial begin + #1; + if (a != 0) $stop; + end +endmodule diff --git a/test_regress/t/t_event_control_star_never_bad.out b/test_regress/t/t_event_control_star_never_bad.out new file mode 100644 index 000000000..4abe8252e --- /dev/null +++ b/test_regress/t/t_event_control_star_never_bad.out @@ -0,0 +1,8 @@ +%Warning-ALWNEVER: t/t_event_control_star_never.v:9:3: 'always @*' will never execute as expression list is empty (no variables read) + : ... note: In instance 't' + : ... Suggest use 'always_comb' + 9 | always @* a = 100; + | ^~~~~~ + ... For warning description see https://verilator.org/warn/ALWNEVER?v=latest + ... Use "/* verilator lint_off ALWNEVER */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_event_control_star_never_bad.py b/test_regress/t/t_event_control_star_never_bad.py new file mode 100755 index 000000000..ec7fab69d --- /dev/null +++ b/test_regress/t/t_event_control_star_never_bad.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('linter') +test.top_filename = 't/t_event_control_star_never.v' + +root = ".." + +if not os.path.exists(root + "/.git"): + test.skip("Not in a git repository") + +test.lint(verilator_flags2=['--timing'], fails=True, expect_filename=test.golden_filename) + +test.extract(in_filename=test.top_filename, + out_filename=root + "/docs/gen/ex_ALWNEVER_faulty.rst", + lines="9-9") + +test.extract(in_filename=test.golden_filename, + out_filename=root + "/docs/gen/ex_ALWNEVER_msg.rst", + lines="1-1") + +test.passes() diff --git a/test_regress/t/t_lint_blksync_bad.v b/test_regress/t/t_lint_blksync_bad.v index f7bc51bf7..839b0db6b 100644 --- a/test_regress/t/t_lint_blksync_bad.v +++ b/test_regress/t/t_lint_blksync_bad.v @@ -26,7 +26,7 @@ module t (/*AUTOARG*/ sync_nblk <= 1'b1; end - always @* begin + always_comb begin combo_blk = 1'b1; combo_nblk <= 1'b1; end diff --git a/test_regress/t/t_math_vgen.v b/test_regress/t/t_math_vgen.v index 0111649c0..c2b0f7720 100644 --- a/test_regress/t/t_math_vgen.v +++ b/test_regress/t/t_math_vgen.v @@ -214,7 +214,7 @@ module t (/*AUTOARG*/ reg signed [ 82:0] W0226 ; //=47A4301EE3FB4133EE3DA - always @* begin : Block144 + always_comb begin : Block144 W0226 = 83'sh47A4301EE3FB4133EE3DA; if ((W0226 >>> 8'sh1a) != 83'sh7ffffff1e90c07b8fed04) if (check) $stop; end