Add ALWNEVER warning, for `always @*` that never execute (#6291) (#6303)

This commit is contained in:
Wilson Snyder 2025-08-18 12:00:53 -04:00 committed by GitHub
parent 53c59e7ac7
commit c90f9e53b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 192 additions and 42 deletions

View File

@ -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 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 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 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 error on class 'function static'.
* Add `-DVERILATOR=1` definition to compiler flags when using verilated.mk. * Add `-DVERILATOR=1` definition to compiler flags when using verilated.mk.
* Support member-level triggers for virtual interfaces (#5166) (#6148). [Yilou Wang] * Support member-level triggers for virtual interfaces (#5166) (#6148). [Yilou Wang]

View File

@ -0,0 +1,4 @@
.. comment: generated by t_event_control_star_never_bad
.. code-block:: sv
always @* a = 100;

View File

@ -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)

View File

@ -111,6 +111,27 @@ List Of Warnings
simulate correctly. 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 .. option:: ASCRANGE
.. TODO better example .. TODO better example

View File

@ -406,6 +406,7 @@ public:
ET_TRUE, ET_TRUE,
// //
ET_COMBO, // Sensitive to all combo inputs to this block 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_HYBRID, // This is like ET_COMB, but with explicit sensitivity to an expression
ET_STATIC, // static variable initializers (runs before 'initial') ET_STATIC, // static variable initializers (runs before 'initial')
ET_INITIAL, // 'initial' statements ET_INITIAL, // 'initial' statements
@ -423,6 +424,7 @@ public:
true, // ET_TRUE true, // ET_TRUE
false, // ET_COMBO false, // ET_COMBO
false, // ET_COMBO_STAR
false, // ET_HYBRID false, // ET_HYBRID
false, // ET_STATIC false, // ET_STATIC
false, // ET_INITIAL false, // ET_INITIAL
@ -443,13 +445,13 @@ public:
} }
const char* ascii() const { const char* ascii() const {
static const char* const names[] static const char* const names[]
= {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", = {"CHANGED", "BOTH", "POS", "NEG", "EVENT", "TRUE", "COMBO",
"COMBO", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"}; "COMBO_STAR", "HYBRID", "STATIC", "INITIAL", "FINAL", "NEVER"};
return names[m_e]; return names[m_e];
} }
const char* verilogKwd() const { const char* verilogKwd() const {
static const char* const names[] static const char* const names[]
= {"[changed]", "edge", "posedge", "negedge", "[event]", "[true]", = {"[changed]", "edge", "posedge", "negedge", "[event]", "[true]", "*",
"*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"}; "*", "[hybrid]", "[static]", "[initial]", "[final]", "[never]"};
return names[m_e]; return names[m_e];
} }

View File

@ -1533,7 +1533,10 @@ public:
AstNodeVarRef* varrefp() const { return VN_CAST(sensp(), NodeVarRef); } AstNodeVarRef* varrefp() const { return VN_CAST(sensp(), NodeVarRef); }
// //
bool isClocked() const { return edgeType().clockedStmt(); } 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 isHybrid() const { return edgeType() == VEdgeType::ET_HYBRID; }
bool isStatic() const { return edgeType() == VEdgeType::ET_STATIC; } bool isStatic() const { return edgeType() == VEdgeType::ET_STATIC; }
bool isInitial() const { return edgeType() == VEdgeType::ET_INITIAL; } bool isInitial() const { return edgeType() == VEdgeType::ET_INITIAL; }
@ -1550,6 +1553,10 @@ public:
addSensesp(sensesp); addSensesp(sensesp);
} }
ASTGEN_MEMBERS_AstSenTree; 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 dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override; void dumpJson(std::ostream& str) const override;
bool maybePointedTo() const override VL_MT_SAFE { return true; } bool maybePointedTo() const override VL_MT_SAFE { return true; }

View File

@ -1254,7 +1254,7 @@ bool AstSenTree::hasFinal() const {
bool AstSenTree::hasCombo() const { bool AstSenTree::hasCombo() const {
UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it"); UASSERT_OBJ(sensesp(), this, "SENTREE without any SENITEMs under it");
for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) { for (AstSenItem* senp = sensesp(); senp; senp = VN_AS(senp->nextp(), SenItem)) {
if (senp->isCombo()) return true; if (senp->isComboOrStar()) return true;
} }
return false; return false;
} }

View File

@ -80,8 +80,10 @@ class ClockVisitor final : public VNVisitor {
AstNodeExpr* senEqnp = nullptr; AstNodeExpr* senEqnp = nullptr;
for (AstSenItem* senp = nodesp; senp; senp = VN_AS(senp->nextp(), SenItem)) { for (AstSenItem* senp = nodesp; senp; senp = VN_AS(senp->nextp(), SenItem)) {
UASSERT_OBJ(senp->edgeType() == VEdgeType::ET_TRUE, senp, "Should have been lowered"); UASSERT_OBJ(senp->edgeType() == VEdgeType::ET_TRUE, senp, "Should have been lowered");
AstNodeExpr* const senOnep = senp->sensp()->cloneTree(false); if (senp->sensp()) {
senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep; AstNodeExpr* const senOnep = senp->sensp()->cloneTree(false);
senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep;
}
} }
return senEqnp; return senEqnp;
} }

View File

@ -1119,7 +1119,8 @@ class DelayedVisitor final : public VNVisitor {
// First gather all senItems // First gather all senItems
AstSenItem* senItemp = nullptr; AstSenItem* senItemp = nullptr;
for (AstSenTree* const domainp : m_timingDomains) { 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(); m_timingDomains.clear();
// Add them to all nba targets we gathered in this process // Add them to all nba targets we gathered in this process

View File

@ -71,6 +71,7 @@ public:
EC_FIRST_WARN, // Just a code so the program knows where to start warnings EC_FIRST_WARN, // Just a code so the program knows where to start warnings
// //
ALWCOMBORDER, // Always_comb with unordered statements ALWCOMBORDER, // Always_comb with unordered statements
ALWNEVER, // always will never execute
ASCRANGE, // Ascending bit range vector ASCRANGE, // Ascending bit range vector
ASSIGNDLY, // Assignment delays ASSIGNDLY, // Assignment delays
ASSIGNIN, // Assigning to input ASSIGNIN, // Assigning to input
@ -203,18 +204,18 @@ public:
// Errors // Errors
"LIFETIME", "NEEDTIMINGOPT", "NOTIMING", "PORTSHORT", "TASKNSVAR", "UNSUPPORTED", "LIFETIME", "NEEDTIMINGOPT", "NOTIMING", "PORTSHORT", "TASKNSVAR", "UNSUPPORTED",
// Warnings // Warnings
" EC_FIRST_WARN", "ALWCOMBORDER", "ASCRANGE", "ASSIGNDLY", "ASSIGNIN", "BADSTDPRAGMA", " EC_FIRST_WARN", "ALWCOMBORDER", "ALWNEVER", "ASCRANGE", "ASSIGNDLY", "ASSIGNIN",
"BADVLTPRAGMA", "BLKANDNBLK", "BLKLOOPINIT", "BLKSEQ", "BSSPACE", "CASEINCOMPLETE", "BADSTDPRAGMA", "BADVLTPRAGMA", "BLKANDNBLK", "BLKLOOPINIT", "BLKSEQ", "BSSPACE",
"CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC", "CLKDATA", "CMPCONST", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST", "CDCRSTLOGIC",
"COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CONTASSREG", "COVERIGN", "DECLFILENAME", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN", "CONTASSREG",
"DEFOVERRIDE", "DEFPARAM", "DEPRECATED", "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED", "ENCAPSULATED",
"ENUMVALUE", "EOFNEWLINE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "IFDEPTH", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "GENCLK", "GENUNNAMED",
"IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "HIERBLOCK", "IFDEPTH", "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC",
"INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE", "LATCH", "LITENDIAN", "IMPORTSTAR", "IMPURE", "INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE",
"MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN", "MULTITOP", "LATCH", "LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING",
"NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NULLPORT", "PARAMNODEFAULT", "MULTIDRIVEN", "MULTITOP", "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NULLPORT",
"PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL", "PARAMNODEFAULT", "PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND",
"PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED", "PKGNODECL", "PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED",
"PROTOTYPEMIS", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY", "SELRANGE", "PROTOTYPEMIS", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY", "SELRANGE",
"SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR", "STMTDLY", "SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR", "STMTDLY",
"SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN", "UNOPT", "SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN", "UNOPT",

View File

@ -835,7 +835,7 @@ class LinkParseVisitor final : public VNVisitor {
if (alwaysp && alwaysp->keyword() == VAlwaysKwd::ALWAYS_COMB) { if (alwaysp && alwaysp->keyword() == VAlwaysKwd::ALWAYS_COMB) {
alwaysp->v3error("Event control statements not legal under always_comb " alwaysp->v3error("Event control statements not legal under always_comb "
"(IEEE 1800-2023 9.2.2.2.2)\n" "(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); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
} else if (alwaysp && !alwaysp->sentreep()) { } else if (alwaysp && !alwaysp->sentreep()) {
// If the event control is at the top, move the sentree to the always // If the event control is at the top, move the sentree to the always

View File

@ -162,9 +162,11 @@ class SchedGraphBuilder final : public VNVisitor {
SchedSenVertex* const vtxp = new SchedSenVertex{m_graphp, senItemp}; SchedSenVertex* const vtxp = new SchedSenVertex{m_graphp, senItemp};
// Connect up the variable references // Connect up the variable references
senItemp->sensp()->foreach([&](AstVarRef* refp) { if (senItemp->sensp()) {
new V3GraphEdge{m_graphp, getVarVertex(refp->varScopep()), vtxp, 1}; 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 // Store back to hash map so we can find it next time
pair.first->second = vtxp; pair.first->second = vtxp;

View File

@ -905,8 +905,11 @@ class TimingControlVisitor final : public VNVisitor {
void visit(AstEventControl* nodep) override { void visit(AstEventControl* nodep) override {
// Do not allow waiting on local named events, as they get enqueued for clearing, but can // Do not allow waiting on local named events, as they get enqueued for clearing, but can
// go out of scope before that happens // go out of scope before that happens
if (!nodep->sentreep()) if (!nodep->sentreep()) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: no sense equation (@*)"); nodep->v3warn(E_UNSUPPORTED, "Unsupported: no sense equation (@*)");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
FileLine* const flp = nodep->fileline(); FileLine* const flp = nodep->fileline();
// Relink child statements after the event control // Relink child statements after the event control
if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBackWithNext()); if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBackWithNext());

View File

@ -6556,6 +6556,7 @@ class WidthVisitor final : public VNVisitor {
userIterateChildren(nodep, nullptr); userIterateChildren(nodep, nullptr);
} }
void visit(AstSenItem* nodep) override { void visit(AstSenItem* nodep) override {
if (nodep->isComboStar()) return;
UASSERT_OBJ(nodep->isClocked(), nodep, "Invalid edge"); UASSERT_OBJ(nodep->isClocked(), nodep, "Invalid edge");
// Optimize concat/replicate senitems; this has to be done here at the latest, otherwise we // Optimize concat/replicate senitems; this has to be done here at the latest, otherwise we
// emit WIDTHCONCAT if there are unsized constants // emit WIDTHCONCAT if there are unsized constants

View File

@ -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 { void visit(AstAttrOf* nodep) override {
switch (nodep->attrType()) { switch (nodep->attrType()) {
case VAttrType::FUNC_ARG_PROTO: // FALLTHRU case VAttrType::FUNC_ARG_PROTO: // FALLTHRU

View File

@ -160,14 +160,17 @@ public:
return new AstGatePin{rangep->fileline(), exprp, rangep->cloneTree(true)}; 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}}; 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) { AstNodeExpr* createGlobalClockParseRef(FileLine* fl) {
return new AstParseRef{fl, VParseRefExp::PX_TEXT, "__024global_clock", nullptr, nullptr}; return new AstParseRef{fl, VParseRefExp::PX_TEXT, "__024global_clock", nullptr, nullptr};
} }
AstSenTree* createGlobalClockSenTree(FileLine* fl) { AstSenTree* createGlobalClockSenTree(FileLine* fl) {
return createClockSenTree(fl, createGlobalClockParseRef(fl)); return createSenTreeChanged(fl, createGlobalClockParseRef(fl));
} }
AstNode* createNettype(FileLine* fl, const string& name) { AstNode* createNettype(FileLine* fl, const string& name) {
// As nettypes are unsupported, we just alias to logic // As nettypes are unsupported, we just alias to logic
@ -2817,7 +2820,6 @@ module_common_item<nodep>: // ==IEEE: module_common_item
; ;
always_construct<nodep>: // IEEE: == always_construct always_construct<nodep>: // IEEE: == always_construct
// // Verilator only - event_control attached to always
yALWAYS stmtBlock { $$ = new AstAlways{$1, VAlwaysKwd::ALWAYS, nullptr, $2}; } yALWAYS stmtBlock { $$ = new AstAlways{$1, VAlwaysKwd::ALWAYS, nullptr, $2}; }
| yALWAYS_FF stmtBlock { $$ = new AstAlways{$1, VAlwaysKwd::ALWAYS_FF, nullptr, $2}; } | yALWAYS_FF stmtBlock { $$ = new AstAlways{$1, VAlwaysKwd::ALWAYS_FF, nullptr, $2}; }
| yALWAYS_LATCH stmtBlock { $$ = new AstAlways{$1, VAlwaysKwd::ALWAYS_LATCH, nullptr, $2}; } | yALWAYS_LATCH stmtBlock { $$ = new AstAlways{$1, VAlwaysKwd::ALWAYS_LATCH, nullptr, $2}; }
@ -3492,14 +3494,14 @@ attr_event_controlE<senTreep>:
attr_event_control<senTreep>: // ==IEEE: event_control attr_event_control<senTreep>: // ==IEEE: event_control
'@' '(' event_expression ')' { $$ = new AstSenTree{$1, $3}; } '@' '(' event_expression ')' { $$ = new AstSenTree{$1, $3}; }
| '@' '(' '*' ')' { $$ = nullptr; } | '@' '(' '*' ')' { $$ = GRAMMARP->createSenTreeDotStar($1); }
| '@' '*' { $$ = nullptr; } | '@' '*' { $$ = GRAMMARP->createSenTreeDotStar($1); }
; ;
event_control<senTreep>: // ==IEEE: event_control event_control<senTreep>: // ==IEEE: event_control
// UNSUP: Needs alignment with IEEE event_control and clocking_event // UNSUP: Needs alignment with IEEE event_control and clocking_event
'@' '(' '*' ')' { $$ = nullptr; } '@' '(' '*' ')' { $$ = GRAMMARP->createSenTreeDotStar($1); }
| '@' '*' { $$ = nullptr; } | '@' '*' { $$ = GRAMMARP->createSenTreeDotStar($1); }
// // IEEE: clocking_event // // IEEE: clocking_event
| '@' '(' event_expression ')' { $$ = new AstSenTree{$1, $3}; } | '@' '(' event_expression ')' { $$ = new AstSenTree{$1, $3}; }
// // IEEE: hierarchical_event_identifier // // IEEE: hierarchical_event_identifier
@ -4464,7 +4466,7 @@ system_f_call_or_t<nodeExprp>: // IEEE: part of system_tf_call (can be task
| yD_CEIL '(' expr ')' { $$ = new AstCeilD{$1, $3}; } | yD_CEIL '(' expr ')' { $$ = new AstCeilD{$1, $3}; }
| yD_CHANGED '(' expr ')' { $$ = new AstLogNot{$1, new AstStable{$1, $3, nullptr}}; } | yD_CHANGED '(' expr ')' { $$ = new AstLogNot{$1, new AstStable{$1, $3, nullptr}}; }
| yD_CHANGED '(' expr ',' expr ')' | 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 ')' | yD_CHANGED_GCLK '(' expr ')'
{ $$ = new AstLogNot{$1, new AstStable{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}}; } { $$ = new AstLogNot{$1, new AstStable{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}}; }
| yD_CLOG2 '(' expr ')' { $$ = new AstCLog2{$1, $3}; } | yD_CLOG2 '(' expr ')' { $$ = new AstCLog2{$1, $3}; }
@ -4487,7 +4489,7 @@ system_f_call_or_t<nodeExprp>: // IEEE: part of system_tf_call (can be task
| yD_DIST_UNIFORM '(' expr ',' expr ',' expr ')' { $$ = new AstDistUniform{$1, $3, $5, $7}; } | yD_DIST_UNIFORM '(' expr ',' expr ',' expr ')' { $$ = new AstDistUniform{$1, $3, $5, $7}; }
| yD_EXP '(' expr ')' { $$ = new AstExpD{$1, $3}; } | yD_EXP '(' expr ')' { $$ = new AstExpD{$1, $3}; }
| yD_FELL '(' expr ')' { $$ = new AstFell{$1, $3, nullptr}; } | 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_FELL_GCLK '(' expr ')' { $$ = new AstFell{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}; }
| yD_FEOF '(' expr ')' { $$ = new AstFEof{$1, $3}; } | yD_FEOF '(' expr ')' { $$ = new AstFEof{$1, $3}; }
| yD_FERROR '(' expr ',' idClassSel ')' { $$ = new AstFError{$1, $3, $5}; } | yD_FERROR '(' expr ',' idClassSel ')' { $$ = new AstFError{$1, $3, $5}; }
@ -4539,7 +4541,7 @@ system_f_call_or_t<nodeExprp>: // IEEE: part of system_tf_call (can be task
| yD_RIGHT '(' exprOrDataType ')' { $$ = new AstAttrOf{$1, VAttrType::DIM_RIGHT, $3, nullptr}; } | yD_RIGHT '(' exprOrDataType ')' { $$ = new AstAttrOf{$1, VAttrType::DIM_RIGHT, $3, nullptr}; }
| yD_RIGHT '(' exprOrDataType ',' expr ')' { $$ = new AstAttrOf{$1, VAttrType::DIM_RIGHT, $3, $5}; } | yD_RIGHT '(' exprOrDataType ',' expr ')' { $$ = new AstAttrOf{$1, VAttrType::DIM_RIGHT, $3, $5}; }
| yD_ROSE '(' expr ')' { $$ = new AstRose{$1, $3, nullptr}; } | 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_ROSE_GCLK '(' expr ')' { $$ = new AstRose{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}; }
| yD_RTOI '(' expr ')' { $$ = new AstRToIS{$1, $3}; } | yD_RTOI '(' expr ')' { $$ = new AstRToIS{$1, $3}; }
| yD_SAMPLED '(' expr ')' { $$ = new AstSampled{$1, $3}; } | yD_SAMPLED '(' expr ')' { $$ = new AstSampled{$1, $3}; }
@ -4555,7 +4557,7 @@ system_f_call_or_t<nodeExprp>: // IEEE: part of system_tf_call (can be task
| yD_STIME parenE | yD_STIME parenE
{ $$ = new AstSel{$1, new AstTime{$1, VTimescale{VTimescale::NONE}}, 0, 32}; } { $$ = new AstSel{$1, new AstTime{$1, VTimescale{VTimescale::NONE}}, 0, 32}; }
| yD_STABLE '(' expr ')' { $$ = new AstStable{$1, $3, nullptr}; } | 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_STABLE_GCLK '(' expr ')' { $$ = new AstStable{$1, $3, GRAMMARP->createGlobalClockSenTree($1)}; }
| yD_TAN '(' expr ')' { $$ = new AstTanD{$1, $3}; } | yD_TAN '(' expr ')' { $$ = new AstTanD{$1, $3}; }
| yD_TANH '(' expr ')' { $$ = new AstTanhD{$1, $3}; } | yD_TANH '(' expr ')' { $$ = new AstTanhD{$1, $3}; }

View File

@ -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; 19 | @* a = c;
| ^ | ^
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: Verilator internal fault, sorry. Suggest trying --debug --gdbbt %Error: Exiting due to
%Error: Command Failed

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -26,7 +26,7 @@ module t (/*AUTOARG*/
sync_nblk <= 1'b1; sync_nblk <= 1'b1;
end end
always @* begin always_comb begin
combo_blk = 1'b1; combo_blk = 1'b1;
combo_nblk <= 1'b1; combo_nblk <= 1'b1;
end end

View File

@ -214,7 +214,7 @@ module t (/*AUTOARG*/
reg signed [ 82:0] W0226 ; //=47A4301EE3FB4133EE3DA reg signed [ 82:0] W0226 ; //=47A4301EE3FB4133EE3DA
always @* begin : Block144 always_comb begin : Block144
W0226 = 83'sh47A4301EE3FB4133EE3DA; W0226 = 83'sh47A4301EE3FB4133EE3DA;
if ((W0226 >>> 8'sh1a) != 83'sh7ffffff1e90c07b8fed04) if (check) $stop; if ((W0226 >>> 8'sh1a) != 83'sh7ffffff1e90c07b8fed04) if (check) $stop;
end end