From f67159de30b91494ca031e507553d63b7b06b436 Mon Sep 17 00:00:00 2001 From: Yogish Sekhar <160029258+ysekhar@users.noreply.github.com> Date: Sun, 10 May 2026 17:12:58 +0000 Subject: [PATCH] Extend FSM coverage detection to case-free FSMs - Use - if/else chains (#7561) --- docs/guide/simulating.rst | 28 +- src/V3FsmDetect.cpp | 643 +++++++++++++++--- test_regress/t/t_cover_fsm_beginif.out | 594 +++++++++++++++- test_regress/t/t_cover_fsm_beginif.v | 459 ++++++++++++- .../t_cover_fsm_if_unknown_enum_multi_bad.out | 12 + .../t/t_cover_fsm_if_unknown_enum_multi_bad.v | 102 +++ .../t/t_cover_fsm_negative_extract.out | 352 ++++++++++ test_regress/t/t_cover_fsm_negative_extract.v | 347 ++++++++++ .../t/t_cover_fsm_nonenum_unsupported_bad.out | 16 +- .../t/t_cover_fsm_nonenum_unsupported_bad.v | 13 + .../t/t_cover_fsm_transition_shapes_multi.v | 15 + .../t/t_fsmmulti_combo_multi_warn_bad.out | 20 + .../t/t_fsmmulti_combo_multi_warn_bad.v | 93 +++ test_regress/t/t_fsmmulti_same_bad.out | 14 +- test_regress/t/t_fsmmulti_same_bad.v | 9 + test_regress/t/t_fsmmulti_warn_bad.out | 14 +- test_regress/t/t_fsmmulti_warn_bad.v | 20 + 17 files changed, 2630 insertions(+), 121 deletions(-) diff --git a/docs/guide/simulating.rst b/docs/guide/simulating.rst index f58f3edef..0943665cf 100644 --- a/docs/guide/simulating.rst +++ b/docs/guide/simulating.rst @@ -215,8 +215,8 @@ FSM Coverage ------------ With :vlopt:`--coverage` or :vlopt:`--coverage-fsm`, Verilator can -instrument a conservative subset of single-process FSMs and report both -state coverage (`fsm_state`) and transition coverage (`fsm_arc`). +instrument a conservative subset of FSMs and report both state coverage +(`fsm_state`) and transition coverage (`fsm_arc`). This feature is currently experimental and might change in subsequent releases. In particular, the native FSM coverage extraction heuristics, @@ -224,10 +224,26 @@ releases. In particular, the native FSM coverage extraction heuristics, should be treated as subject to change while the interface settles. FSM extraction is intentionally narrow. The current implementation targets -clocked, enum-driven state machines that can be recovered directly from the -RTL. It does not claim broad support for two-process FSMs, one-hot -inference, helper-function next-state recovery, or deeply nested control -recovery. +clocked state machines that can be recovered directly from the RTL. It +recognizes scalar enum, parameter, localparam, and selected literal state +encodings in these common forms: + +- Single-process FSMs, whose state dispatch is written as ``case (state)`` + or as a top-level ``if`` / ``else if`` chain comparing the same state + variable against known state values +- Two-process and three-block FSMs, where a clocked state register is paired + with a combinational next-state block using the same supported + ``case`` or top-level ``if`` / ``else if`` dispatch forms + +Simple input guards are supported when they appear inside a recognized +state branch, or as a top-level conjunction containing exactly one state +comparison, such as ``(state_q == IDLE) && ready``. Directly traceable +predecoded state aliases, such as ``assign idle_state = (state_q == IDLE)``, +may also be used in these guarded predicates. + +Verilator does not claim broad support for arbitrary predicate +decomposition, one-hot inference, helper-function next-state recovery, +deeply nested control recovery, or cross-module state alias tracing. The following metacomments may be attached to the state variable to steer the extracted coverage model: diff --git a/src/V3FsmDetect.cpp b/src/V3FsmDetect.cpp index 2e7f2ae9f..653278ed9 100644 --- a/src/V3FsmDetect.cpp +++ b/src/V3FsmDetect.cpp @@ -40,6 +40,10 @@ VL_DEFINE_DEBUG_FUNCTIONS; namespace { +// FSM graph state ids intentionally stay scalar here; arbitrary-width Verilog +// encodings need a wider graph representation and are rejected before lowering. +using FsmStateValue = uint32_t; + // Captures one sensitivity-list entry so the lowering phase can later rebuild // an active block with the same triggering event control. struct FsmSenDesc final { @@ -58,16 +62,16 @@ struct FsmResetCondDesc final { }; class FsmResetArcDesc final { - int m_toValue = 0; // Encoded reset target state. + FsmStateValue m_toValue = 0; // Encoded reset target state. AstNode* m_nodep = nullptr; // Source node for warnings and emitted metadata. public: FsmResetArcDesc() = default; - FsmResetArcDesc(int toValue, AstNode* nodep) + FsmResetArcDesc(FsmStateValue toValue, AstNode* nodep) : m_toValue{toValue} , m_nodep{nodep} {} - int toValue() const { return m_toValue; } + FsmStateValue toValue() const { return m_toValue; } AstNode* nodep() const { return m_nodep; } }; @@ -131,10 +135,10 @@ public: private: Kind m_kind; // State vs synthetic ANY/default vertex role. string m_label; // User-facing state or pseudo-state label. - int m_value = 0; // Encoded state value for real state vertices. + FsmStateValue m_value = 0; // Encoded state value for real state vertices. protected: - FsmVertex(V3Graph* graphp, Kind kind, string label, int value) VL_MT_DISABLED + FsmVertex(V3Graph* graphp, Kind kind, string label, FsmStateValue value) VL_MT_DISABLED : V3GraphVertex{graphp}, m_kind{kind}, m_label{label}, @@ -147,7 +151,7 @@ public: bool isResetAny() const { return m_kind == Kind::RESET_ANY; } bool isDefaultAny() const { return m_kind == Kind::DEFAULT_ANY; } const string& label() const { return m_label; } - int value() const { return m_value; } + FsmStateValue value() const { return m_value; } string name() const override VL_MT_SAFE { return m_label + "=" + cvtToStr(m_value); } }; @@ -156,7 +160,7 @@ class FsmStateVertex final : public FsmVertex { VL_RTTI_IMPL(FsmStateVertex, FsmVertex) public: - FsmStateVertex(V3Graph* graphp, string label, int value) VL_MT_DISABLED + FsmStateVertex(V3Graph* graphp, string label, FsmStateValue value) VL_MT_DISABLED : FsmVertex{graphp, Kind::STATE, label, value} {} ~FsmStateVertex() override = default; @@ -226,7 +230,7 @@ class FsmGraph final : public V3Graph { bool m_resetInclude = false; // Whether reset arcs count toward coverage totals. bool m_inclCond = false; // Whether conditional arcs should be kept explicitly. FileLine* m_flp = nullptr; // Representative source location for declarations/arcs. - std::unordered_map m_stateVertices; // Value to state-vertex map. + std::unordered_map m_stateVertices; // Value to state map. FsmPseudoVertex* m_resetVertexp = nullptr; // Synthetic ANY source for reset arcs. FsmPseudoVertex* m_defaultVertexp = nullptr; // Synthetic default source for case defaults. @@ -258,15 +262,15 @@ public: FileLine* fileline() const { return m_flp; } void fileline(FileLine* flp) { m_flp = flp; } - FsmStateVertex* addStateVertex(string label, int value) VL_MT_DISABLED { + FsmStateVertex* addStateVertex(string label, FsmStateValue value) VL_MT_DISABLED { FsmStateVertex* const vertexp = new FsmStateVertex{this, label, value}; m_stateVertices.emplace(value, vertexp); return vertexp; } FsmPseudoVertex* resetAnyVertex() VL_MT_DISABLED { return m_resetVertexp; } FsmPseudoVertex* defaultAnyVertex() VL_MT_DISABLED { return m_defaultVertexp; } - FsmArcEdge* addArc(int fromValue, int toValue, bool isReset, bool isCond, bool isDefault, - FileLine* flp) VL_MT_DISABLED { + FsmArcEdge* addArc(FsmStateValue fromValue, FsmStateValue toValue, bool isReset, bool isCond, + bool isDefault, FileLine* flp) VL_MT_DISABLED { FsmStateVertex* const top = m_stateVertices.at(toValue); FsmVertex* fromp = nullptr; if (isReset) { @@ -303,6 +307,37 @@ struct FsmCaseCandidate final { AstVarScope* stateVscp = nullptr; // FSM state variable associated with that candidate. }; +// Keep the source expression with the encoded value so inferred literal FSMs can +// reuse the same state-space policy as case-item dispatch. +struct FsmStateComparison final { + AstVarScope* stateVscp = nullptr; // Compared state variable + AstNodeExpr* valuep = nullptr; // Compared constant value expression + FsmStateValue value = 0; // Encoded compared state value +}; + +// A branch is usable only after its predicate has exactly one state comparison; +// any extra predicate term is treated as an arc guard. +struct FsmIfBranch final { + AstIf* ifp = nullptr; // Source if/else-if node + AstNode* stmtsp = nullptr; // Branch body + AstNodeExpr* valuep = nullptr; // Source state value expression + FsmStateValue fromValue = 0; // Encoded source state value + bool hasTopGuard = false; // Branch condition had extra guard terms +}; + +// If-chains are kept separate from cases until graph construction so the +// existing case path remains the preferred candidate when both forms appear. +struct FsmIfChainCandidate final { + AstIf* ifp = nullptr; // Top-level if-chain node + AstVarScope* compareVscp = nullptr; // Variable used by every state comparison + std::vector branches; // Recognized state-dispatch branches + AstNode* defaultStmtsp = nullptr; // Optional final else body +}; + +// Aliases are accepted only when they are equivalent to spelling the state +// comparison inline; this avoids inferring FSM semantics from arbitrary logic. +using FsmAliasMap = std::unordered_map; + struct StateConstLabel final { string text; bool fromParam = false; @@ -310,10 +345,10 @@ struct StateConstLabel final { }; struct FsmStateSpace final { - std::vector> states; // User label and encoded value. - std::unordered_map labels; // Encoded value to user label. - string stateVarName; // Pretty tracked FSM state variable name. - bool enumBacked = false; // Whether states came from an enum declaration. + std::vector> states; // User label and encoded value + std::unordered_map labels; // Encoded value to label + AstVar* stateVarp = nullptr; // Tracked FSM state variable + bool enumBacked = false; // Whether states came from an enum declaration }; // Local shared state between the two adjacent FSM coverage phases. Detection @@ -341,9 +376,16 @@ class FsmDetectVisitor final : public VNVisitor { FsmState& m_state; AstScope* m_scopep = nullptr; std::unordered_map m_registerCandidates; + // Deferring one-block detection avoids making continuous alias support + // depend on whether the assign appears before or after the always block. + std::vector m_oneBlockAlwayss; std::vector m_comboAlwayss; std::vector m_nonComboAlwayss; std::unordered_map m_comboPaired; + // Continuous aliases are order-independent, while procedural aliases must + // remain source-order scoped to avoid using assignments not yet executed. + FsmAliasMap m_stateAliases; + std::unordered_set m_ambiguousStateAliases; // METHODS // Enum-backed FSMs may be wrapped in refs/typedefs; normalize to the @@ -371,8 +413,7 @@ class FsmDetectVisitor final : public VNVisitor { oneBlockCandidates(AstAlways* alwaysp) const { std::vector> candidates; AstNode* const stmtsp = alwaysp->stmtsp(); - AstIf* const firstIfp = VN_CAST(stmtsp, If); - if (firstIfp) { + if (AstIf* const firstIfp = VN_CAST(stmtsp, If)) { if (AstCase* const casep = VN_CAST(firstIfp->elsesp(), Case)) { candidates.emplace_back(casep, FsmDetectVisitor::isSimpleResetCond(firstIfp->condp()) @@ -387,6 +428,27 @@ class FsmDetectVisitor final : public VNVisitor { return candidates; } + std::vector> + oneBlockIfCandidates(AstAlways* alwaysp) const { + std::vector> candidates; + AstNode* const stmtsp = alwaysp->stmtsp(); + // Reset-else FSMs should behave like the existing case path: reset + // information is metadata, not part of steady-state dispatch. + if (AstIf* const firstIfp = VN_CAST(stmtsp, If)) { + if (AstIf* const chainp + = VN_CAST(FsmDetectVisitor::singleMeaningfulBranch(firstIfp->elsesp()), If)) { + candidates.emplace_back(chainp, + FsmDetectVisitor::isSimpleResetCond(firstIfp->condp()) + ? firstIfp->condp() + : nullptr); + } + } + for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) { + if (AstIf* const ifp = VN_CAST(nodep, If)) candidates.emplace_back(ifp, nullptr); + } + return candidates; + } + bool matchRegisterCandidate(AstAlways* alwaysp, FsmRegisterCandidate& cand) const { return FsmDetectVisitor::matchRegisterAlways(alwaysp, m_scopep, cand); } @@ -409,7 +471,7 @@ class FsmDetectVisitor final : public VNVisitor { firstIfp->thensp(), resetStateVscp, reg.resetArcs()); if (resetStatus == ResetAssignStatus::NONE || resetStateVscp != vscp) { reg.resetArcs().clear(); - int resetValue = 0; + FsmStateValue resetValue = 0; AstNode* const thenNodep = FsmDetectVisitor::singleMeaningfulBranch(firstIfp->thensp()); UASSERT_OBJ(thenNodep, firstIfp, @@ -464,6 +526,31 @@ class FsmDetectVisitor final : public VNVisitor { return match; } + ComboMatch matchIfChain(AstNode* stmtsp, const FsmIfChainCandidate& chain) const { + ComboMatch match; + for (std::unordered_map::const_iterator it + = m_registerCandidates.begin(); + it != m_registerCandidates.end(); ++it) { + const FsmRegisterCandidate& reg = it->second; + // Comparing state_d is safe only with the canonical default; + // otherwise the chain may be dispatching on already-mutated data. + if (chain.compareVscp == reg.nextVscp()) { + if (!FsmDetectVisitor::hasCanonicalNextStateDefaultBeforeCase( + stmtsp, chain.ifp, reg.stateVscp(), reg.nextVscp())) { + continue; + } + } else if (chain.compareVscp != reg.stateVscp()) { + continue; + } + AstNode* const warnNodep + = FsmDetectVisitor::ifChainSupportedTransitionNode(chain, reg.nextVscp()); + if (!warnNodep) continue; + match.matchedp = ® + match.warnNodep = warnNodep; + } + return match; + } + bool shouldWarnUnsupported(AstNode* stmtsp, AstCase* casep) const { const AstVarRef* const selp = VN_CAST(casep->exprp(), VarRef); if (!selp) return false; @@ -610,7 +697,7 @@ class FsmDetectVisitor final : public VNVisitor { static AstNodeAssign* directCondStateVarAssign(AstNode* nodep, AstVarScope*& stateVscp, AstVarScope*& fromVscp, AstNodeExpr*& condp, - int& resetValue) { + FsmStateValue& resetValue) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); @@ -629,7 +716,7 @@ class FsmDetectVisitor final : public VNVisitor { } static AstNodeAssign* directConstStateAssignNode(AstNode* nodep, AstVarScope*& stateVscp, - int& value) { + FsmStateValue& value) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); @@ -656,7 +743,7 @@ class FsmDetectVisitor final : public VNVisitor { UASSERT_OBJ(nodep, stmtp, "Empty reset branch unexpectedly survived to FSM detection"); for (;; nodep = nodep->nextp()) { AstVarScope* assignStateVscp = nullptr; - int value = 0; + FsmStateValue value = 0; AstNodeAssign* const assp = directConstStateAssignNode(nodep, assignStateVscp, value); if (!assp) return ResetAssignStatus::NONE; if (!stateVscp) stateVscp = assignStateVscp; @@ -672,15 +759,15 @@ class FsmDetectVisitor final : public VNVisitor { } } - static bool hasCanonicalNextStateDefaultBeforeCase(AstNode* stmtsp, AstCase* casep, + static bool hasCanonicalNextStateDefaultBeforeCase(AstNode* stmtsp, AstNode* targetp, AstVarScope* stateVscp, AstVarScope* nextVscp) { AstNode* const bodyp = skipLeadingIgnorableStmt(stmtsp); bool sawCanonicalDefault = false; for (AstNode* nodep = bodyp;; nodep = nodep->nextp()) { - UASSERT_OBJ(nodep, casep, - "case(state_d) candidate not found in scanned statement list"); - if (nodep == casep) return sawCanonicalDefault; + UASSERT_OBJ(nodep, targetp, + "next-state candidate not found in scanned statement list"); + if (nodep == targetp) return sawCanonicalDefault; if (AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign)) { AstVarRef* const lhsp = VN_CAST(assp->lhsp(), VarRef); AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef); @@ -700,8 +787,8 @@ class FsmDetectVisitor final : public VNVisitor { } } - static bool ifStateConstAssign(AstNode* stmtp, AstVarScope* stateVscp, int& thenValue, - int& elseValue) { + static bool ifStateConstAssign(AstNode* stmtp, AstVarScope* stateVscp, + FsmStateValue& thenValue, FsmStateValue& elseValue) { AstIf* const ifp = VN_CAST(singleMeaningfulBranch(stmtp), If); if (!ifp || !ifp->elsesp()) return false; AstVarScope* thenVscp = nullptr; @@ -725,8 +812,8 @@ class FsmDetectVisitor final : public VNVisitor { return true; } - static bool directStateCondConstAssign(AstNode* stmtp, AstVarScope* stateVscp, int& thenValue, - int& elseValue) { + static bool directStateCondConstAssign(AstNode* stmtp, AstVarScope* stateVscp, + FsmStateValue& thenValue, FsmStateValue& elseValue) { AstNodeAssign* const assp = directStateAssign(stmtp, stateVscp); if (!assp) return false; AstCond* const condp = VN_CAST(assp->rhsp(), Cond); @@ -742,11 +829,11 @@ class FsmDetectVisitor final : public VNVisitor { } AstNodeAssign* const assp = directStateAssign(itemp->stmtsp(), stateVscp); if (assp) { - int toValue = 0; + FsmStateValue toValue = 0; if (constValueStatus(assp->rhsp(), toValue) == ConstValueStatus::OK) return assp; } - int thenValue = 0; - int elseValue = 0; + FsmStateValue thenValue = 0; + FsmStateValue elseValue = 0; if (directStateCondConstAssign(itemp->stmtsp(), stateVscp, thenValue, elseValue)) { return assp; } @@ -768,10 +855,31 @@ class FsmDetectVisitor final : public VNVisitor { return nullptr; } + static AstNode* caseItemSupportedArcNodeLike(AstNode* stmtsp, AstVarScope* stateVscp) { + if (AstNodeAssign* const assp = directStateAssign(stmtsp, stateVscp)) { + FsmStateValue toValue = 0; + if (constValueStatus(assp->rhsp(), toValue) == ConstValueStatus::OK) return assp; + FsmStateValue thenValue = 0; + FsmStateValue elseValue = 0; + if (directStateCondConstAssign(stmtsp, stateVscp, thenValue, elseValue)) return assp; + } + return nullptr; + } + + static AstNode* ifChainSupportedTransitionNode(const FsmIfChainCandidate& chain, + AstVarScope* stateVscp) { + for (size_t i = 0; i < chain.branches.size(); ++i) { + const FsmIfBranch& branch = chain.branches[i]; + AstNode* const nodep = caseItemSupportedArcNodeLike(branch.stmtsp, stateVscp); + if (!nodep) return nullptr; + } + return chain.branches.front().ifp; + } + // Prefer user labels in reports. Forced non-enum FSMs prepopulate synthetic // labels, so all emitted arcs should already have a known label here. - static string labelForValue(const std::unordered_map& labels, - int value) { + static string labelForValue(const std::unordered_map& labels, + FsmStateValue value) { return labels.at(value).text; } @@ -779,7 +887,7 @@ class FsmDetectVisitor final : public VNVisitor { // time detect runs those values have already been constant-folded. enum class ConstValueStatus : uint8_t { OK, NOT_CONST, XZ, WIDE }; - static ConstValueStatus constValueStatus(AstNodeExpr* exprp, int& value) { + static ConstValueStatus constValueStatus(AstNodeExpr* exprp, FsmStateValue& value) { const AstConst* const constp = VN_CAST(exprp, Const); if (!constp) return ConstValueStatus::NOT_CONST; const V3Number& num = constp->num(); @@ -787,27 +895,179 @@ class FsmDetectVisitor final : public VNVisitor { // Some callers are still only probing candidate shapes, so wide constants // should reject the candidate instead of reporting a V3Number error. if (constp->width() > 32) return ConstValueStatus::WIDE; - value = constp->toSInt(); + value = constp->toUInt(); return ConstValueStatus::OK; } + static bool pureStateComparisonNoAlias(AstNodeExpr* exprp, FsmStateComparison& cmp) { + AstEq* const eqp = VN_CAST(exprp, Eq); + if (!eqp) return false; + + // Operand order is not semantically meaningful for state dispatch, so + // both normalized forms should classify identically. + AstVarRef* vrefp = VN_CAST(eqp->lhsp(), VarRef); + AstNodeExpr* valuep = eqp->rhsp(); + if (!vrefp) { + vrefp = VN_AS(eqp->rhsp(), VarRef); + valuep = eqp->lhsp(); + } + + FsmStateValue value = 0; + if (constValueStatus(valuep, value) != ConstValueStatus::OK) return false; + cmp.stateVscp = vrefp->varScopep(); + cmp.valuep = valuep; + cmp.value = value; + return true; + } + + static bool pureStateComparison(AstNodeExpr* exprp, const FsmAliasMap& aliases, + FsmStateComparison& cmp) { + if (pureStateComparisonNoAlias(exprp, cmp)) return true; + // Bare predicates are too broad for FSM inference unless a prior alias + // proves they are exactly a state comparison. + if (AstVarRef* const vrefp = VN_CAST(exprp, VarRef)) { + const FsmAliasMap::const_iterator it = aliases.find(vrefp->varScopep()); + if (it == aliases.end()) return false; + cmp = it->second; + return true; + } + return false; + } + + static bool supportedTopLevelGuard(AstNodeExpr* exprp) { + // These terms can combine multiple dispatch choices into one branch, so + // treating them as ordinary guards would over-infer the FSM shape. + if (VN_IS(exprp, Or)) return false; + if (VN_IS(exprp, RedAnd)) return false; + if (VN_IS(exprp, RedOr)) return false; + if (VN_IS(exprp, RedXor)) return false; + return true; + } + + static bool resolveIfPredicate(AstNodeExpr* exprp, const FsmAliasMap& aliases, + FsmStateComparison& cmp, bool& hasGuard) { + std::vector terms; + std::vector pending; + pending.push_back(exprp); + // Top-level conjunction is the only decomposition we can map cleanly to + // one source state plus optional transition guards. + while (!pending.empty()) { + AstNodeExpr* const nodep = pending.back(); + pending.pop_back(); + if (AstAnd* const andp = VN_CAST(nodep, And)) { + pending.push_back(andp->rhsp()); + pending.push_back(andp->lhsp()); + } else { + terms.push_back(nodep); + } + } + + bool sawComparison = false; + for (size_t i = 0; i < terms.size(); ++i) { + AstNodeExpr* const termp = terms[i]; + FsmStateComparison termCmp; + if (pureStateComparison(termp, aliases, termCmp /*ref*/)) { + if (sawComparison) return false; + cmp = termCmp; + sawComparison = true; + continue; + } + if (!supportedTopLevelGuard(termp)) return false; + hasGuard = true; + } + return sawComparison; + } + + static void addAlias(FsmAliasMap& aliases, std::unordered_set& ambiguous, + AstVarScope* aliasVscp, const FsmStateComparison& cmp) { + if (ambiguous.find(aliasVscp) != ambiguous.end()) return; + const FsmAliasMap::iterator it = aliases.find(aliasVscp); + if (it == aliases.end()) { + aliases.emplace(aliasVscp, cmp); + return; + } + // Conflicting alias definitions make the predicate ambiguous, and + // ambiguous aliases are worse than missing an optional FSM. + if (it->second.stateVscp == cmp.stateVscp && it->second.value == cmp.value) return; + aliases.erase(aliasVscp); + ambiguous.emplace(aliasVscp); + return; + } + + static void collectAliasFromAssign(AstNodeAssign* assp, FsmAliasMap& aliases, + std::unordered_set& ambiguous) { + AstVarRef* const lhsp = VN_CAST(assp->lhsp(), VarRef); + if (!lhsp) return; + FsmStateComparison cmp; + // Guarded aliases blur dispatch and transition conditions, so require a + // pure comparison and let guards live at the use site. + if (!pureStateComparisonNoAlias(assp->rhsp(), cmp /*ref*/)) return; + addAlias(aliases /*ref*/, ambiguous /*ref*/, lhsp->varScopep(), cmp); + } + + FsmAliasMap localAliasesBefore(AstNode* stmtsp, AstNode* limitp) const { + FsmAliasMap aliases = m_stateAliases; + std::unordered_set ambiguous = m_ambiguousStateAliases; + // Procedural aliases cannot be applied before their assignment without + // changing the meaning of the surrounding always block. + for (AstNode* nodep = skipLeadingIgnorableStmt(stmtsp); nodep && nodep != limitp; + nodep = nodep->nextp()) { + if (AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign)) { + collectAliasFromAssign(assp, aliases /*ref*/, ambiguous /*ref*/); + } + } + for (const AstVarScope* const vscp : ambiguous) aliases.erase(vscp); + return aliases; + } + + static bool collectIfChain(AstIf* ifp, const FsmAliasMap& aliases, + FsmIfChainCandidate& chain) { + chain.ifp = ifp; + std::unordered_set seenValues; + AstIf* curp = ifp; + // Only the top-level spine represents dispatch; treating nested branch + // logic as additional source states would invent transitions. + while (true) { + FsmStateComparison cmp; + bool hasGuard = false; + if (!resolveIfPredicate(curp->condp(), aliases, cmp, hasGuard)) return false; + if (chain.compareVscp && chain.compareVscp != cmp.stateVscp) return false; + if (!seenValues.insert(cmp.value).second) return false; + chain.compareVscp = cmp.stateVscp; + chain.branches.push_back(FsmIfBranch{curp, curp->thensp(), cmp.valuep, cmp.value, + hasGuard}); + + AstNode* const elseNodep + = singleMeaningfulBranch(skipLeadingIgnorableStmt(curp->elsesp())); + if (!elseNodep) break; + if (AstIf* const elseIfp = VN_CAST(elseNodep, If)) { + curp = elseIfp; + continue; + } + chain.defaultStmtsp = elseNodep; + break; + } + return chain.branches.size() >= 2; + } + // Enum-backed FSMs should only use values that were interned as known states. // If a constant transition references some other encoding, warn and skip FSM // instrumentation for that edge rather than silently dropping it or turning // optional coverage into a hard compile failure. - static bool validateKnownStateValue(AstNode* nodep, const FsmStateSpace& stateSpace, int value, - const string& role) { + static bool validateKnownStateValue(AstNode* nodep, const FsmStateSpace& stateSpace, + FsmStateValue value, const string& role) { if (stateSpace.labels.find(value) != stateSpace.labels.end()) return true; if (stateSpace.enumBacked) { const string enumRole = role == "source" ? "case item value" : "assigned value"; nodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum state variable " - + stateSpace.stateVarName + ": " + enumRole + " " + + stateSpace.stateVarp->prettyNameQ() + ": " + enumRole + + " " + cvtToStr(value) + " is not present in the declared enum"); return false; } nodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum state variable " - + stateSpace.stateVarName + ": " + role + " value " + + stateSpace.stateVarp->prettyNameQ() + ": " + role + " value " + cvtToStr(value) + " is not present in the inferred state space"); return false; @@ -819,7 +1079,7 @@ class FsmDetectVisitor final : public VNVisitor { return StateConstLabel{constp->name(), false, 0}; } - static void updateStateLabel(FsmStateSpace& stateSpace, int value, + static void updateStateLabel(FsmStateSpace& stateSpace, FsmStateValue value, const StateConstLabel& label) { stateSpace.states.at(stateSpace.labels.at(value).stateIndex).first = label.text; } @@ -843,7 +1103,7 @@ class FsmDetectVisitor final : public VNVisitor { = collectConstStateAssigns(ifp->thensp(), resetStateVscp, cand.resetArcs()); if (resetStatus == ResetAssignStatus::NONE) { cand.resetArcs().clear(); - int resetValue = 0; + FsmStateValue resetValue = 0; AstNode* const thenNodep = singleMeaningfulBranch(ifp->thensp()); UASSERT_OBJ(thenNodep, ifp, "reset fallback requires a non-empty reset branch"); if (!directConstStateAssignNode(thenNodep, resetStateVscp, resetValue)) @@ -860,7 +1120,7 @@ class FsmDetectVisitor final : public VNVisitor { cand.hasResetCond(cand.resetCond().varScopep != nullptr); } else { AstNodeExpr* resetCondp = nullptr; - int resetValue = 0; + FsmStateValue resetValue = 0; if (AstNodeAssign* const assp = directCondStateVarAssign(nodep, stateVscp, nextVscp, resetCondp, resetValue)) { cand.resetArcs().emplace_back(resetValue, assp); @@ -883,13 +1143,13 @@ class FsmDetectVisitor final : public VNVisitor { // Helper: process a single condition expression and add it to the state space. // Returns true on success, false if the state space is invalid. static bool addCondToStateSpace(AstNodeExpr* condp, FsmStateSpace& stateSpace) { - int value = 0; + FsmStateValue value = 0; const ConstValueStatus status = constValueStatus(condp, value); if (status != ConstValueStatus::OK) { if (status == ConstValueStatus::XZ) { condp->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum " "state variable " - + stateSpace.stateVarName + + stateSpace.stateVarp->prettyNameQ() + " with X/Z state encoding values"); } return false; @@ -902,7 +1162,7 @@ class FsmDetectVisitor final : public VNVisitor { if (existingLabel.text != label.text && existingLabel.fromParam && label.fromParam) { condp->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum " "state variable " - + stateSpace.stateVarName + + stateSpace.stateVarp->prettyNameQ() + " with multiple labels for the same value " + cvtToStr(value) + ": " + existingLabel.text + " and " + label.text); @@ -923,29 +1183,29 @@ class FsmDetectVisitor final : public VNVisitor { } // Build the Phase 1 state space from the tracked registered state - // variable, not from whichever signal the transition case happened to use. - static bool collectStateSpace(AstCase* casep, AstVarScope* stateVscp, - FsmStateSpace& stateSpace) { + // variable, not from whichever signal the transition statement happened to use. + static bool collectDeclaredStateSpace(AstNode* warnNodep, AstVarScope* stateVscp, + FsmStateSpace& stateSpace, bool& needsSourceValues) { AstVar* const stateVarp = stateVscp->varp(); AstEnumDType* enump = VN_CAST(unwrapEnumCandidate(stateVscp->dtypep()), EnumDType); if (!enump) enump = VN_CAST(unwrapEnumCandidate(stateVarp->dtypep()), EnumDType); const bool forced = stateVarp->attrFsmState(); - stateSpace.stateVarName = stateVscp->prettyNameQ(); + stateSpace.stateVarp = stateVarp; if (enump) { if (stateVscp->width() > 32) { - casep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum-typed state " - "variable " - + stateSpace.stateVarName + " with width " - + cvtToStr(stateVscp->width()) - + " wider than 32 bits"); + warnNodep->v3warn(COVERIGN, + "Ignoring unsupported: FSM coverage on enum-typed state " + "variable " + + stateSpace.stateVarp->prettyNameQ() + " with width " + + cvtToStr(stateVscp->width()) + " wider than 32 bits"); return false; } stateSpace.enumBacked = true; for (AstEnumItem* itemp = enump->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), EnumItem)) { const AstConst* const constp = VN_AS(itemp->valuep(), Const); - const int value = constp->toSInt(); + const FsmStateValue value = constp->toUInt(); const size_t stateIndex = stateSpace.states.size(); stateSpace.states.emplace_back(itemp->name(), value); stateSpace.labels.emplace(value, @@ -956,51 +1216,95 @@ class FsmDetectVisitor final : public VNVisitor { if (forced) { const int width = stateVarp->width(); + // Forced non-enum FSMs have no declared state list, so enumeration + // must stay small enough for this scalar graph representation. if (width >= 31) return false; const unsigned stateCount = 1U << width; - for (unsigned value = 0; value < stateCount; ++value) { + for (FsmStateValue value = 0; value < stateCount; ++value) { const string label = "S" + cvtToStr(value); const size_t stateIndex = stateSpace.states.size(); - stateSpace.states.emplace_back(label, static_cast(value)); - stateSpace.labels.emplace(static_cast(value), - StateConstLabel{label, false, stateIndex}); + stateSpace.states.emplace_back(label, value); + stateSpace.labels.emplace(value, StateConstLabel{label, false, stateIndex}); } return true; } if (stateVscp->width() > 32) { - casep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum state " - "variable " - + stateSpace.stateVarName + " with width " - + cvtToStr(stateVscp->width()) + " wider than 32 bits"); + warnNodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum state " + "variable " + + stateSpace.stateVarp->prettyNameQ() + " with width " + + cvtToStr(stateVscp->width()) + + " wider than 32 bits"); return false; } + needsSourceValues = true; + return true; + } - for (AstCaseItem* itemp = casep->itemsp(); itemp; - itemp = VN_AS(itemp->nextp(), CaseItem)) { - if (itemp->isDefault()) continue; - for (AstNodeExpr* condp = itemp->condsp(); condp; - condp = VN_AS(condp->nextp(), NodeExpr)) { - if (!addCondToStateSpace(condp, stateSpace)) return false; - } + template + static bool collectStateSpaceFromValues(AstNode* warnNodep, AstVarScope* stateVscp, + FsmStateSpace& stateSpace, + const T_ValuepVisitor& visitValueps) { + bool needsSourceValues = false; + // Cases and if-chains should share the same state-space policy; only + // the source of inferred literal values differs between the forms. + if (!collectDeclaredStateSpace(warnNodep, stateVscp, stateSpace, needsSourceValues)) { + return false; + } + if (!needsSourceValues) return true; + if (!visitValueps([&](AstNodeExpr* valuep) { + return addCondToStateSpace(valuep, stateSpace); + })) { + return false; } return stateSpace.states.size() >= 2; } + static bool collectStateSpace(AstCase* casep, AstVarScope* stateVscp, + FsmStateSpace& stateSpace) { + return collectStateSpaceFromValues( + casep, stateVscp, stateSpace, [casep](const auto& visitValuep) { + for (AstCaseItem* itemp = casep->itemsp(); itemp; + itemp = VN_AS(itemp->nextp(), CaseItem)) { + if (itemp->isDefault()) continue; + for (AstNodeExpr* condp = itemp->condsp(); condp; + condp = VN_AS(condp->nextp(), NodeExpr)) { + if (!visitValuep(condp)) return false; + } + } + return true; + }); + } + + static bool collectStateSpace(const FsmIfChainCandidate& chain, AstVarScope* stateVscp, + FsmStateSpace& stateSpace) { + return collectStateSpaceFromValues( + chain.ifp, stateVscp, stateSpace, [&chain](const auto& visitValuep) { + for (const FsmIfBranch& branch : chain.branches) { + // Reaching this point with an unresolvable source value + // would mean the if-chain classifier and emitter disagree. + UASSERT_OBJ(visitValuep(branch.valuep), branch.valuep, + "FSM if-chain source values should be prevalidated"); + } + return true; + } + ); + } + // Extract supported case-item transitions in one place so the conservative // policy for direct and ternary forms stays consistent. The false exits in // this helper are deliberate subset boundaries: they document shapes we do // not yet model in this PR and that future FSM-detection work may widen. static bool emitCaseItemArcs(FsmGraph& graph, AstCaseItem* itemp, AstVarScope* stateVscp, const FsmStateSpace& stateSpace, bool inclCond) { - std::vector> froms; + std::vector> froms; if (itemp->isDefault()) { if (!inclCond) return false; froms.emplace_back("default", 0); } else { for (AstNodeExpr* condp = itemp->condsp(); condp; condp = VN_AS(condp->nextp(), NodeExpr)) { - int value = 0; + FsmStateValue value = 0; if (constValueStatus(condp, value) != ConstValueStatus::OK) continue; if (!validateKnownStateValue(condp, stateSpace, value, "source")) return true; froms.emplace_back(labelForValue(stateSpace.labels, value), value); @@ -1009,11 +1313,11 @@ class FsmDetectVisitor final : public VNVisitor { } if (AstNodeAssign* const assp = directStateAssign(itemp->stmtsp(), stateVscp)) { - int toValue = 0; + FsmStateValue toValue = 0; const ConstValueStatus status = constValueStatus(assp->rhsp(), toValue); if (status == ConstValueStatus::OK) { if (!validateKnownStateValue(assp, stateSpace, toValue, "target")) return true; - for (const std::pair& from : froms) { + for (const std::pair& from : froms) { graph.addArc(from.second, toValue, false, false, itemp->isDefault(), assp->fileline()); } @@ -1021,16 +1325,16 @@ class FsmDetectVisitor final : public VNVisitor { } } - int thenValue = 0; - int elseValue = 0; + FsmStateValue thenValue = 0; + FsmStateValue elseValue = 0; if (directStateCondConstAssign(itemp->stmtsp(), stateVscp, thenValue, elseValue) || ifStateConstAssign(itemp->stmtsp(), stateVscp, thenValue, elseValue)) { if (!validateKnownStateValue(itemp->stmtsp(), stateSpace, thenValue, "target")) return true; if (!validateKnownStateValue(itemp->stmtsp(), stateSpace, elseValue, "target")) return true; - for (const int branchValue : {thenValue, elseValue}) { - for (const std::pair& from : froms) { + for (const FsmStateValue branchValue : {thenValue, elseValue}) { + for (const std::pair& from : froms) { graph.addArc(from.second, branchValue, false, true, itemp->isDefault(), itemp->stmtsp()->fileline()); } @@ -1041,6 +1345,45 @@ class FsmDetectVisitor final : public VNVisitor { return false; } + static void emitStmtArcsFrom(FsmGraph& graph, AstNode* stmtsp, AstVarScope* stateVscp, + const FsmStateSpace& stateSpace, FsmStateValue fromValue, + bool isDefault, bool forceCond) { + AstNodeAssign* const assp = directStateAssign(stmtsp, stateVscp); + UASSERT_OBJ(assp, stmtsp, "FSM if-chain branch should have been prevalidated"); + FsmStateValue toValue = 0; + const ConstValueStatus status = constValueStatus(assp->rhsp(), toValue); + if (status == ConstValueStatus::OK) { + if (!validateKnownStateValue(assp, stateSpace, toValue, "target")) return; + // Preserve the user's guard in coverage by marking this arc + // conditional even when the branch body is a direct assignment. + graph.addArc(fromValue, toValue, false, forceCond, isDefault, assp->fileline()); + return; + } + + FsmStateValue thenValue = 0; + FsmStateValue elseValue = 0; + const bool condAssign = directStateCondConstAssign(stmtsp, stateVscp, thenValue, elseValue); + UASSERT_OBJ(condAssign, stmtsp, "FSM if-chain branch should be a direct constant transition"); + if (!validateKnownStateValue(stmtsp, stateSpace, thenValue, "target")) return; + if (!validateKnownStateValue(stmtsp, stateSpace, elseValue, "target")) return; + for (const FsmStateValue branchValue : {thenValue, elseValue}) { + graph.addArc(fromValue, branchValue, false, true, isDefault, stmtsp->fileline()); + } + } + + static void emitIfChainArcs(FsmGraph& graph, const FsmIfChainCandidate& chain, + AstVarScope* stateVscp, const FsmStateSpace& stateSpace) { + for (size_t i = 0; i < chain.branches.size(); ++i) { + const FsmIfBranch& branch = chain.branches[i]; + // Invalid source labels mean the extracted graph would no longer + // match the resolved state space, so abandon the candidate. + if (!validateKnownStateValue(branch.ifp, stateSpace, branch.fromValue, "source")) + return; + emitStmtArcsFrom(graph, branch.stmtsp, stateVscp, stateSpace, branch.fromValue, false, + branch.hasTopGuard); + } + } + // Reset transitions are described separately because they live in the reset // branch outside the steady-state case statement. static void addResetArcs(FsmGraph& graph, const std::vector& resetArcs, @@ -1075,7 +1418,7 @@ class FsmDetectVisitor final : public VNVisitor { entry.graphp->resetInclude(reg.resetInclude()); entry.graphp->inclCond(reg.inclCond()); entry.graphp->fileline(casep->fileline()); - for (const std::pair& state : stateSpace.states) { + for (const std::pair& state : stateSpace.states) { entry.graphp->addStateVertex(state.first, state.second); } addResetArcs(*entry.graphp, reg.resetArcs(), stateSpace); @@ -1087,17 +1430,47 @@ class FsmDetectVisitor final : public VNVisitor { } } + void processIfChain(const FsmIfChainCandidate& chain, AstVarScope* assignVscp, + const FsmRegisterCandidate& reg) { + UASSERT_OBJ(assignVscp, chain.ifp, + "FSM if-chain processing requires a non-null assignment var"); + AstVarScope* const stateVscp = reg.stateVscp(); + FsmStateSpace stateSpace; + if (!collectStateSpace(chain, stateVscp, stateSpace)) return; + DetectedFsm& entry = m_state.fsms()[stateVscp]; + // Case candidates keep ownership of existing graphs; reaching this path + // means the if-chain is the only supported dispatch for this FSM. + UASSERT_OBJ(!entry.graphp, chain.ifp, "FSM if-chain graph should not already exist"); + entry.graphp.reset(new FsmGraph{}); + entry.graphp->scopep(reg.scopep()); + entry.graphp->stateAlwaysp(reg.alwaysp()); + entry.graphp->stateVarName(stateVscp->prettyName()); + entry.graphp->stateVarInternalName(stateVscp->varp()->name()); + entry.graphp->stateVarScopep(stateVscp); + entry.graphp->senses() = reg.senses(); + entry.graphp->resetCond() = reg.resetCond(); + entry.graphp->hasResetCond(reg.hasResetCond()); + entry.graphp->resetInclude(reg.resetInclude()); + entry.graphp->inclCond(reg.inclCond()); + entry.graphp->fileline(chain.ifp->fileline()); + for (const std::pair& state : stateSpace.states) { + entry.graphp->addStateVertex(state.first, state.second); + } + addResetArcs(*entry.graphp, reg.resetArcs(), stateSpace); + emitIfChainArcs(*entry.graphp, chain, assignVscp, stateSpace); + } + // Find the first supported FSM candidate in a clocked always block, warn on // additional candidates, and attach reset arcs when present. Candidate // filtering stays narrow on purpose: we prefer to skip ambiguous shapes now // and expand detection in a later PR rather than over-infer coverage from // forms we do not yet model confidently. - void processOneBlockAlways(AstAlways* alwaysp) { - const RegisterAlwaysAnalyzer analyzer{m_scopep}; + void processOneBlockAlways(const FsmComboAlways& oneBlock) { + const RegisterAlwaysAnalyzer analyzer{oneBlock.scopep()}; + AstAlways* const alwaysp = oneBlock.alwaysp(); if (!alwaysp->sentreep() || !alwaysp->sentreep()->hasEdge()) return; const std::vector> candidates = analyzer.oneBlockCandidates(alwaysp); - if (candidates.empty()) return; FsmCaseCandidate firstCand; for (const std::pair& cand : candidates) { @@ -1124,6 +1497,39 @@ class FsmDetectVisitor final : public VNVisitor { << candidateConflictContext(cand.first, firstCand)); } } + + if (firstCand.stateVscp) return; + + // Case dispatch is more explicit and pre-existing behavior depends on + // it winning when both shapes are present. + const std::vector> ifCandidates + = analyzer.oneBlockIfCandidates(alwaysp); + for (const std::pair& cand : ifCandidates) { + const FsmAliasMap aliases = localAliasesBefore(alwaysp->stmtsp(), cand.first); + FsmIfChainCandidate chain; + if (!collectIfChain(cand.first, aliases, chain)) continue; + AstVarScope* const vscp = chain.compareVscp; + if (!ifChainSupportedTransitionNode(chain, vscp)) continue; + if (!firstCand.stateVscp) { + firstCand.warnNodep = cand.first; + firstCand.stateVscp = vscp; + FsmRegisterCandidate reg; + analyzer.buildOneBlockCandidate(alwaysp, vscp, cand.second, reg); + processIfChain(chain, vscp, reg); + } else if (vscp != firstCand.stateVscp) { + cand.first->v3warn(FSMMULTI, + "FSM coverage: multiple enum-typed transition candidates found " + "in the same always block. Only the first candidate will be " + "instrumented." + << candidateConflictContext(cand.first, firstCand)); + } else { + cand.first->v3warn(COVERIGN, + "Ignoring unsupported: FSM coverage on multiple supported " + "transition candidates found in the same always block. Only " + "the first candidate will be instrumented." + << candidateConflictContext(cand.first, firstCand)); + } + } } // Phase 1 two-process pairing scans combinational always blocks only after @@ -1178,6 +1584,52 @@ class FsmDetectVisitor final : public VNVisitor { "candidate will be instrumented." << candidateConflictContext(matchedWarnNodep, firstCand)); } + if (firstCand.stateVscp) return; + + // Keep the same priority in paired combinational logic: if-chain + // support must not change which existing case FSM is instrumented. + for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) { + AstIf* const ifp = VN_CAST(nodep, If); + if (!ifp) continue; + FsmIfChainCandidate chain; + const FsmAliasMap aliases = localAliasesBefore(stmtsp, nodep); + if (!collectIfChain(ifp, aliases, chain)) continue; + const ComboAlwaysAnalyzer::ComboMatch match = analyzer.matchIfChain(stmtsp, chain); + const FsmRegisterCandidate* const matchedp = match.matchedp; + AstNode* const matchedWarnNodep = match.warnNodep; + if (!matchedp) continue; + if (!firstCand.stateVscp) { + const std::pair< + std::unordered_map::iterator, bool> + insertPair = m_comboPaired.emplace( + matchedp->stateVscp(), + FsmCaseCandidate{matchedWarnNodep, + const_cast(matchedp->stateVscp())}); + if (!insertPair.second) { + matchedWarnNodep->v3warn( + FSMMULTI, "FSM coverage: multiple supported transition candidates found " + "for the same FSM in combinational always blocks. Only the " + "first candidate will be instrumented." + << candidateConflictContext(matchedWarnNodep, + insertPair.first->second)); + continue; + } + firstCand.warnNodep = matchedWarnNodep; + firstCand.stateVscp = const_cast(matchedp->stateVscp()); + processIfChain(chain, matchedp->nextVscp(), *matchedp); + continue; + } + if (matchedp->stateVscp() != firstCand.stateVscp) { + warnComboSameAlways(matchedWarnNodep, firstCand); + continue; + } + matchedWarnNodep->v3warn(COVERIGN, + "Ignoring unsupported: FSM coverage on multiple " + "supported if-chain statements found in the same " + "combinational always block. Only the first " + "candidate will be instrumented." + << candidateConflictContext(matchedWarnNodep, firstCand)); + } } // Track the current scope so each detected FSM records the module/scope @@ -1188,11 +1640,16 @@ class FsmDetectVisitor final : public VNVisitor { iterateChildren(nodep); } - // Collect one-block FSMs immediately, strict register candidates for later - // pairing, and combinational processes for the second-stage transition - // scan. + // Collect processes first, then analyze FSM candidates once all alias and + // register information is available. void visit(AstAlways* nodep) override { - processOneBlockAlways(nodep); + if (nodep->keyword() == VAlwaysKwd::CONT_ASSIGN) { + iterateChildren(nodep); + return; + } + // This avoids making one-block if-chain detection sensitive to whether + // a continuous alias appears before or after the always block. + m_oneBlockAlwayss.emplace_back(m_scopep, nodep); const RegisterAlwaysAnalyzer analyzer{m_scopep}; FsmRegisterCandidate reg; if (analyzer.matchRegisterCandidate(nodep, reg)) { @@ -1209,6 +1666,13 @@ class FsmDetectVisitor final : public VNVisitor { } } + void visit(AstAssignW* nodep) override { + // Continuous aliases are unordered hardware connections, so source + // order should not affect whether an if-chain FSM is recognized. + collectAliasFromAssign(nodep, m_stateAliases, m_ambiguousStateAliases); + iterateChildren(nodep); + } + // Continue the walk through the rest of the design hierarchy. void visit(AstNode* nodep) override { iterateChildren(nodep); } @@ -1219,6 +1683,7 @@ public: FsmDetectVisitor(FsmState& state, AstNetlist* rootp) : m_state{state} { iterate(rootp); + for (const FsmComboAlways& oneBlock : m_oneBlockAlwayss) processOneBlockAlways(oneBlock); for (const FsmComboAlways& combo : m_comboAlwayss) processComboAlways(combo); for (const FsmComboAlways& combo : m_nonComboAlwayss) warnUnsupportedComboAlways(combo); } @@ -1234,8 +1699,8 @@ class FsmLowerVisitor final { // METHODS // Rebuild a state-typed constant using the tracked state variable // width/sign so emitted comparisons match the original representation. - static AstConst* makeStateConst(FileLine* flp, AstVarScope* vscp, int value) { - V3Number num{flp, vscp->width(), static_cast(value)}; + static AstConst* makeStateConst(FileLine* flp, AstVarScope* vscp, FsmStateValue value) { + V3Number num{flp, vscp->width(), value}; num.isSigned(vscp->dtypep()->isSigned()); return new AstConst{flp, num}; } diff --git a/test_regress/t/t_cover_fsm_beginif.out b/test_regress/t/t_cover_fsm_beginif.out index 314d1b5aa..9f49edc43 100644 --- a/test_regress/t/t_cover_fsm_beginif.out +++ b/test_regress/t/t_cover_fsm_beginif.out @@ -7,6 +7,556 @@ // SPDX-FileCopyrightText: 2026 Wilson Snyder // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + module fsm_if_enum_oneblock ( + input logic clk, + input logic rst, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; +%000008 else if (state_q == S0) state_q <= start ? S1 : S0; + // [FSM coverage] +%000001 // [fsm_arc t.enum_oneblock_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] +%000008 // [fsm_arc t.enum_oneblock_u.state_q::S0->S0] +%000001 // [fsm_arc t.enum_oneblock_u.state_q::S0->S1] +%000001 // [fsm_arc t.enum_oneblock_u.state_q::S1->S2] +%000001 // [fsm_arc t.enum_oneblock_u.state_q::S2->S0] +%000001 // [fsm_state t.enum_oneblock_u.state_q::S0] +%000001 // [fsm_state t.enum_oneblock_u.state_q::S1] +%000001 // [fsm_state t.enum_oneblock_u.state_q::S2] + else if (state_q == S1) state_q <= S2; + else if (state_q == S2) state_q <= S0; + end + endmodule + + module fsm_if_enum_twoproc ( + input logic clk, + input logic rst, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; +%000008 if (state_q == S0) state_d = start ? S1 : S0; + // [FSM coverage] +%000001 // [fsm_arc t.enum_twoproc_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] +%000008 // [fsm_arc t.enum_twoproc_u.state_q::S0->S0] +%000001 // [fsm_arc t.enum_twoproc_u.state_q::S0->S1] +%000001 // [fsm_arc t.enum_twoproc_u.state_q::S1->S2] +%000001 // [fsm_arc t.enum_twoproc_u.state_q::S2->S0] +%000001 // [fsm_state t.enum_twoproc_u.state_q::S0] +%000001 // [fsm_state t.enum_twoproc_u.state_q::S1] +%000001 // [fsm_state t.enum_twoproc_u.state_q::S2] + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end + endmodule + + module fsm_if_localparam ( + input logic clk, + input logic rst, + input logic start + ); + localparam logic [1:0] IDLE = 2'd0; + localparam logic [1:0] BUSY = 2'd1; + localparam logic [1:0] DONE = 2'd2; + + logic [1:0] state_q; + logic [1:0] state_d; + + always_comb begin + state_d = state_q; +%000008 if (state_q == IDLE) state_d = start ? BUSY : IDLE; + // [FSM coverage] +%000001 // [fsm_arc t.localparam_u.state_q::ANY->IDLE[reset]] [reset arc, excluded from %] +%000001 // [fsm_arc t.localparam_u.state_q::BUSY->DONE] +%000001 // [fsm_arc t.localparam_u.state_q::DONE->IDLE] +%000001 // [fsm_arc t.localparam_u.state_q::IDLE->BUSY] +%000008 // [fsm_arc t.localparam_u.state_q::IDLE->IDLE] +%000001 // [fsm_state t.localparam_u.state_q::BUSY] +%000001 // [fsm_state t.localparam_u.state_q::DONE] +%000001 // [fsm_state t.localparam_u.state_q::IDLE] + else if (state_q == BUSY) state_d = DONE; + else if (state_q == DONE) state_d = IDLE; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= IDLE; + else state_q <= state_d; + end + endmodule + + module fsm_if_parameter #( + parameter logic [1:0] IDLE = 2'd0, + parameter logic [1:0] BUSY = 2'd1, + parameter logic [1:0] DONE = 2'd2 + ) ( + input logic clk, + input logic rst, + input logic start + ); + logic [1:0] state_q; + logic [1:0] state_d; + + always_comb begin + state_d = state_q; +%000008 if (state_q == IDLE) state_d = start ? BUSY : IDLE; + // [FSM coverage] +%000001 // [fsm_arc t.parameter_u.state_q::ANY->IDLE[reset]] [reset arc, excluded from %] +%000001 // [fsm_arc t.parameter_u.state_q::BUSY->DONE] +%000001 // [fsm_arc t.parameter_u.state_q::DONE->IDLE] +%000001 // [fsm_arc t.parameter_u.state_q::IDLE->BUSY] +%000008 // [fsm_arc t.parameter_u.state_q::IDLE->IDLE] +%000001 // [fsm_state t.parameter_u.state_q::BUSY] +%000001 // [fsm_state t.parameter_u.state_q::DONE] +%000001 // [fsm_state t.parameter_u.state_q::IDLE] + else if (state_q == BUSY) state_d = DONE; + else if (state_q == DONE) state_d = IDLE; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= IDLE; + else state_q <= state_d; + end + endmodule + + module fsm_if_literal_forced ( + input logic clk, + input logic rst + ); + logic [1:0] state /*verilator fsm_state*/; + + always_ff @(posedge clk) begin + if (rst) state <= 2'd0; +%000003 else if (state == 2'd0) state <= 2'd1; + // [FSM coverage] +%000001 // [fsm_arc t.literal_forced_u.state::ANY->S0[reset]] [reset arc, excluded from %] +%000003 // [fsm_arc t.literal_forced_u.state::S0->S1] +%000003 // [fsm_arc t.literal_forced_u.state::S1->S2] +%000003 // [fsm_arc t.literal_forced_u.state::S2->S0] +%000003 // [fsm_state t.literal_forced_u.state::S0] +%000003 // [fsm_state t.literal_forced_u.state::S1] +%000003 // [fsm_state t.literal_forced_u.state::S2] +%000000 // [fsm_state t.literal_forced_u.state::S3] *** UNCOVERED *** + else if (state == 2'd1) state <= 2'd2; + else if (state == 2'd2) state <= 2'd0; + end + endmodule + + module fsm_if_guard_state_first ( + input logic clk, + input logic rst, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; +%000001 if ((state_q == S0) && start) state_d = S1; + // [FSM coverage] +%000001 // [fsm_arc t.guard_state_first_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] +%000001 // [fsm_arc t.guard_state_first_u.state_q::S0->S1] +%000001 // [fsm_arc t.guard_state_first_u.state_q::S1->S2] +%000001 // [fsm_arc t.guard_state_first_u.state_q::S2->S0] +%000001 // [fsm_state t.guard_state_first_u.state_q::S0] +%000001 // [fsm_state t.guard_state_first_u.state_q::S1] +%000001 // [fsm_state t.guard_state_first_u.state_q::S2] + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end + endmodule + + module fsm_if_guard_first ( + input logic clk, + input logic rst, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; +%000001 if (start && (state_q == S0)) state_d = S1; + // [FSM coverage] +%000001 // [fsm_arc t.guard_first_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] +%000001 // [fsm_arc t.guard_first_u.state_q::S0->S1] +%000001 // [fsm_arc t.guard_first_u.state_q::S1->S2] +%000001 // [fsm_arc t.guard_first_u.state_q::S2->S0] +%000001 // [fsm_state t.guard_first_u.state_q::S0] +%000001 // [fsm_state t.guard_first_u.state_q::S1] +%000001 // [fsm_state t.guard_first_u.state_q::S2] + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end + endmodule + + module fsm_if_local_alias ( + input logic clk, + input logic rst, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; + idle_state = (state_q == S0); +%000001 if (idle_state && start) state_d = S1; + // [FSM coverage] +%000001 // [fsm_arc t.local_alias_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] +%000001 // [fsm_arc t.local_alias_u.state_q::S0->S1] +%000001 // [fsm_arc t.local_alias_u.state_q::S1->S2] +%000001 // [fsm_arc t.local_alias_u.state_q::S2->S0] +%000001 // [fsm_state t.local_alias_u.state_q::S0] +%000001 // [fsm_state t.local_alias_u.state_q::S1] +%000001 // [fsm_state t.local_alias_u.state_q::S2] + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end + endmodule + + module fsm_if_continuous_alias ( + input logic clk, + input logic rst, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q; + state_t state_d; + + assign idle_state = (state_q == S0); + + always_comb begin + state_d = state_q; +%000001 if (idle_state && start) state_d = S1; + // [FSM coverage] +%000001 // [fsm_arc t.continuous_alias_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] +%000001 // [fsm_arc t.continuous_alias_u.state_q::S0->S1] +%000001 // [fsm_arc t.continuous_alias_u.state_q::S1->S2] +%000001 // [fsm_arc t.continuous_alias_u.state_q::S2->S0] +%000001 // [fsm_state t.continuous_alias_u.state_q::S0] +%000001 // [fsm_state t.continuous_alias_u.state_q::S1] +%000001 // [fsm_state t.continuous_alias_u.state_q::S2] + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end + endmodule + + module fsm_if_nested_bitand_guard ( + input logic clk, + input logic rst, + input logic start, + input logic choose + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; +~000011 if ((state_q == S0) & start) begin + // [FSM coverage] +%000001 // [fsm_arc t.nested_bitand_guard_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] + 000011 // [fsm_arc t.nested_bitand_guard_u.state_q::S0->S0] +%000000 // [fsm_arc t.nested_bitand_guard_u.state_q::S0->S1] +%000000 // [fsm_arc t.nested_bitand_guard_u.state_q::S1->S2] +%000000 // [fsm_arc t.nested_bitand_guard_u.state_q::S2->S0] +%000000 // [fsm_state t.nested_bitand_guard_u.state_q::S0] *** UNCOVERED *** +%000000 // [fsm_state t.nested_bitand_guard_u.state_q::S1] *** UNCOVERED *** +%000000 // [fsm_state t.nested_bitand_guard_u.state_q::S2] *** UNCOVERED *** + if (choose) state_d = S1; + else state_d = S0; + end else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end + endmodule + + module fsm_if_default_incl ( + input logic clk, + input logic rst, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_arc_include_cond*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = start ? S1 : S0; + else if (state_q == S1) state_d = S2; + else state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end + endmodule + + module fsm_if_oneblock_late_continuous_alias ( + input logic clk, + input logic rst, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q; + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; +%000001 else if (idle_state && start) state_q <= S1; + // [FSM coverage] +%000001 // [fsm_arc t.oneblock_late_continuous_alias_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] +%000001 // [fsm_arc t.oneblock_late_continuous_alias_u.state_q::S0->S1] +%000001 // [fsm_arc t.oneblock_late_continuous_alias_u.state_q::S1->S2] +%000001 // [fsm_arc t.oneblock_late_continuous_alias_u.state_q::S2->S0] +%000001 // [fsm_state t.oneblock_late_continuous_alias_u.state_q::S0] +%000001 // [fsm_state t.oneblock_late_continuous_alias_u.state_q::S1] +%000001 // [fsm_state t.oneblock_late_continuous_alias_u.state_q::S2] + else if (state_q == S1) state_q <= S2; + else if (state_q == S2) state_q <= S0; + end + + assign idle_state = (state_q == S0); + endmodule + + module fsm_if_nextstate_compare ( + input logic clk, + input logic rst, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; +%000008 if (state_d == S0) state_d = start ? S1 : S0; + // [FSM coverage] +%000001 // [fsm_arc t.nextstate_compare_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] +%000008 // [fsm_arc t.nextstate_compare_u.state_q::S0->S0] +%000001 // [fsm_arc t.nextstate_compare_u.state_q::S0->S1] +%000001 // [fsm_arc t.nextstate_compare_u.state_q::S1->S2] +%000001 // [fsm_arc t.nextstate_compare_u.state_q::S2->S0] +%000001 // [fsm_state t.nextstate_compare_u.state_q::S0] +%000001 // [fsm_state t.nextstate_compare_u.state_q::S1] +%000001 // [fsm_state t.nextstate_compare_u.state_q::S2] + else if (state_d == S1) state_d = S2; + else if (state_d == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end + endmodule + + module fsm_if_reversed_compare ( + input logic clk, + input logic rst + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; +%000003 else if (S0 == state_q) state_q <= S1; + // [FSM coverage] +%000001 // [fsm_arc t.reversed_compare_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] +%000003 // [fsm_arc t.reversed_compare_u.state_q::S0->S1] +%000003 // [fsm_arc t.reversed_compare_u.state_q::S1->S2] +%000003 // [fsm_arc t.reversed_compare_u.state_q::S2->S0] +%000003 // [fsm_state t.reversed_compare_u.state_q::S0] +%000003 // [fsm_state t.reversed_compare_u.state_q::S1] +%000003 // [fsm_state t.reversed_compare_u.state_q::S2] + else if (S1 == state_q) state_q <= S2; + else if (S2 == state_q) state_q <= S0; + end + endmodule + + module fsm_if_duplicate_alias ( + input logic clk, + input logic rst, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; + idle_state = (state_q == S0); + idle_state = (state_q == S0); +%000001 if (idle_state && start) state_d = S1; + // [FSM coverage] +%000001 // [fsm_arc t.duplicate_alias_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] +%000001 // [fsm_arc t.duplicate_alias_u.state_q::S0->S1] +%000001 // [fsm_arc t.duplicate_alias_u.state_q::S1->S2] +%000001 // [fsm_arc t.duplicate_alias_u.state_q::S2->S0] +%000001 // [fsm_state t.duplicate_alias_u.state_q::S0] +%000001 // [fsm_state t.duplicate_alias_u.state_q::S1] +%000001 // [fsm_state t.duplicate_alias_u.state_q::S2] + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end + endmodule + + module fsm_if_case_priority ( + input logic clk, + input logic rst + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + state_t other_q; + state_t other_d; + + always_comb begin + state_d = state_q; + other_d = other_q; +%000003 case (state_q) + // [FSM coverage] +%000001 // [fsm_arc t.case_priority_u.state_q::ANY->S0[reset]] [reset arc, excluded from %] +%000003 // [fsm_arc t.case_priority_u.state_q::S0->S1] +%000003 // [fsm_arc t.case_priority_u.state_q::S1->S2] +%000003 // [fsm_arc t.case_priority_u.state_q::S2->S0] +%000003 // [fsm_state t.case_priority_u.state_q::S0] +%000003 // [fsm_state t.case_priority_u.state_q::S1] +%000003 // [fsm_state t.case_priority_u.state_q::S2] + S0: state_d = S1; + S1: state_d = S2; + S2: state_d = S0; + default: state_d = state_q; + endcase + if (other_q == S0) other_d = S1; + else if (other_q == S1) other_d = S2; + else if (other_q == S2) other_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) begin + state_q <= S0; + other_q <= S0; + end else begin + state_q <= state_d; + other_q <= other_d; + end + end + endmodule + module t ( input logic clk ); @@ -19,21 +569,51 @@ logic rst; logic sel; + logic start; + logic choose; int cyc; state_t state /*verilator fsm_reset_arc*/; + fsm_if_enum_oneblock enum_oneblock_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_enum_twoproc enum_twoproc_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_localparam localparam_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_parameter parameter_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_literal_forced literal_forced_u (.clk(clk), .rst(rst)); + fsm_if_guard_state_first guard_state_first_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_guard_first guard_first_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_local_alias local_alias_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_continuous_alias continuous_alias_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_nested_bitand_guard nested_bitand_guard_u ( + .clk(clk), .rst(rst), .start(start), .choose(choose)); + fsm_if_default_incl default_incl_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_oneblock_late_continuous_alias oneblock_late_continuous_alias_u ( + .clk(clk), .rst(rst), .start(start)); + fsm_if_nextstate_compare nextstate_compare_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_reversed_compare reversed_compare_u (.clk(clk), .rst(rst)); + fsm_if_duplicate_alias duplicate_alias_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_case_priority case_priority_u (.clk(clk), .rst(rst)); + initial begin rst = 1'b1; sel = 1'b0; + start = 1'b0; + choose = 1'b0; cyc = 0; end always @(posedge clk) begin cyc <= cyc + 1; if (cyc == 1) rst <= 1'b0; - if (cyc == 2) sel <= 1'b1; - if (cyc == 3) sel <= 1'b0; - if (cyc == 6) begin + if (cyc == 2) begin + sel <= 1'b1; + start <= 1'b1; + end + if (cyc == 3) begin + sel <= 1'b0; + choose <= 1'b1; + start <= 1'b0; + end + if (cyc == 10) begin $write("*-* All Finished *-*\n"); $finish; end @@ -44,15 +624,15 @@ state <= S0; end else begin -%000003 case (state) +%000005 case (state) // [FSM coverage] %000001 // [fsm_arc t.state::ANY->S0[reset_include]] [reset arc, excluded from %] %000000 // [fsm_arc t.state::S0->S1] -%000003 // [fsm_arc t.state::S0->S2] +%000005 // [fsm_arc t.state::S0->S2] %000000 // [fsm_arc t.state::S1->S0] -%000002 // [fsm_state t.state::S0] +%000004 // [fsm_state t.state::S0] %000000 // [fsm_state t.state::S1] *** UNCOVERED *** -%000003 // [fsm_state t.state::S2] +%000005 // [fsm_state t.state::S2] S0: begin if (sel) begin state <= S1; diff --git a/test_regress/t/t_cover_fsm_beginif.v b/test_regress/t/t_cover_fsm_beginif.v index b42d792cc..98895846f 100644 --- a/test_regress/t/t_cover_fsm_beginif.v +++ b/test_regress/t/t_cover_fsm_beginif.v @@ -6,6 +6,429 @@ // SPDX-FileCopyrightText: 2026 Wilson Snyder // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +module fsm_if_enum_oneblock ( + input logic clk, + input logic rst, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else if (state_q == S0) state_q <= start ? S1 : S0; + else if (state_q == S1) state_q <= S2; + else if (state_q == S2) state_q <= S0; + end +endmodule + +module fsm_if_enum_twoproc ( + input logic clk, + input logic rst, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = start ? S1 : S0; + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end +endmodule + +module fsm_if_localparam ( + input logic clk, + input logic rst, + input logic start +); + localparam logic [1:0] IDLE = 2'd0; + localparam logic [1:0] BUSY = 2'd1; + localparam logic [1:0] DONE = 2'd2; + + logic [1:0] state_q; + logic [1:0] state_d; + + always_comb begin + state_d = state_q; + if (state_q == IDLE) state_d = start ? BUSY : IDLE; + else if (state_q == BUSY) state_d = DONE; + else if (state_q == DONE) state_d = IDLE; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= IDLE; + else state_q <= state_d; + end +endmodule + +module fsm_if_parameter #( + parameter logic [1:0] IDLE = 2'd0, + parameter logic [1:0] BUSY = 2'd1, + parameter logic [1:0] DONE = 2'd2 +) ( + input logic clk, + input logic rst, + input logic start +); + logic [1:0] state_q; + logic [1:0] state_d; + + always_comb begin + state_d = state_q; + if (state_q == IDLE) state_d = start ? BUSY : IDLE; + else if (state_q == BUSY) state_d = DONE; + else if (state_q == DONE) state_d = IDLE; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= IDLE; + else state_q <= state_d; + end +endmodule + +module fsm_if_literal_forced ( + input logic clk, + input logic rst +); + logic [1:0] state /*verilator fsm_state*/; + + always_ff @(posedge clk) begin + if (rst) state <= 2'd0; + else if (state == 2'd0) state <= 2'd1; + else if (state == 2'd1) state <= 2'd2; + else if (state == 2'd2) state <= 2'd0; + end +endmodule + +module fsm_if_guard_state_first ( + input logic clk, + input logic rst, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; + if ((state_q == S0) && start) state_d = S1; + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end +endmodule + +module fsm_if_guard_first ( + input logic clk, + input logic rst, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; + if (start && (state_q == S0)) state_d = S1; + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end +endmodule + +module fsm_if_local_alias ( + input logic clk, + input logic rst, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; + idle_state = (state_q == S0); + if (idle_state && start) state_d = S1; + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end +endmodule + +module fsm_if_continuous_alias ( + input logic clk, + input logic rst, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q; + state_t state_d; + + assign idle_state = (state_q == S0); + + always_comb begin + state_d = state_q; + if (idle_state && start) state_d = S1; + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end +endmodule + +module fsm_if_nested_bitand_guard ( + input logic clk, + input logic rst, + input logic start, + input logic choose +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; + if ((state_q == S0) & start) begin + if (choose) state_d = S1; + else state_d = S0; + end else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end +endmodule + +module fsm_if_default_incl ( + input logic clk, + input logic rst, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_arc_include_cond*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = start ? S1 : S0; + else if (state_q == S1) state_d = S2; + else state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end +endmodule + +module fsm_if_oneblock_late_continuous_alias ( + input logic clk, + input logic rst, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q; + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else if (idle_state && start) state_q <= S1; + else if (state_q == S1) state_q <= S2; + else if (state_q == S2) state_q <= S0; + end + + assign idle_state = (state_q == S0); +endmodule + +module fsm_if_nextstate_compare ( + input logic clk, + input logic rst, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_d == S0) state_d = start ? S1 : S0; + else if (state_d == S1) state_d = S2; + else if (state_d == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end +endmodule + +module fsm_if_reversed_compare ( + input logic clk, + input logic rst +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else if (S0 == state_q) state_q <= S1; + else if (S1 == state_q) state_q <= S2; + else if (S2 == state_q) state_q <= S0; + end +endmodule + +module fsm_if_duplicate_alias ( + input logic clk, + input logic rst, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; + idle_state = (state_q == S0); + idle_state = (state_q == S0); + if (idle_state && start) state_d = S1; + else if (state_q == S1) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) state_q <= S0; + else state_q <= state_d; + end +endmodule + +module fsm_if_case_priority ( + input logic clk, + input logic rst +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q; + state_t state_d; + state_t other_q; + state_t other_d; + + always_comb begin + state_d = state_q; + other_d = other_q; + case (state_q) + S0: state_d = S1; + S1: state_d = S2; + S2: state_d = S0; + default: state_d = state_q; + endcase + if (other_q == S0) other_d = S1; + else if (other_q == S1) other_d = S2; + else if (other_q == S2) other_d = S0; + end + + always_ff @(posedge clk) begin + if (rst) begin + state_q <= S0; + other_q <= S0; + end else begin + state_q <= state_d; + other_q <= other_d; + end + end +endmodule + module t ( input logic clk ); @@ -18,21 +441,51 @@ module t ( logic rst; logic sel; + logic start; + logic choose; int cyc; state_t state /*verilator fsm_reset_arc*/; + fsm_if_enum_oneblock enum_oneblock_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_enum_twoproc enum_twoproc_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_localparam localparam_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_parameter parameter_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_literal_forced literal_forced_u (.clk(clk), .rst(rst)); + fsm_if_guard_state_first guard_state_first_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_guard_first guard_first_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_local_alias local_alias_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_continuous_alias continuous_alias_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_nested_bitand_guard nested_bitand_guard_u ( + .clk(clk), .rst(rst), .start(start), .choose(choose)); + fsm_if_default_incl default_incl_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_oneblock_late_continuous_alias oneblock_late_continuous_alias_u ( + .clk(clk), .rst(rst), .start(start)); + fsm_if_nextstate_compare nextstate_compare_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_reversed_compare reversed_compare_u (.clk(clk), .rst(rst)); + fsm_if_duplicate_alias duplicate_alias_u (.clk(clk), .rst(rst), .start(start)); + fsm_if_case_priority case_priority_u (.clk(clk), .rst(rst)); + initial begin rst = 1'b1; sel = 1'b0; + start = 1'b0; + choose = 1'b0; cyc = 0; end always @(posedge clk) begin cyc <= cyc + 1; if (cyc == 1) rst <= 1'b0; - if (cyc == 2) sel <= 1'b1; - if (cyc == 3) sel <= 1'b0; - if (cyc == 6) begin + if (cyc == 2) begin + sel <= 1'b1; + start <= 1'b1; + end + if (cyc == 3) begin + sel <= 1'b0; + choose <= 1'b1; + start <= 1'b0; + end + if (cyc == 10) begin $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.out b/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.out index ab63ea3ff..e433db988 100644 --- a/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.out +++ b/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.out @@ -15,4 +15,16 @@ %Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:150:7: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_source_u.state_q': case item value 3 is not present in the declared enum 150 | 2'd3: state_d = S0; | ^~~~ +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:175:5: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_source_u.state_q': case item value 3 is not present in the declared enum + 175 | if (state_q == 2'd3) state_d = S0; + | ^~ +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:199:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_direct_target_u.state_q': assigned value 3 is not present in the declared enum + 199 | if (state_q == S0) state_d = 2'd3; + | ^ +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:224:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_then_target_u.state_q': assigned value 3 is not present in the declared enum + 224 | if (state_q == S0) state_d = sel ? 2'd3 : S1; + | ^ +%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:249:32: Ignoring unsupported: FSM coverage on enum state variable 't.unknown_if_else_target_u.state_q': assigned value 3 is not present in the declared enum + 249 | if (state_q == S0) state_d = sel ? S1 : 2'd3; + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.v b/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.v index 6fda6835f..ddd3c8231 100644 --- a/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.v +++ b/test_regress/t/t_cover_fsm_if_unknown_enum_multi_bad.v @@ -158,6 +158,104 @@ module unknown_source ( end endmodule +module unknown_if_source ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + /* verilator lint_off ENUMVALUE */ + if (state_q == 2'd3) state_d = S0; + /* verilator lint_on ENUMVALUE */ + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) begin + state_q <= state_d; + end +endmodule + +module unknown_if_direct_target ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + /* verilator lint_off ENUMVALUE */ + if (state_q == S0) state_d = 2'd3; + /* verilator lint_on ENUMVALUE */ + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) begin + state_q <= state_d; + end +endmodule + +module unknown_if_then_target ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + logic sel; + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + /* verilator lint_off ENUMVALUE */ + if (state_q == S0) state_d = sel ? 2'd3 : S1; + /* verilator lint_on ENUMVALUE */ + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) begin + state_q <= state_d; + end +endmodule + +module unknown_if_else_target ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + logic sel; + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + /* verilator lint_off ENUMVALUE */ + if (state_q == S0) state_d = sel ? S1 : 2'd3; + /* verilator lint_on ENUMVALUE */ + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) begin + state_q <= state_d; + end +endmodule + module t; logic clk; unknown_then unknown_then_u (.clk(clk)); @@ -165,4 +263,8 @@ module t; unknown_direct unknown_direct_u (.clk(clk)); unknown_reset unknown_reset_u (.clk(clk)); unknown_source unknown_source_u (.clk(clk)); + unknown_if_source unknown_if_source_u (.clk(clk)); + unknown_if_direct_target unknown_if_direct_target_u (.clk(clk)); + unknown_if_then_target unknown_if_then_target_u (.clk(clk)); + unknown_if_else_target unknown_if_else_target_u (.clk(clk)); endmodule diff --git a/test_regress/t/t_cover_fsm_negative_extract.out b/test_regress/t/t_cover_fsm_negative_extract.out index 808935b89..06c1fd797 100644 --- a/test_regress/t/t_cover_fsm_negative_extract.out +++ b/test_regress/t/t_cover_fsm_negative_extract.out @@ -5,6 +5,340 @@ // SPDX-FileCopyrightText: 2026 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 + module fsm_if_mixed_vars_bad ( + input logic clk + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t other_q; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = S1; + else if (other_q == S1) state_d = S2; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_one_branch_bad ( + input logic clk + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = S1; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_duplicate_bad ( + input logic clk + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = S1; + else if (state_q == S0) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_two_comparisons_bad ( + input logic clk, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if ((state_q == S0) && (state_q == S1)) state_d = S2; + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) state_q <= start ? state_d : state_q; + endmodule + + module fsm_if_or_bad ( + input logic clk + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if ((state_q == S0) || (state_q == S1)) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_alias_guard_bad ( + input logic clk, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + idle_state = (state_q == S0) && start; + if (idle_state) state_d = S1; + else if (state_q == S1) state_d = S2; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_ambiguous_alias_bad ( + input logic clk + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + idle_state = (state_q == S0); + idle_state = (state_q == S1); + idle_state = (state_q == S2); + if (idle_state) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_missing_default_bad ( + input logic clk + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = S0; + if (state_d == S0) state_d = S1; + else if (state_d == S1) state_d = S2; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_no_assign_bad ( + input logic clk + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + logic flag; + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + flag = 1'b0; + state_d = state_q; + if (state_q == S0) flag = 1'b1; + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_nonvar_compare_bad ( + input logic clk + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; +%000003 if ((state_q + 2'd0) == S0) state_d = S1; + // [FSM coverage] +%000003 // [fsm_arc t.nonvar_compare_u.state_q::S0->S1] +%000003 // [fsm_arc t.nonvar_compare_u.state_q::S1->S0] +%000003 // [fsm_state t.nonvar_compare_u.state_q::S0] +%000003 // [fsm_state t.nonvar_compare_u.state_q::S1] + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_var_rhs_compare_bad ( + input logic clk + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == state_d) state_d = S1; + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_var_target_bad ( + input logic clk, + input logic [1:0] dyn + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = state_t'(dyn); + else if (state_q == S1) state_d = S2; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_alias_other_state_bad ( + input logic clk + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + logic idle_state; + state_t state_q /*verilator fsm_state*/; + state_t other_q; + state_t state_d; + + always_comb begin + state_d = state_q; + idle_state = (state_q == S0); + idle_state = (other_q == S0); + if (idle_state) state_d = S1; + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_bit_or_bad ( + input logic clk, + input logic start + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if ((state_q == S0) | start) state_d = S1; + else if (state_q == S1) state_d = S2; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + + module fsm_if_reduction_bad ( + input logic clk + ); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (&state_q) state_d = S1; + else if (state_q == S1) state_d = S2; + if (|state_q) state_d = S2; + else if (state_q == S2) state_d = S0; + if (^state_q) state_d = S0; + else if (state_q == S0) state_d = S1; + end + + always_ff @(posedge clk) state_q <= state_d; + endmodule + module t ( input logic clk ); @@ -16,12 +350,30 @@ } state_t; int cyc; + logic start; logic side; logic [2:0] dyn_case; state_t state /*verilator fsm_reset_arc*/; + fsm_if_mixed_vars_bad mixed_vars_u (.clk(clk)); + fsm_if_one_branch_bad one_branch_u (.clk(clk)); + fsm_if_duplicate_bad duplicate_u (.clk(clk)); + fsm_if_two_comparisons_bad two_comparisons_u (.clk(clk), .start(start)); + fsm_if_or_bad or_u (.clk(clk)); + fsm_if_alias_guard_bad alias_guard_u (.clk(clk), .start(start)); + fsm_if_ambiguous_alias_bad ambiguous_alias_u (.clk(clk)); + fsm_if_missing_default_bad missing_default_u (.clk(clk)); + fsm_if_no_assign_bad no_assign_u (.clk(clk)); + fsm_if_nonvar_compare_bad nonvar_compare_u (.clk(clk)); + fsm_if_var_rhs_compare_bad var_rhs_compare_u (.clk(clk)); + fsm_if_var_target_bad var_target_u (.clk(clk), .dyn(dyn_case[1:0])); + fsm_if_alias_other_state_bad alias_other_state_u (.clk(clk)); + fsm_if_bit_or_bad bit_or_u (.clk(clk), .start(start)); + fsm_if_reduction_bad reduction_u (.clk(clk)); + initial begin cyc = 0; + start = 1'b0; side = 1'b0; dyn_case = 3'd7; end diff --git a/test_regress/t/t_cover_fsm_negative_extract.v b/test_regress/t/t_cover_fsm_negative_extract.v index 3777b1a4a..251b2a6c7 100644 --- a/test_regress/t/t_cover_fsm_negative_extract.v +++ b/test_regress/t/t_cover_fsm_negative_extract.v @@ -4,6 +4,335 @@ // SPDX-FileCopyrightText: 2026 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 +module fsm_if_mixed_vars_bad ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t other_q; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = S1; + else if (other_q == S1) state_d = S2; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_one_branch_bad ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = S1; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_duplicate_bad ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = S1; + else if (state_q == S0) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_two_comparisons_bad ( + input logic clk, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if ((state_q == S0) && (state_q == S1)) state_d = S2; + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) state_q <= start ? state_d : state_q; +endmodule + +module fsm_if_or_bad ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if ((state_q == S0) || (state_q == S1)) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_alias_guard_bad ( + input logic clk, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + idle_state = (state_q == S0) && start; + if (idle_state) state_d = S1; + else if (state_q == S1) state_d = S2; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_ambiguous_alias_bad ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + logic idle_state; + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + idle_state = (state_q == S0); + idle_state = (state_q == S1); + idle_state = (state_q == S2); + if (idle_state) state_d = S2; + else if (state_q == S2) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_missing_default_bad ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = S0; + if (state_d == S0) state_d = S1; + else if (state_d == S1) state_d = S2; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_no_assign_bad ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + logic flag; + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + flag = 1'b0; + state_d = state_q; + if (state_q == S0) flag = 1'b1; + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_nonvar_compare_bad ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if ((state_q + 2'd0) == S0) state_d = S1; + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_var_rhs_compare_bad ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == state_d) state_d = S1; + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_var_target_bad ( + input logic clk, + input logic [1:0] dyn +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = state_t'(dyn); + else if (state_q == S1) state_d = S2; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_alias_other_state_bad ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1 + } state_t; + + logic idle_state; + state_t state_q /*verilator fsm_state*/; + state_t other_q; + state_t state_d; + + always_comb begin + state_d = state_q; + idle_state = (state_q == S0); + idle_state = (other_q == S0); + if (idle_state) state_d = S1; + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_bit_or_bad ( + input logic clk, + input logic start +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if ((state_q == S0) | start) state_d = S1; + else if (state_q == S1) state_d = S2; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + +module fsm_if_reduction_bad ( + input logic clk +); + typedef enum logic [1:0] { + S0 = 2'd0, + S1 = 2'd1, + S2 = 2'd2 + } state_t; + + state_t state_q /*verilator fsm_state*/; + state_t state_d; + + always_comb begin + state_d = state_q; + if (&state_q) state_d = S1; + else if (state_q == S1) state_d = S2; + if (|state_q) state_d = S2; + else if (state_q == S2) state_d = S0; + if (^state_q) state_d = S0; + else if (state_q == S0) state_d = S1; + end + + always_ff @(posedge clk) state_q <= state_d; +endmodule + module t ( input logic clk ); @@ -15,12 +344,30 @@ module t ( } state_t; int cyc; + logic start; logic side; logic [2:0] dyn_case; state_t state /*verilator fsm_reset_arc*/; + fsm_if_mixed_vars_bad mixed_vars_u (.clk(clk)); + fsm_if_one_branch_bad one_branch_u (.clk(clk)); + fsm_if_duplicate_bad duplicate_u (.clk(clk)); + fsm_if_two_comparisons_bad two_comparisons_u (.clk(clk), .start(start)); + fsm_if_or_bad or_u (.clk(clk)); + fsm_if_alias_guard_bad alias_guard_u (.clk(clk), .start(start)); + fsm_if_ambiguous_alias_bad ambiguous_alias_u (.clk(clk)); + fsm_if_missing_default_bad missing_default_u (.clk(clk)); + fsm_if_no_assign_bad no_assign_u (.clk(clk)); + fsm_if_nonvar_compare_bad nonvar_compare_u (.clk(clk)); + fsm_if_var_rhs_compare_bad var_rhs_compare_u (.clk(clk)); + fsm_if_var_target_bad var_target_u (.clk(clk), .dyn(dyn_case[1:0])); + fsm_if_alias_other_state_bad alias_other_state_u (.clk(clk)); + fsm_if_bit_or_bad bit_or_u (.clk(clk), .start(start)); + fsm_if_reduction_bad reduction_u (.clk(clk)); + initial begin cyc = 0; + start = 1'b0; side = 1'b0; dyn_case = 3'd7; end diff --git a/test_regress/t/t_cover_fsm_nonenum_unsupported_bad.out b/test_regress/t/t_cover_fsm_nonenum_unsupported_bad.out index 1b4a31681..e467e12f5 100644 --- a/test_regress/t/t_cover_fsm_nonenum_unsupported_bad.out +++ b/test_regress/t/t_cover_fsm_nonenum_unsupported_bad.out @@ -1,15 +1,15 @@ -%Warning-COVERIGN: t/t_cover_fsm_nonenum_unsupported_bad.v:32:7: Ignoring unsupported: FSM coverage on non-enum state variable 't.wide_state' with width 33 wider than 32 bits - 32 | case (wide_state) +%Warning-COVERIGN: t/t_cover_fsm_nonenum_unsupported_bad.v:33:7: Ignoring unsupported: FSM coverage on non-enum state variable 't.wide_state' with width 33 wider than 32 bits + 33 | case (wide_state) | ^~~~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_cover_fsm_nonenum_unsupported_bad.v:47:28: Ignoring unsupported: FSM coverage on non-enum state variable 't.target_state': target value 2 is not present in the inferred state space - 47 | 2'h1: target_state <= 2'h2; +%Warning-COVERIGN: t/t_cover_fsm_nonenum_unsupported_bad.v:48:28: Ignoring unsupported: FSM coverage on non-enum state variable 't.target_state': target value 2 is not present in the inferred state space + 48 | 2'h1: target_state <= 2'h2; | ^~ -%Warning-COVERIGN: t/t_cover_fsm_nonenum_unsupported_bad.v:61:9: Ignoring unsupported: FSM coverage on non-enum state variable 't.duplicate_state' with multiple labels for the same value 0: IDLE and RESET - 61 | RESET: duplicate_state <= IDLE; +%Warning-COVERIGN: t/t_cover_fsm_nonenum_unsupported_bad.v:62:9: Ignoring unsupported: FSM coverage on non-enum state variable 't.duplicate_state' with multiple labels for the same value 0: IDLE and RESET + 62 | RESET: duplicate_state <= IDLE; | ^~~~~ -%Warning-COVERIGN: t/t_cover_fsm_nonenum_unsupported_bad.v:77:9: Ignoring unsupported: FSM coverage on non-enum state variable 't.xz_case_state' with X/Z state encoding values - 77 | 2'b1x: xz_case_state <= 2'h0; +%Warning-COVERIGN: t/t_cover_fsm_nonenum_unsupported_bad.v:78:9: Ignoring unsupported: FSM coverage on non-enum state variable 't.xz_case_state' with X/Z state encoding values + 78 | 2'b1x: xz_case_state <= 2'h0; | ^~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_nonenum_unsupported_bad.v b/test_regress/t/t_cover_fsm_nonenum_unsupported_bad.v index f1bc5ed46..139b1ee2f 100644 --- a/test_regress/t/t_cover_fsm_nonenum_unsupported_bad.v +++ b/test_regress/t/t_cover_fsm_nonenum_unsupported_bad.v @@ -23,6 +23,7 @@ module t ( logic [1:0] underscore_state; logic [1:0] xz_reset_probe_state; logic [1:0] xz_rhs_probe_state; + logic [32:0] wide_if_state /*verilator fsm_state*/; always_ff @(posedge clk) begin if (rst) begin @@ -136,6 +137,18 @@ module t ( end end + always_ff @(posedge clk) begin + if (rst) begin + wide_if_state <= 33'h0; + end + else if (wide_if_state == 33'h0) begin + wide_if_state <= 33'h1; + end + else if (wide_if_state == 33'h1) begin + wide_if_state <= 33'h0; + end + end + logic [1:0] not_const_item_state; always_ff @(posedge clk) begin diff --git a/test_regress/t/t_cover_fsm_transition_shapes_multi.v b/test_regress/t/t_cover_fsm_transition_shapes_multi.v index 7994a7809..db15fdc7e 100644 --- a/test_regress/t/t_cover_fsm_transition_shapes_multi.v +++ b/test_regress/t/t_cover_fsm_transition_shapes_multi.v @@ -731,6 +731,20 @@ module fsm_forced_wide_bad ( endmodule +module fsm_forced_if_wide_bad ( + input logic clk +); + + // Forced non-enum FSMs enumerate every possible state; 31 bits is too many. + logic [30:0] state /*verilator fsm_state*/; + + always_ff @(posedge clk) begin + if (state == 31'd0) state <= 31'd1; + else if (state == 31'd1) state <= 31'd0; + end + +endmodule + module fsm_reset_commit_mismatch_bad ( input logic clk ); @@ -852,6 +866,7 @@ module t ( fsm_normalized_if_follow_wronglhs_bad normalized_if_follow_wronglhs_bad_u (.clk(clk)); fsm_case_next_wrongrhs_bad case_next_wrongrhs_bad_u (.clk(clk)); fsm_forced_wide_bad forced_wide_bad_u (.clk(clk)); + fsm_forced_if_wide_bad forced_if_wide_bad_u (.clk(clk)); fsm_reset_commit_mismatch_bad reset_commit_mismatch_bad_u (.clk(clk)); fsm_reset_then_bad reset_then_bad_u (.clk(clk)); diff --git a/test_regress/t/t_fsmmulti_combo_multi_warn_bad.out b/test_regress/t/t_fsmmulti_combo_multi_warn_bad.out index 24a60a0cc..8985f2c91 100644 --- a/test_regress/t/t_fsmmulti_combo_multi_warn_bad.out +++ b/test_regress/t/t_fsmmulti_combo_multi_warn_bad.out @@ -12,4 +12,24 @@ t/t_fsmmulti_combo_multi_warn_bad.v:65:19: ... Location of first supported candidate for 't.split_u.state_q' 65 | S0: state_d = S1; | ^ +%Warning-FSMMULTI: t/t_fsmmulti_combo_multi_warn_bad.v:107:5: FSM coverage: multiple supported transition candidates found in the same combinational always block. Only the first candidate will be instrumented. + 107 | if (state_b_q == B0) state_b_d = B1; + | ^~ + t/t_fsmmulti_combo_multi_warn_bad.v:105:5: ... Location of first supported candidate for 't.same_if_u.state_a_q' + 105 | if (state_a_q == A0) state_a_d = A1; + | ^~ +%Warning-FSMMULTI: t/t_fsmmulti_combo_multi_warn_bad.v:140:5: FSM coverage: multiple supported transition candidates found for the same FSM in combinational always blocks. Only the first candidate will be instrumented. + 140 | if (state_q == S0) state_d = S1; + | ^~ + t/t_fsmmulti_combo_multi_warn_bad.v:134:5: ... Location of first supported candidate for 't.split_if_u.state_q' + 134 | if (state_q == S0) state_d = S1; + | ^~ +%Warning-COVERIGN: t/t_fsmmulti_combo_multi_warn_bad.v:165:5: Ignoring unsupported: FSM coverage on multiple supported if-chain statements found in the same combinational always block. Only the first candidate will be instrumented. + 165 | if (state_q == S1) state_d = S0; + | ^~ + t/t_fsmmulti_combo_multi_warn_bad.v:163:5: ... Location of first supported candidate for 't.same_same_if_u.state_q' + 163 | if (state_q == S0) state_d = S1; + | ^~ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_fsmmulti_combo_multi_warn_bad.v b/test_regress/t/t_fsmmulti_combo_multi_warn_bad.v index 28b3038d3..8902ee387 100644 --- a/test_regress/t/t_fsmmulti_combo_multi_warn_bad.v +++ b/test_regress/t/t_fsmmulti_combo_multi_warn_bad.v @@ -81,9 +81,102 @@ module split_always_warn ( /* verilator lint_on MULTIDRIVEN */ endmodule +module same_always_if_warn ( + input logic clk +); + typedef enum logic [1:0] { + A0, + A1 + } a_state_t; + + typedef enum logic [1:0] { + B0, + B1 + } b_state_t; + + a_state_t state_a_q; + a_state_t state_a_d; + b_state_t state_b_q; + b_state_t state_b_d; + + always_comb begin + state_a_d = state_a_q; + state_b_d = state_b_q; + if (state_a_q == A0) state_a_d = A1; + else if (state_a_q == A1) state_a_d = A0; + if (state_b_q == B0) state_b_d = B1; + else if (state_b_q == B1) state_b_d = B0; + end + + always_ff @(posedge clk) begin + state_a_q <= state_a_d; + end + + always_ff @(posedge clk) begin + state_b_q <= state_b_d; + end +endmodule + +module split_always_if_warn ( + input logic clk +); + /* verilator lint_off MULTIDRIVEN */ + typedef enum logic [1:0] { + S0, + S1 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = S1; + else if (state_q == S1) state_d = S0; + end + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = S1; + else if (state_q == S1) state_d = S0; + end + + always_ff @(posedge clk) begin + state_q <= state_d; + end + /* verilator lint_on MULTIDRIVEN */ +endmodule + +module same_always_same_if_warn ( + input logic clk +); + typedef enum logic [1:0] { + S0, + S1 + } state_t; + + state_t state_q; + state_t state_d; + + always_comb begin + state_d = state_q; + if (state_q == S0) state_d = S1; + else if (state_q == S1) state_d = S0; + if (state_q == S1) state_d = S0; + else if (state_q == S0) state_d = S1; + end + + always_ff @(posedge clk) begin + state_q <= state_d; + end +endmodule + module t ( input logic clk ); same_always_warn same_u (.clk(clk)); split_always_warn split_u (.clk(clk)); + same_always_if_warn same_if_u (.clk(clk)); + split_always_if_warn split_if_u (.clk(clk)); + same_always_same_if_warn same_same_if_u (.clk(clk)); endmodule diff --git a/test_regress/t/t_fsmmulti_same_bad.out b/test_regress/t/t_fsmmulti_same_bad.out index 1efc9fc77..85387cd8c 100644 --- a/test_regress/t/t_fsmmulti_same_bad.out +++ b/test_regress/t/t_fsmmulti_same_bad.out @@ -1,9 +1,15 @@ -%Warning-COVERIGN: t/t_fsmmulti_same_bad.v:33:5: Ignoring unsupported: FSM coverage on multiple supported case statements found in the same always block. Only the first candidate will be instrumented. - 33 | case (state) +%Warning-COVERIGN: t/t_fsmmulti_same_bad.v:34:5: Ignoring unsupported: FSM coverage on multiple supported case statements found in the same always block. Only the first candidate will be instrumented. + 34 | case (state) | ^~~~ - t/t_fsmmulti_same_bad.v:28:7: ... Location of first supported candidate for 't.state' - 28 | case (state) + t/t_fsmmulti_same_bad.v:29:7: ... Location of first supported candidate for 't.state' + 29 | case (state) | ^~~~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_fsmmulti_same_bad.v:44:5: Ignoring unsupported: FSM coverage on multiple supported transition candidates found in the same always block. Only the first candidate will be instrumented. + 44 | if (state_if == S1) state_if <= S2; + | ^~ + t/t_fsmmulti_same_bad.v:41:5: ... Location of first supported candidate for 't.state_if' + 41 | if (state_if == S0) state_if <= S1; + | ^~ %Error: Exiting due to diff --git a/test_regress/t/t_fsmmulti_same_bad.v b/test_regress/t/t_fsmmulti_same_bad.v index d4f8d3438..d9030d5d7 100644 --- a/test_regress/t/t_fsmmulti_same_bad.v +++ b/test_regress/t/t_fsmmulti_same_bad.v @@ -16,6 +16,7 @@ module t ( } state_t; state_t state; + state_t state_if; // This is intentionally non-idiomatic RTL. The detector sees one supported // candidate in the reset-if else branch and a second supported top-level @@ -36,4 +37,12 @@ module t ( endcase end + always_ff @(posedge clk) begin + if (state_if == S0) state_if <= S1; + else if (state_if == S1) state_if <= S0; + + if (state_if == S1) state_if <= S2; + else if (state_if == S2) state_if <= S1; + end + endmodule diff --git a/test_regress/t/t_fsmmulti_warn_bad.out b/test_regress/t/t_fsmmulti_warn_bad.out index 82d64e44b..69bfb07fe 100644 --- a/test_regress/t/t_fsmmulti_warn_bad.out +++ b/test_regress/t/t_fsmmulti_warn_bad.out @@ -1,9 +1,15 @@ -%Warning-FSMMULTI: t/t_fsmmulti_warn_bad.v:29:5: FSM coverage: multiple enum-typed case statements found in the same always block. Only the first candidate will be instrumented. - 29 | case (state_b) +%Warning-FSMMULTI: t/t_fsmmulti_warn_bad.v:41:5: FSM coverage: multiple enum-typed case statements found in the same always block. Only the first candidate will be instrumented. + 41 | case (state_b) | ^~~~ - t/t_fsmmulti_warn_bad.v:25:5: ... Location of first supported candidate for 't.state_a' - 25 | case (state_a) + t/t_fsmmulti_warn_bad.v:37:5: ... Location of first supported candidate for 't.state_a' + 37 | case (state_a) | ^~~~ ... For warning description see https://verilator.org/warn/FSMMULTI?v=latest ... Use "/* verilator lint_off FSMMULTI */" and lint_on around source to disable this message. +%Warning-FSMMULTI: t/t_fsmmulti_warn_bad.v:51:5: FSM coverage: multiple enum-typed transition candidates found in the same always block. Only the first candidate will be instrumented. + 51 | if (state_d == D0) state_d <= D1; + | ^~ + t/t_fsmmulti_warn_bad.v:48:5: ... Location of first supported candidate for 't.state_c' + 48 | if (state_c == C0) state_c <= C1; + | ^~ %Error: Exiting due to diff --git a/test_regress/t/t_fsmmulti_warn_bad.v b/test_regress/t/t_fsmmulti_warn_bad.v index 8a4f549c1..dd9e7d313 100644 --- a/test_regress/t/t_fsmmulti_warn_bad.v +++ b/test_regress/t/t_fsmmulti_warn_bad.v @@ -18,8 +18,20 @@ module t ( B1 } b_state_t; + typedef enum logic [1:0] { + C0, + C1 + } c_state_t; + + typedef enum logic [1:0] { + D0, + D1 + } d_state_t; + a_state_t state_a; b_state_t state_b; + c_state_t state_c; + d_state_t state_d; always_ff @(posedge clk) begin case (state_a) @@ -32,4 +44,12 @@ module t ( endcase end + always_ff @(posedge clk) begin + if (state_c == C0) state_c <= C1; + else if (state_c == C1) state_c <= C0; + + if (state_d == D0) state_d <= D1; + else if (state_d == D1) state_d <= D0; + end + endmodule