Improve FSM coverage detection (#7490)
This commit is contained in:
parent
21020ea2d1
commit
7f01806e15
|
|
@ -34,6 +34,7 @@
|
|||
#include <map>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
|
|
@ -56,6 +57,69 @@ struct FsmResetCondDesc final {
|
|||
AstVarScope* varScopep = nullptr;
|
||||
};
|
||||
|
||||
class FsmResetArcDesc final {
|
||||
int 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)
|
||||
: m_toValue{toValue}
|
||||
, m_nodep{nodep} {}
|
||||
|
||||
int toValue() const { return m_toValue; }
|
||||
AstNode* nodep() const { return m_nodep; }
|
||||
};
|
||||
|
||||
class FsmRegisterCandidate final {
|
||||
AstScope* m_scopep = nullptr; // Owning scope for the paired FSM.
|
||||
AstAlways* m_alwaysp = nullptr; // Register process that commits the state.
|
||||
AstVarScope* m_stateVscp = nullptr; // Registered FSM state variable.
|
||||
AstVarScope* m_nextVscp = nullptr; // Next-state variable or same state var for 1-block FSMs.
|
||||
std::vector<FsmSenDesc> m_senses; // Event controls for recreated coverage blocks.
|
||||
FsmResetCondDesc m_resetCond; // Saved reset predicate, if any.
|
||||
std::vector<FsmResetArcDesc> m_resetArcs; // Reset target arcs recovered during detect.
|
||||
bool m_hasResetCond = false; // Whether the FSM had a modeled reset predicate.
|
||||
bool m_resetInclude = false; // Whether reset arcs count toward summary totals.
|
||||
bool m_inclCond = false; // Whether conditional/default arcs are kept explicitly.
|
||||
|
||||
public:
|
||||
AstScope* scopep() const { return m_scopep; }
|
||||
void scopep(AstScope* scopep) { m_scopep = scopep; }
|
||||
AstAlways* alwaysp() const { return m_alwaysp; }
|
||||
void alwaysp(AstAlways* alwaysp) { m_alwaysp = alwaysp; }
|
||||
AstVarScope* stateVscp() const { return m_stateVscp; }
|
||||
void stateVscp(AstVarScope* vscp) { m_stateVscp = vscp; }
|
||||
AstVarScope* nextVscp() const { return m_nextVscp; }
|
||||
void nextVscp(AstVarScope* vscp) { m_nextVscp = vscp; }
|
||||
const std::vector<FsmSenDesc>& senses() const { return m_senses; }
|
||||
std::vector<FsmSenDesc>& senses() { return m_senses; }
|
||||
const FsmResetCondDesc& resetCond() const { return m_resetCond; }
|
||||
FsmResetCondDesc& resetCond() { return m_resetCond; }
|
||||
const std::vector<FsmResetArcDesc>& resetArcs() const { return m_resetArcs; }
|
||||
std::vector<FsmResetArcDesc>& resetArcs() { return m_resetArcs; }
|
||||
bool hasResetCond() const { return m_hasResetCond; }
|
||||
void hasResetCond(bool flag) { m_hasResetCond = flag; }
|
||||
bool resetInclude() const { return m_resetInclude; }
|
||||
void resetInclude(bool flag) { m_resetInclude = flag; }
|
||||
bool inclCond() const { return m_inclCond; }
|
||||
void inclCond(bool flag) { m_inclCond = flag; }
|
||||
};
|
||||
|
||||
class FsmComboAlways final {
|
||||
AstScope* const m_scopep = nullptr; // Owning scope for the combinational process.
|
||||
AstAlways* const m_alwaysp = nullptr; // Candidate transition process.
|
||||
|
||||
public:
|
||||
FsmComboAlways() = default;
|
||||
FsmComboAlways(AstScope* scopep, AstAlways* alwaysp)
|
||||
: m_scopep{scopep}
|
||||
, m_alwaysp{alwaysp} {}
|
||||
|
||||
AstScope* scopep() const { return m_scopep; }
|
||||
AstAlways* alwaysp() const { return m_alwaysp; }
|
||||
};
|
||||
|
||||
class FsmGraph;
|
||||
|
||||
class FsmVertex VL_NOT_FINAL : public V3GraphVertex {
|
||||
|
|
@ -152,7 +216,7 @@ public:
|
|||
// context needed to lower states/arcs back into the AST after detection.
|
||||
class FsmGraph final : public V3Graph {
|
||||
AstScope* m_scopep = nullptr; // Owning scoped block for the detected FSM.
|
||||
AstAlways* m_alwaysp = nullptr; // Original always block being instrumented.
|
||||
AstAlways* m_stateAlwaysp = nullptr; // Register always block being instrumented.
|
||||
string m_stateVarName; // Pretty state variable name for user-visible output.
|
||||
string m_stateVarInternalName; // Internal state symbol name for dump tags.
|
||||
AstVarScope* m_stateVarScopep = nullptr; // Scoped state variable being tracked.
|
||||
|
|
@ -173,8 +237,8 @@ public:
|
|||
|
||||
AstScope* scopep() const { return m_scopep; }
|
||||
void scopep(AstScope* scopep) { m_scopep = scopep; }
|
||||
AstAlways* alwaysp() const { return m_alwaysp; }
|
||||
void alwaysp(AstAlways* alwaysp) { m_alwaysp = alwaysp; }
|
||||
AstAlways* stateAlwaysp() const { return m_stateAlwaysp; }
|
||||
void stateAlwaysp(AstAlways* alwaysp) { m_stateAlwaysp = alwaysp; }
|
||||
const string& stateVarName() const { return m_stateVarName; }
|
||||
void stateVarName(const string& name) { m_stateVarName = name; }
|
||||
const string& stateVarInternalName() const { return m_stateVarInternalName; }
|
||||
|
|
@ -232,13 +296,18 @@ public:
|
|||
struct DetectedFsm final {
|
||||
std::unique_ptr<FsmGraph> graphp; // Extracted graph for one detected FSM candidate.
|
||||
};
|
||||
using DetectedFsmMap = std::map<string, DetectedFsm>;
|
||||
using DetectedFsmMap = std::map<const AstVarScope*, DetectedFsm>;
|
||||
|
||||
struct FsmCaseCandidate final {
|
||||
AstNode* warnNodep = nullptr; // Transition node that made the candidate supported.
|
||||
AstVarScope* stateVscp = nullptr; // FSM state variable associated with that candidate.
|
||||
};
|
||||
|
||||
// Local shared state between the two adjacent FSM coverage phases. Detection
|
||||
// fills this with recovered FSM graphs; lowering consumes the completed graphs
|
||||
// immediately afterward without needing any AST serialization bridge.
|
||||
class FsmState final {
|
||||
// All detected FSMs keyed by state varscope name. This is the only bridge
|
||||
// All detected FSMs keyed by state varscope identity. This is the only bridge
|
||||
// between the adjacent detect and lower phases, so the second phase never
|
||||
// needs to rediscover or serialize the extracted machine.
|
||||
DetectedFsmMap m_fsms;
|
||||
|
|
@ -258,6 +327,10 @@ class FsmDetectVisitor final : public VNVisitor {
|
|||
// STATE - for current visit position (use VL_RESTORER)
|
||||
FsmState& m_state;
|
||||
AstScope* m_scopep = nullptr;
|
||||
std::unordered_map<const AstVarScope*, FsmRegisterCandidate> m_registerCandidates;
|
||||
std::vector<FsmComboAlways> m_comboAlwayss;
|
||||
std::vector<FsmComboAlways> m_nonComboAlwayss;
|
||||
std::unordered_map<const AstVarScope*, FsmCaseCandidate> m_comboPaired;
|
||||
|
||||
// METHODS
|
||||
// Enum-backed FSMs may be wrapped in refs/typedefs; normalize to the
|
||||
|
|
@ -266,6 +339,142 @@ class FsmDetectVisitor final : public VNVisitor {
|
|||
return dtypep->skipRefToEnump();
|
||||
}
|
||||
|
||||
static string candidateConflictContext(AstNode* laterNodep,
|
||||
const FsmCaseCandidate& firstCand) {
|
||||
return '\n' + laterNodep->warnContextPrimary() + firstCand.warnNodep->warnOther()
|
||||
+ "... Location of first supported candidate for "
|
||||
+ firstCand.stateVscp->prettyNameQ() + '\n'
|
||||
+ firstCand.warnNodep->warnContextSecondary();
|
||||
}
|
||||
|
||||
class RegisterAlwaysAnalyzer final {
|
||||
AstScope* const m_scopep;
|
||||
|
||||
public:
|
||||
explicit RegisterAlwaysAnalyzer(AstScope* scopep)
|
||||
: m_scopep{scopep} {}
|
||||
|
||||
std::vector<std::pair<AstCase*, AstNodeExpr*>> oneBlockCandidates(AstAlways* alwaysp) const {
|
||||
std::vector<std::pair<AstCase*, AstNodeExpr*>> candidates;
|
||||
AstNode* const stmtsp = alwaysp->stmtsp();
|
||||
AstIf* const firstIfp = VN_CAST(stmtsp, If);
|
||||
if (firstIfp) {
|
||||
if (AstCase* const casep = VN_CAST(firstIfp->elsesp(), Case)) {
|
||||
candidates.emplace_back(
|
||||
casep, FsmDetectVisitor::isSimpleResetCond(firstIfp->condp())
|
||||
? firstIfp->condp()
|
||||
: nullptr);
|
||||
}
|
||||
}
|
||||
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
|
||||
if (AstCase* const casep = VN_CAST(nodep, Case))
|
||||
candidates.emplace_back(casep, nullptr);
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
bool matchRegisterCandidate(AstAlways* alwaysp, FsmRegisterCandidate& cand) const {
|
||||
return FsmDetectVisitor::matchRegisterAlways(alwaysp, m_scopep, cand);
|
||||
}
|
||||
|
||||
void buildOneBlockCandidate(AstAlways* alwaysp, AstVarScope* vscp,
|
||||
AstNodeExpr* resetCondp, FsmRegisterCandidate& reg) const {
|
||||
reg.scopep(m_scopep);
|
||||
reg.alwaysp(alwaysp);
|
||||
reg.stateVscp(vscp);
|
||||
reg.nextVscp(vscp);
|
||||
reg.senses() = FsmDetectVisitor::describeSenTree(alwaysp->sentreep());
|
||||
reg.resetCond() = FsmDetectVisitor::describeResetCond(resetCondp);
|
||||
reg.hasResetCond(reg.resetCond().varScopep != nullptr);
|
||||
reg.resetInclude(vscp->varp()->attrFsmResetArc());
|
||||
reg.inclCond(vscp->varp()->attrFsmArcInclCond());
|
||||
AstIf* const firstIfp = VN_CAST(alwaysp->stmtsp(), If);
|
||||
if (firstIfp && reg.hasResetCond()) {
|
||||
AstVarScope* resetStateVscp = nullptr;
|
||||
const ResetAssignStatus resetStatus = FsmDetectVisitor::collectConstStateAssigns(
|
||||
firstIfp->thensp(), resetStateVscp, reg.resetArcs());
|
||||
if (resetStatus == ResetAssignStatus::NONE || resetStateVscp != vscp) {
|
||||
reg.resetArcs().clear();
|
||||
int resetValue = 0;
|
||||
AstNode* const thenNodep
|
||||
= FsmDetectVisitor::singleMeaningfulBranch(firstIfp->thensp());
|
||||
UASSERT_OBJ(thenNodep, firstIfp,
|
||||
"one-block reset fallback requires a non-empty reset branch");
|
||||
if (FsmDetectVisitor::directConstStateAssignNode(thenNodep, resetStateVscp,
|
||||
resetValue)
|
||||
&& resetStateVscp == vscp) {
|
||||
reg.resetArcs().emplace_back(resetValue, firstIfp->thensp());
|
||||
}
|
||||
} else if (resetStatus == ResetAssignStatus::MULTI_SAME_STATE) {
|
||||
reg.resetArcs().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ComboAlwaysAnalyzer final {
|
||||
public:
|
||||
struct ComboMatch final {
|
||||
const FsmRegisterCandidate* matchedp = nullptr;
|
||||
AstNode* warnNodep = nullptr;
|
||||
};
|
||||
|
||||
private:
|
||||
const std::unordered_map<const AstVarScope*, FsmRegisterCandidate>& m_registerCandidates;
|
||||
|
||||
public:
|
||||
explicit ComboAlwaysAnalyzer(
|
||||
const std::unordered_map<const AstVarScope*, FsmRegisterCandidate>& registerCandidates)
|
||||
: m_registerCandidates{registerCandidates} {}
|
||||
|
||||
ComboMatch matchCase(AstNode* stmtsp, AstCase* casep) const {
|
||||
ComboMatch match;
|
||||
AstVarRef* const selp = VN_CAST(casep->exprp(), VarRef);
|
||||
if (!selp) return match;
|
||||
for (const auto& it : m_registerCandidates) {
|
||||
const FsmRegisterCandidate& reg = it.second;
|
||||
if (selp->varScopep() == reg.nextVscp()) {
|
||||
if (!FsmDetectVisitor::hasCanonicalNextStateDefaultBeforeCase(
|
||||
stmtsp, casep, reg.stateVscp(), reg.nextVscp())) {
|
||||
continue;
|
||||
}
|
||||
} else if (selp->varScopep() != reg.stateVscp()) {
|
||||
continue;
|
||||
}
|
||||
AstNode* const warnNodep
|
||||
= FsmDetectVisitor::caseSupportedTransitionNode(casep, reg.nextVscp(),
|
||||
reg.inclCond());
|
||||
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;
|
||||
|
||||
const auto isRecognizedFsm = [&](const auto& entry) -> bool {
|
||||
const FsmRegisterCandidate& reg = entry.second;
|
||||
const bool matchesNext = selp->varScopep() == reg.nextVscp();
|
||||
const bool matchesState = selp->varScopep() == reg.stateVscp();
|
||||
|
||||
if (!matchesNext && !matchesState) return false;
|
||||
if (matchesNext
|
||||
&& !FsmDetectVisitor::hasCanonicalNextStateDefaultBeforeCase(
|
||||
stmtsp, casep, reg.stateVscp(), reg.nextVscp())) {
|
||||
return false;
|
||||
}
|
||||
return FsmDetectVisitor::caseSupportedTransitionNode(casep, reg.nextVscp(),
|
||||
reg.inclCond());
|
||||
};
|
||||
|
||||
return std::any_of(m_registerCandidates.begin(), m_registerCandidates.end(),
|
||||
isRecognizedFsm);
|
||||
}
|
||||
};
|
||||
|
||||
// Reset arcs are only modeled for the simple signal form that survives to
|
||||
// this pass after earlier normalization.
|
||||
static bool isSimpleResetCond(AstNodeExpr* condp) { return VN_IS(condp, VarRef); }
|
||||
|
|
@ -303,10 +512,15 @@ class FsmDetectVisitor final : public VNVisitor {
|
|||
// rather than other instrumentation already attached to the block.
|
||||
static bool isIgnorableStmt(AstNode* nodep) { return VN_IS(nodep, CoverInc); }
|
||||
|
||||
// Conservative extractor: only treat a branch as simple when exactly one
|
||||
// non-coverage statement remains after unwrapping. Richer multi-statement
|
||||
// or control-flow forms are intentionally left for follow-on FSM-detection
|
||||
// work instead of being partially inferred here.
|
||||
static AstNode* skipLeadingIgnorableStmt(AstNode* nodep) {
|
||||
while (nodep && isIgnorableStmt(nodep)) nodep = nodep->nextp();
|
||||
return nodep;
|
||||
}
|
||||
|
||||
// Conservative extractor for statement lists: only treat a list as simple
|
||||
// when exactly one non-coverage statement remains after unwrapping.
|
||||
// Richer multi-statement or control-flow forms are intentionally left for
|
||||
// follow-on FSM-detection work instead of being partially inferred here.
|
||||
static AstNode* singleMeaningfulStmt(AstNode* stmtp) {
|
||||
AstNode* resultp = nullptr;
|
||||
for (AstNode* nodep = stmtp; nodep; nodep = nodep->nextp()) {
|
||||
|
|
@ -317,12 +531,49 @@ class FsmDetectVisitor final : public VNVisitor {
|
|||
return resultp;
|
||||
}
|
||||
|
||||
// Recognize the direct "state <= X" form that gives us an unambiguous arc
|
||||
// target without needing deeper control-flow reasoning. Branches that fall
|
||||
// out here represent currently unsupported next-state shapes rather than
|
||||
// bugs in the implemented subset.
|
||||
// If/else branches are a single subtree, not a statement list, so do not
|
||||
// walk nextp() here or we may accidentally consume the sibling else-arm.
|
||||
static AstNode* singleMeaningfulBranch(AstNode* branchp) {
|
||||
if (!branchp) return nullptr;
|
||||
return branchp;
|
||||
}
|
||||
|
||||
// By fsm-detect time, non-clocked always @* blocks are already admitted through
|
||||
// a missing sentree. This helper therefore only needs to recognize
|
||||
// explicit changed-sensitivity lists such as always @(a or b); clocked and
|
||||
// event-driven forms remain out of scope.
|
||||
static bool isPlainComboSentree(const AstSenTree* sentreep) {
|
||||
UASSERT(sentreep, "plain combo sensitivity check requires a sensitivity tree");
|
||||
for (const AstSenItem* senp = sentreep->sensesp(); senp;
|
||||
senp = VN_AS(senp->nextp(), SenItem)) {
|
||||
if (senp->edgeType() == VEdgeType::ET_CHANGED) continue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void warnUnsupportedComboAlways(const FsmComboAlways& combo) {
|
||||
const ComboAlwaysAnalyzer analyzer{m_registerCandidates};
|
||||
AstNode* const stmtsp = skipLeadingIgnorableStmt(combo.alwaysp()->stmtsp());
|
||||
bool warned = false;
|
||||
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
|
||||
AstCase* const casep = VN_CAST(nodep, Case);
|
||||
if (!casep) continue;
|
||||
if (analyzer.shouldWarnUnsupported(stmtsp, casep)) {
|
||||
casep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-clocked always "
|
||||
"blocks requires a combinational sensitivity list or "
|
||||
"always_comb");
|
||||
warned = true;
|
||||
}
|
||||
if (warned) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Case-item bodies are single subtrees like if/else arms, not statement
|
||||
// lists, so unwrap only local begin/end wrappers here rather than walking
|
||||
// sibling case items via nextp().
|
||||
static AstNodeAssign* directStateAssign(AstNode* stmtp, AstVarScope* stateVscp) {
|
||||
AstNode* const nodep = singleMeaningfulStmt(stmtp);
|
||||
AstNode* const nodep = singleMeaningfulBranch(stmtp);
|
||||
if (!nodep) return nullptr;
|
||||
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
|
||||
if (!assp) return nullptr;
|
||||
|
|
@ -331,6 +582,177 @@ class FsmDetectVisitor final : public VNVisitor {
|
|||
return assp;
|
||||
}
|
||||
|
||||
static AstNodeAssign* nodeStateVarAssign(AstNode* nodep, AstVarScope*& stateVscp,
|
||||
AstVarScope*& fromVscp) {
|
||||
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
|
||||
if (!assp) return nullptr;
|
||||
AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef);
|
||||
UASSERT_OBJ(lhsp, assp, "register commit lhs should be normalized to a VarRef");
|
||||
AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef);
|
||||
if (!rhsp) return nullptr;
|
||||
stateVscp = lhsp->varScopep();
|
||||
fromVscp = rhsp->varScopep();
|
||||
return assp;
|
||||
}
|
||||
|
||||
static AstNodeAssign* directCondStateVarAssign(AstNode* nodep, AstVarScope*& stateVscp,
|
||||
AstVarScope*& fromVscp, AstNodeExpr*& condp,
|
||||
int& resetValue) {
|
||||
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
|
||||
if (!assp) return nullptr;
|
||||
AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef);
|
||||
UASSERT_OBJ(lhsp, assp,
|
||||
"conditional register commit lhs should be normalized to a VarRef");
|
||||
AstCond* const rhsp = VN_CAST(assp->rhsp(), Cond);
|
||||
if (!rhsp) return nullptr;
|
||||
AstVarRef* const elsep = VN_CAST(rhsp->elsep(), VarRef);
|
||||
if (!elsep || !exprConstValue(rhsp->thenp(), resetValue)) return nullptr;
|
||||
stateVscp = lhsp->varScopep();
|
||||
fromVscp = elsep->varScopep();
|
||||
condp = rhsp->condp();
|
||||
return assp;
|
||||
}
|
||||
|
||||
static AstNodeAssign* directConstStateAssignNode(AstNode* nodep, AstVarScope*& stateVscp,
|
||||
int& value) {
|
||||
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
|
||||
if (!assp) return nullptr;
|
||||
AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef);
|
||||
UASSERT_OBJ(lhsp, assp,
|
||||
"direct constant state assignment lhs should be normalized to a VarRef");
|
||||
if (!exprConstValue(assp->rhsp(), value)) return nullptr;
|
||||
stateVscp = lhsp->varScopep();
|
||||
return assp;
|
||||
}
|
||||
|
||||
enum class ResetAssignStatus : uint8_t {
|
||||
NONE, // Reset branch was not the supported direct-constant shape.
|
||||
SINGLE, // Exactly one supported reset assignment was collected.
|
||||
MULTI_SAME_STATE // Multiple assignments to the same FSM state var; warn and ignore.
|
||||
};
|
||||
|
||||
// Reset arcs are only extracted from the single direct-constant form. If
|
||||
// user RTL assigns the same state register multiple times in the reset
|
||||
// branch, warn and skip reset-arc modeling rather than inventing multiple
|
||||
// reset transitions for an odd but legal coding style.
|
||||
static ResetAssignStatus collectConstStateAssigns(AstNode* stmtp, AstVarScope*& stateVscp,
|
||||
std::vector<FsmResetArcDesc>& resetArcs) {
|
||||
AstNode* nodep = skipLeadingIgnorableStmt(stmtp);
|
||||
UASSERT_OBJ(nodep, stmtp, "Empty reset branch unexpectedly survived to FSM detection");
|
||||
for (;; nodep = nodep->nextp()) {
|
||||
AstVarScope* assignStateVscp = nullptr;
|
||||
int value = 0;
|
||||
AstNodeAssign* const assp = directConstStateAssignNode(nodep, assignStateVscp, value);
|
||||
if (!assp) return ResetAssignStatus::NONE;
|
||||
if (!stateVscp) stateVscp = assignStateVscp;
|
||||
if (assignStateVscp != stateVscp) return ResetAssignStatus::NONE;
|
||||
if (!resetArcs.empty()) {
|
||||
assp->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on reset branches with "
|
||||
"multiple assignments to the state variable");
|
||||
resetArcs.clear();
|
||||
return ResetAssignStatus::MULTI_SAME_STATE;
|
||||
}
|
||||
resetArcs.emplace_back(value, assp);
|
||||
if (!nodep->nextp()) return ResetAssignStatus::SINGLE;
|
||||
}
|
||||
}
|
||||
|
||||
static bool hasCanonicalNextStateDefaultBeforeCase(AstNode* stmtsp, AstCase* casep,
|
||||
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;
|
||||
if (AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign)) {
|
||||
AstVarRef* const lhsp = VN_CAST(assp->lhsp(), VarRef);
|
||||
AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef);
|
||||
if (!lhsp || lhsp->varScopep() != nextVscp) continue;
|
||||
if (sawCanonicalDefault) {
|
||||
const string nextName = nextVscp->varp()->prettyNameQ();
|
||||
const string stateName = stateVscp->varp()->prettyNameQ();
|
||||
assp->v3warn(COVERIGN,
|
||||
"Ignoring unsupported: FSM coverage on case(" + nextName
|
||||
+ ") when the canonical " + nextName + " = " + stateName
|
||||
+ " default is overwritten before the case statement");
|
||||
return false;
|
||||
}
|
||||
if (!rhsp || rhsp->varScopep() != stateVscp) return false;
|
||||
sawCanonicalDefault = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ifStateConstAssign(AstNode* stmtp, AstVarScope* stateVscp, int& thenValue,
|
||||
int& elseValue) {
|
||||
AstIf* const ifp = VN_CAST(singleMeaningfulBranch(stmtp), If);
|
||||
if (!ifp || !ifp->elsesp()) return false;
|
||||
AstVarScope* thenVscp = nullptr;
|
||||
AstVarScope* elseVscp = nullptr;
|
||||
AstNode* const thenNodep = singleMeaningfulBranch(skipLeadingIgnorableStmt(ifp->thensp()));
|
||||
UASSERT_OBJ(thenNodep, ifp, "Empty then-branch unexpectedly survived to FSM detection");
|
||||
AstNode* const elseNodep = singleMeaningfulBranch(skipLeadingIgnorableStmt(ifp->elsesp()));
|
||||
if (!elseNodep) return false;
|
||||
if (!directConstStateAssignNode(thenNodep, thenVscp, thenValue)) return false;
|
||||
if (!directConstStateAssignNode(elseNodep, elseVscp, elseValue)) return false;
|
||||
if (thenVscp == stateVscp && elseVscp == stateVscp) return true;
|
||||
if (thenVscp != elseVscp) return false;
|
||||
AstNode* const followp = skipLeadingIgnorableStmt(ifp->nextp());
|
||||
AstVarScope* finalStateVscp = nullptr;
|
||||
AstVarScope* finalFromVscp = nullptr;
|
||||
AstNode* const finalNodep = singleMeaningfulBranch(followp);
|
||||
if (!finalNodep) return false;
|
||||
if (!nodeStateVarAssign(finalNodep, finalStateVscp, finalFromVscp)) return false;
|
||||
if (finalStateVscp != stateVscp) return false;
|
||||
if (finalFromVscp != thenVscp) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool directStateCondConstAssign(AstNode* stmtp, AstVarScope* stateVscp, int& thenValue,
|
||||
int& elseValue) {
|
||||
AstNodeAssign* const assp = directStateAssign(stmtp, stateVscp);
|
||||
if (!assp) return false;
|
||||
AstCond* const condp = VN_CAST(assp->rhsp(), Cond);
|
||||
if (!condp) return false;
|
||||
return exprConstValue(condp->thenp(), thenValue)
|
||||
&& exprConstValue(condp->elsep(), elseValue);
|
||||
}
|
||||
|
||||
static AstNode* caseItemSupportedArcNode(AstCaseItem* itemp, AstVarScope* stateVscp,
|
||||
bool inclCond) {
|
||||
if (itemp->isDefault()) {
|
||||
if (!inclCond) return nullptr;
|
||||
}
|
||||
AstNodeAssign* const assp = directStateAssign(itemp->stmtsp(), stateVscp);
|
||||
if (assp) {
|
||||
int toValue = 0;
|
||||
if (exprConstValue(assp->rhsp(), toValue)) return assp;
|
||||
}
|
||||
int thenValue = 0;
|
||||
int elseValue = 0;
|
||||
if (directStateCondConstAssign(itemp->stmtsp(), stateVscp, thenValue, elseValue)) {
|
||||
return assp;
|
||||
}
|
||||
if (ifStateConstAssign(itemp->stmtsp(), stateVscp, thenValue, elseValue)) {
|
||||
return singleMeaningfulBranch(itemp->stmtsp());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Combinational transition blocks are paired only through supported case
|
||||
// items that assign to the recorded next-state variable.
|
||||
static AstNode* caseSupportedTransitionNode(AstCase* casep, AstVarScope* stateVscp,
|
||||
bool inclCond) {
|
||||
for (AstCaseItem* itemp = casep->itemsp(); itemp;
|
||||
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
||||
if (AstNode* const nodep = caseItemSupportedArcNode(itemp, stateVscp, inclCond))
|
||||
return nodep;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Prefer enum labels in reports; fall back to synthetic labels for forced
|
||||
// non-enum FSMs so coverage points remain human-readable.
|
||||
static string labelForValue(const std::unordered_map<int, string>& labels, int value) {
|
||||
|
|
@ -356,10 +778,105 @@ class FsmDetectVisitor final : public VNVisitor {
|
|||
const std::unordered_map<int, string>& labels, int value) {
|
||||
if (labels.find(value) != labels.end()) return true;
|
||||
nodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum state transitions "
|
||||
"that assign a constant not present in the declared enum");
|
||||
"that assign a constant that is not present in the declared "
|
||||
"enum");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Strict Phase 1 matcher for register processes: either a bare state
|
||||
// commit, or a top-level reset guard whose else path is that commit.
|
||||
static bool matchRegisterAlways(AstAlways* alwaysp, AstScope* scopep,
|
||||
FsmRegisterCandidate& cand) {
|
||||
if (!alwaysp->sentreep() || !alwaysp->sentreep()->hasEdge()) return false;
|
||||
|
||||
AstNode* const stmtsp = skipLeadingIgnorableStmt(alwaysp->stmtsp());
|
||||
AstNode* const nodep = singleMeaningfulStmt(stmtsp);
|
||||
if (!nodep) return false;
|
||||
|
||||
AstVarScope* stateVscp = nullptr;
|
||||
AstVarScope* nextVscp = nullptr;
|
||||
if (AstIf* const ifp = VN_CAST(nodep, If)) {
|
||||
if (!ifp->elsesp() || !isSimpleResetCond(ifp->condp())) return false;
|
||||
AstVarScope* resetStateVscp = nullptr;
|
||||
const ResetAssignStatus resetStatus
|
||||
= collectConstStateAssigns(ifp->thensp(), resetStateVscp, cand.resetArcs());
|
||||
if (resetStatus == ResetAssignStatus::NONE) {
|
||||
cand.resetArcs().clear();
|
||||
int 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))
|
||||
return false;
|
||||
cand.resetArcs().emplace_back(resetValue, ifp->thensp());
|
||||
} else if (resetStatus == ResetAssignStatus::MULTI_SAME_STATE) {
|
||||
cand.resetArcs().clear();
|
||||
}
|
||||
AstNode* const elseNodep = singleMeaningfulBranch(ifp->elsesp());
|
||||
UASSERT_OBJ(elseNodep, ifp, "register reset match requires a non-empty commit branch");
|
||||
if (!nodeStateVarAssign(elseNodep, stateVscp, nextVscp)) return false;
|
||||
if (resetStateVscp != stateVscp) return false;
|
||||
cand.resetCond() = describeResetCond(ifp->condp());
|
||||
cand.hasResetCond(cand.resetCond().varScopep != nullptr);
|
||||
} else {
|
||||
AstNodeExpr* resetCondp = nullptr;
|
||||
int resetValue = 0;
|
||||
if (AstNodeAssign* const assp
|
||||
= directCondStateVarAssign(nodep, stateVscp, nextVscp, resetCondp, resetValue)) {
|
||||
cand.resetArcs().emplace_back(resetValue, assp);
|
||||
cand.resetCond() = describeResetCond(resetCondp);
|
||||
cand.hasResetCond(cand.resetCond().varScopep != nullptr);
|
||||
} else if (!nodeStateVarAssign(nodep, stateVscp, nextVscp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
cand.scopep(scopep);
|
||||
cand.alwaysp(alwaysp);
|
||||
cand.stateVscp(stateVscp);
|
||||
cand.nextVscp(nextVscp);
|
||||
cand.senses() = describeSenTree(alwaysp->sentreep());
|
||||
cand.resetInclude(stateVscp->varp()->attrFsmResetArc());
|
||||
cand.inclCond(stateVscp->varp()->attrFsmArcInclCond());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Build the Phase 1 state space from the tracked registered state
|
||||
// variable, not from whichever signal the transition case happened to use.
|
||||
static bool collectStateLabels(AstNode* nodep, AstVarScope* stateVscp,
|
||||
std::vector<std::pair<string, int>>& states,
|
||||
std::unordered_map<int, string>& labels) {
|
||||
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();
|
||||
if (!enump && !forced) return false;
|
||||
|
||||
if (enump) {
|
||||
if (stateVscp->width() > 32) {
|
||||
nodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum-typed state "
|
||||
"variables wider than 32 bits");
|
||||
return false;
|
||||
}
|
||||
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();
|
||||
states.emplace_back(itemp->name(), value);
|
||||
labels.emplace(value, itemp->name());
|
||||
}
|
||||
return states.size() >= 2;
|
||||
}
|
||||
|
||||
const int width = stateVarp->width();
|
||||
if (width >= 31) return false;
|
||||
const unsigned stateCount = 1U << width;
|
||||
for (unsigned value = 0; value < stateCount; ++value) {
|
||||
const string label = "S" + cvtToStr(value);
|
||||
states.emplace_back(label, static_cast<int>(value));
|
||||
labels.emplace(static_cast<int>(value), label);
|
||||
}
|
||||
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
|
||||
|
|
@ -390,24 +907,21 @@ class FsmDetectVisitor final : public VNVisitor {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (AstCond* const condp = VN_CAST(assp->rhsp(), Cond)) {
|
||||
int thenValue = 0;
|
||||
int elseValue = 0;
|
||||
const bool simpleCond = exprConstValue(condp->thenp(), thenValue)
|
||||
&& exprConstValue(condp->elsep(), elseValue);
|
||||
if (simpleCond || inclCond) {
|
||||
if (!validateKnownStateValue(condp->thenp(), labels, thenValue)) return true;
|
||||
if (!validateKnownStateValue(condp->elsep(), labels, elseValue)) return true;
|
||||
for (const int branchValue : {thenValue, elseValue}) {
|
||||
for (const std::pair<string, int>& from : froms) {
|
||||
graph.addArc(from.second, branchValue, false, true, itemp->isDefault(),
|
||||
assp->fileline());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
int thenValue = 0;
|
||||
int elseValue = 0;
|
||||
if (directStateCondConstAssign(itemp->stmtsp(), stateVscp, thenValue, elseValue)
|
||||
|| ifStateConstAssign(itemp->stmtsp(), stateVscp, thenValue, elseValue)) {
|
||||
if (!validateKnownStateValue(itemp->stmtsp(), labels, thenValue)) return true;
|
||||
if (!validateKnownStateValue(itemp->stmtsp(), labels, elseValue)) return true;
|
||||
for (const int branchValue : {thenValue, elseValue}) {
|
||||
for (const std::pair<string, int>& from : froms) {
|
||||
graph.addArc(from.second, branchValue, false, true, itemp->isDefault(),
|
||||
itemp->stmtsp()->fileline());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -415,82 +929,45 @@ class FsmDetectVisitor final : public VNVisitor {
|
|||
|
||||
// Reset transitions are described separately because they live in the reset
|
||||
// branch outside the steady-state case statement.
|
||||
static void addResetArcs(FsmGraph& graph, AstNode* stmtsp, AstVarScope* stateVscp,
|
||||
static void addResetArcs(FsmGraph& graph, const std::vector<FsmResetArcDesc>& resetArcs,
|
||||
const std::unordered_map<int, string>& labels) {
|
||||
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
|
||||
if (AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign)) {
|
||||
AstVarRef* const vrefp = VN_CAST(assp->lhsp(), VarRef);
|
||||
int toValue = 0;
|
||||
if (vrefp && vrefp->varScopep() == stateVscp
|
||||
&& exprConstValue(assp->rhsp(), toValue)) {
|
||||
if (!validateKnownStateValue(assp, labels, toValue)) continue;
|
||||
graph.addArc(0, toValue, true, false, false, assp->fileline());
|
||||
}
|
||||
}
|
||||
for (const FsmResetArcDesc& resetArc : resetArcs) {
|
||||
if (!validateKnownStateValue(resetArc.nodep(), labels, resetArc.toValue())) continue;
|
||||
graph.addArc(0, resetArc.toValue(), true, false, false, resetArc.nodep()->fileline());
|
||||
}
|
||||
}
|
||||
|
||||
// Turn one candidate case statement into the graph representation that the
|
||||
// later lowering phase will consume directly, while reviewers can still
|
||||
// inspect the extracted machine via DOT dumps.
|
||||
void processCase(AstCase* casep, AstNodeExpr* resetCondp, AstAlways* alwaysp) {
|
||||
AstVarRef* const selp = VN_CAST(casep->exprp(), VarRef);
|
||||
if (!selp) return;
|
||||
AstVarScope* const stateVscp = selp->varScopep();
|
||||
AstVar* const stateVarp = selp->varp();
|
||||
AstEnumDType* enump = VN_CAST(unwrapEnumCandidate(stateVscp->dtypep()), EnumDType);
|
||||
if (!enump) enump = VN_CAST(unwrapEnumCandidate(stateVarp->dtypep()), EnumDType);
|
||||
const bool forced = stateVarp->attrFsmState();
|
||||
if (!enump && !forced) return;
|
||||
|
||||
void processCase(AstCase* casep, AstVarScope* assignVscp, const FsmRegisterCandidate& reg) {
|
||||
UASSERT_OBJ(assignVscp, casep, "FSM case processing requires a non-null assignment var");
|
||||
AstVarScope* const stateVscp = reg.stateVscp();
|
||||
std::vector<std::pair<string, int>> states;
|
||||
std::unordered_map<int, string> labels;
|
||||
if (enump) {
|
||||
if (stateVscp->width() < 1 || stateVscp->width() > 32) {
|
||||
casep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum-typed state "
|
||||
"variables wider than 32 bits");
|
||||
return;
|
||||
}
|
||||
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();
|
||||
states.emplace_back(itemp->name(), value);
|
||||
labels.emplace(value, itemp->name());
|
||||
}
|
||||
if (states.size() < 2) return;
|
||||
} else {
|
||||
const int width = stateVarp->width();
|
||||
if (width < 1 || width >= 31) return;
|
||||
const unsigned stateCount = 1U << width;
|
||||
for (unsigned value = 0; value < stateCount; ++value) {
|
||||
const string label = "S" + cvtToStr(value);
|
||||
states.emplace_back(label, static_cast<int>(value));
|
||||
labels.emplace(static_cast<int>(value), label);
|
||||
}
|
||||
}
|
||||
|
||||
DetectedFsm& entry = m_state.fsms()[stateVscp->name()];
|
||||
if (!collectStateLabels(casep, stateVscp, states, labels)) return;
|
||||
DetectedFsm& entry = m_state.fsms()[stateVscp];
|
||||
if (!entry.graphp) {
|
||||
entry.graphp.reset(new FsmGraph{});
|
||||
entry.graphp->scopep(m_scopep);
|
||||
entry.graphp->alwaysp(alwaysp);
|
||||
entry.graphp->scopep(reg.scopep());
|
||||
entry.graphp->stateAlwaysp(reg.alwaysp());
|
||||
entry.graphp->stateVarName(stateVscp->prettyName());
|
||||
entry.graphp->stateVarInternalName(stateVarp->name());
|
||||
entry.graphp->stateVarInternalName(stateVscp->varp()->name());
|
||||
entry.graphp->stateVarScopep(stateVscp);
|
||||
entry.graphp->senses() = describeSenTree(alwaysp->sentreep());
|
||||
entry.graphp->resetCond() = describeResetCond(resetCondp);
|
||||
entry.graphp->hasResetCond(entry.graphp->resetCond().varScopep != nullptr);
|
||||
entry.graphp->resetInclude(stateVarp->attrFsmResetArc());
|
||||
entry.graphp->inclCond(stateVarp->attrFsmArcInclCond());
|
||||
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(casep->fileline());
|
||||
for (const std::pair<string, int>& state : states) {
|
||||
entry.graphp->addStateVertex(state.first, state.second);
|
||||
}
|
||||
addResetArcs(*entry.graphp, reg.resetArcs(), labels);
|
||||
}
|
||||
for (AstCaseItem* itemp = casep->itemsp(); itemp;
|
||||
itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
||||
emitCaseItemArcs(*entry.graphp, itemp, stateVscp, labels, entry.graphp->inclCond());
|
||||
emitCaseItemArcs(*entry.graphp, itemp, assignVscp, labels, entry.graphp->inclCond());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -499,57 +976,93 @@ class FsmDetectVisitor final : public VNVisitor {
|
|||
// 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 processAlways(AstAlways* alwaysp) {
|
||||
if (!alwaysp->sentreep() || !alwaysp->sentreep()->hasClocked()) return;
|
||||
std::vector<std::pair<AstCase*, AstNodeExpr*>> candidates;
|
||||
AstNode* stmtsp = alwaysp->stmtsp();
|
||||
AstIf* const firstIfp = VN_CAST(stmtsp, If);
|
||||
if (firstIfp) {
|
||||
if (AstCase* const casep = VN_CAST(firstIfp->elsesp(), Case)) {
|
||||
candidates.emplace_back(
|
||||
casep, isSimpleResetCond(firstIfp->condp()) ? firstIfp->condp() : nullptr);
|
||||
}
|
||||
}
|
||||
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
|
||||
if (AstCase* const casep = VN_CAST(nodep, Case))
|
||||
candidates.emplace_back(casep, nullptr);
|
||||
}
|
||||
void processOneBlockAlways(AstAlways* alwaysp) {
|
||||
const RegisterAlwaysAnalyzer analyzer{m_scopep};
|
||||
if (!alwaysp->sentreep() || !alwaysp->sentreep()->hasEdge()) return;
|
||||
const std::vector<std::pair<AstCase*, AstNodeExpr*>> candidates
|
||||
= analyzer.oneBlockCandidates(alwaysp);
|
||||
if (candidates.empty()) return;
|
||||
|
||||
AstVarScope* firstVscp = nullptr;
|
||||
FsmCaseCandidate firstCand;
|
||||
for (const std::pair<AstCase*, AstNodeExpr*>& cand : candidates) {
|
||||
AstVarRef* const selp = VN_CAST(cand.first->exprp(), VarRef);
|
||||
AstVarScope* const vscp = selp ? selp->varScopep() : nullptr;
|
||||
if (!vscp) continue;
|
||||
if (!firstVscp) {
|
||||
firstVscp = vscp;
|
||||
processCase(cand.first, cand.second, alwaysp);
|
||||
} else if (vscp != firstVscp) {
|
||||
if (!firstCand.stateVscp) {
|
||||
firstCand.warnNodep = cand.first;
|
||||
firstCand.stateVscp = vscp;
|
||||
FsmRegisterCandidate reg;
|
||||
analyzer.buildOneBlockCandidate(alwaysp, vscp, cand.second, reg);
|
||||
processCase(cand.first, vscp, reg);
|
||||
} else if (vscp != firstCand.stateVscp) {
|
||||
cand.first->v3warn(FSMMULTI,
|
||||
"FSM coverage: multiple enum-typed case statements found in "
|
||||
"the same always block. Only the first candidate will be "
|
||||
"instrumented.");
|
||||
"instrumented."
|
||||
<< candidateConflictContext(cand.first, firstCand));
|
||||
} else {
|
||||
cand.first->v3warn(COVERIGN,
|
||||
"Ignoring unsupported: FSM coverage on multiple supported case "
|
||||
"statements found in the same always block. Only the first "
|
||||
"candidate will be instrumented.");
|
||||
"candidate will be instrumented."
|
||||
<< candidateConflictContext(cand.first, firstCand));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(firstIfp && firstVscp)) return;
|
||||
const DetectedFsmMap& fsms = m_state.fsms();
|
||||
const DetectedFsmMap::const_iterator it = fsms.find(firstVscp->name());
|
||||
if (it == fsms.end()) return;
|
||||
FsmGraph* const graphp = it->second.graphp.get();
|
||||
if (!graphp->hasResetCond()) return;
|
||||
std::unordered_map<int, string> labels;
|
||||
for (const V3GraphVertex& vtx : graphp->vertices()) {
|
||||
const FsmVertex* const vertexp = vtx.as<FsmVertex>();
|
||||
if (!vertexp->isState()) continue;
|
||||
labels.emplace(vertexp->value(), vertexp->label());
|
||||
// Phase 1 two-process pairing scans combinational always blocks only after
|
||||
// all strict register candidates have been collected, so source order does
|
||||
// not matter.
|
||||
static void warnComboSameAlways(AstNode* warnNodep, const FsmCaseCandidate& firstCand) {
|
||||
warnNodep->v3warn(FSMMULTI,
|
||||
"FSM coverage: multiple supported transition candidates found in "
|
||||
"the same combinational always block. Only the first candidate "
|
||||
"will be instrumented."
|
||||
<< candidateConflictContext(warnNodep, firstCand));
|
||||
}
|
||||
|
||||
void processComboAlways(const FsmComboAlways& combo) {
|
||||
const ComboAlwaysAnalyzer analyzer{m_registerCandidates};
|
||||
AstNode* const stmtsp = skipLeadingIgnorableStmt(combo.alwaysp()->stmtsp());
|
||||
FsmCaseCandidate firstCand;
|
||||
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
|
||||
AstCase* const casep = VN_CAST(nodep, Case);
|
||||
if (!casep) continue;
|
||||
const ComboAlwaysAnalyzer::ComboMatch match = analyzer.matchCase(stmtsp, casep);
|
||||
const FsmRegisterCandidate* const matchedp = match.matchedp;
|
||||
AstNode* const matchedWarnNodep = match.warnNodep;
|
||||
if (!matchedp) continue;
|
||||
if (!firstCand.stateVscp) {
|
||||
const auto insertPair = m_comboPaired.emplace(
|
||||
matchedp->stateVscp(), FsmCaseCandidate{matchedWarnNodep,
|
||||
const_cast<AstVarScope*>(
|
||||
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<AstVarScope*>(matchedp->stateVscp());
|
||||
processCase(casep, matchedp->nextVscp(), *matchedp);
|
||||
continue;
|
||||
}
|
||||
if (matchedp->stateVscp() != firstCand.stateVscp) {
|
||||
warnComboSameAlways(matchedWarnNodep, firstCand);
|
||||
continue;
|
||||
}
|
||||
matchedWarnNodep->v3warn(COVERIGN,
|
||||
"Ignoring unsupported: FSM coverage on multiple "
|
||||
"supported case statements found in the same "
|
||||
"combinational always block. Only the first "
|
||||
"candidate will be instrumented."
|
||||
<< candidateConflictContext(matchedWarnNodep,
|
||||
firstCand));
|
||||
}
|
||||
addResetArcs(*graphp, firstIfp->thensp(), firstVscp, labels);
|
||||
}
|
||||
|
||||
// Track the current scope so each detected FSM records the module/scope
|
||||
|
|
@ -560,8 +1073,26 @@ class FsmDetectVisitor final : public VNVisitor {
|
|||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
// FSM extraction only cares about clocked always processes.
|
||||
void visit(AstAlways* nodep) override { processAlways(nodep); }
|
||||
// Collect one-block FSMs immediately, strict register candidates for later
|
||||
// pairing, and combinational processes for the second-stage transition
|
||||
// scan.
|
||||
void visit(AstAlways* nodep) override {
|
||||
processOneBlockAlways(nodep);
|
||||
const RegisterAlwaysAnalyzer analyzer{m_scopep};
|
||||
FsmRegisterCandidate reg;
|
||||
if (analyzer.matchRegisterCandidate(nodep, reg)) {
|
||||
m_registerCandidates.emplace(reg.stateVscp(), reg);
|
||||
}
|
||||
if (nodep->keyword() == VAlwaysKwd::ALWAYS_COMB) {
|
||||
m_comboAlwayss.emplace_back(m_scopep, nodep);
|
||||
} else if (nodep->keyword() == VAlwaysKwd::ALWAYS) {
|
||||
if (!nodep->sentreep() || isPlainComboSentree(nodep->sentreep())) {
|
||||
m_comboAlwayss.emplace_back(m_scopep, nodep);
|
||||
} else {
|
||||
m_nonComboAlwayss.emplace_back(m_scopep, nodep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Continue the walk through the rest of the design hierarchy.
|
||||
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
|
@ -573,6 +1104,8 @@ public:
|
|||
FsmDetectVisitor(FsmState& state, AstNetlist* rootp)
|
||||
: m_state{state} {
|
||||
iterate(rootp);
|
||||
for (const FsmComboAlways& combo : m_comboAlwayss) processComboAlways(combo);
|
||||
for (const FsmComboAlways& combo : m_nonComboAlwayss) warnUnsupportedComboAlways(combo);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -621,7 +1154,9 @@ class FsmLowerVisitor final {
|
|||
// used by generated models: declarations, previous-state tracking, and the
|
||||
// pre/post-triggered increment logic for states and arcs.
|
||||
void buildOne(const FsmGraph& graph) {
|
||||
AstAlways* const alwaysp = graph.alwaysp();
|
||||
UINFO(1, "buildOne lowering FSM " << graph.stateVarName()
|
||||
<< " vertices=" << graph.vertices().size() << endl);
|
||||
AstAlways* const alwaysp = graph.stateAlwaysp();
|
||||
AstScope* const scopep = graph.scopep();
|
||||
AstVarScope* const stateVscp = graph.stateVarScopep();
|
||||
FileLine* const flp = graph.fileline();
|
||||
|
|
@ -761,9 +1296,7 @@ public:
|
|||
// still valid in the same pass.
|
||||
explicit FsmLowerVisitor(const FsmState& state)
|
||||
: m_state{state} {
|
||||
for (const std::pair<const string, DetectedFsm>& it : m_state.fsms()) {
|
||||
buildOne(*it.second.graphp);
|
||||
}
|
||||
for (const auto& it : m_state.fsms()) { buildOne(*it.second.graphp); }
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -777,7 +1310,7 @@ void V3FsmDetect::detect(AstNetlist* rootp) {
|
|||
FsmDetectVisitor detect{state, rootp};
|
||||
if (dumpGraphLevel() >= 6) {
|
||||
size_t index = 0;
|
||||
for (const std::pair<const string, DetectedFsm>& it : state.fsms()) {
|
||||
for (const auto& it : state.fsms()) {
|
||||
it.second.graphp->dumpDotFilePrefixed(it.second.graphp->dumpTag(index++));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,16 +17,6 @@ test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
|||
|
||||
test.execute()
|
||||
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
test.obj_dir + "/coverage.dat",
|
||||
],
|
||||
logfile=test.obj_dir + "/summary.log",
|
||||
tee=False,
|
||||
verilator_run=True)
|
||||
|
||||
test.files_identical(test.obj_dir + "/summary.log", "t/" + test.name + "_summary.out")
|
||||
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate",
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
Coverage Summary:
|
||||
line : 0.0% (0/0)
|
||||
toggle : 0.0% (0/0)
|
||||
branch : 0.0% (0/0)
|
||||
expr : 0.0% (0/0)
|
||||
fsm_state : 50.0% (2/4)
|
||||
fsm_arc : 100.0% (5/5)
|
||||
|
|
@ -53,11 +53,20 @@
|
|||
%000002 // [fsm_state t.state::S0]
|
||||
%000000 // [fsm_state t.state::S1] *** UNCOVERED ***
|
||||
%000003 // [fsm_state t.state::S2]
|
||||
S0:
|
||||
if (sel) state <= S1;
|
||||
else state <= S2;
|
||||
S1: state <= S0;
|
||||
default: state <= S0;
|
||||
S0: begin
|
||||
if (sel) begin
|
||||
state <= S1;
|
||||
end
|
||||
else begin
|
||||
state <= S2;
|
||||
end
|
||||
end
|
||||
S1: begin
|
||||
state <= S0;
|
||||
end
|
||||
default: begin
|
||||
state <= S0;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -44,11 +44,20 @@ module t (
|
|||
end
|
||||
else begin
|
||||
case (state)
|
||||
S0:
|
||||
if (sel) state <= S1;
|
||||
else state <= S2;
|
||||
S1: state <= S0;
|
||||
default: state <= S0;
|
||||
S0: begin
|
||||
if (sel) begin
|
||||
state <= S1;
|
||||
end
|
||||
else begin
|
||||
state <= S2;
|
||||
end
|
||||
end
|
||||
S1: begin
|
||||
state <= S0;
|
||||
end
|
||||
default: begin
|
||||
state <= S0;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage keeps grouped canonical case(state_d) forms
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group accepted canonical case(state_d) forms so supported next-state
|
||||
// defaults stay separate from the rejected wrong-RHS and tail-mismatch shapes.
|
||||
|
||||
module fsm_case_next_other_assign_ok (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'b00,
|
||||
S1 = 2'b01,
|
||||
S2 = 2'b10
|
||||
} state_t;
|
||||
|
||||
logic [1:0] aux;
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
initial aux = 2'b00;
|
||||
|
||||
always_comb begin
|
||||
aux[0] = start;
|
||||
state_d = state_q;
|
||||
%000004 case (state_d)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.case_next_other_assign_ok_u.state_q::ANY->S0[reset_include]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.case_next_other_assign_ok_u.state_q::S0->S1]
|
||||
%000004 // [fsm_arc t.case_next_other_assign_ok_u.state_q::S0->S2]
|
||||
%000003 // [fsm_state t.case_next_other_assign_ok_u.state_q::S0]
|
||||
%000000 // [fsm_state t.case_next_other_assign_ok_u.state_q::S1] *** UNCOVERED ***
|
||||
%000004 // [fsm_state t.case_next_other_assign_ok_u.state_q::S2]
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= S0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_case_next_other_lhs_ok (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'b00,
|
||||
S1 = 2'b01,
|
||||
S2 = 2'b10
|
||||
} state_t;
|
||||
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
state_t other_d;
|
||||
|
||||
always_comb begin
|
||||
other_d = state_q;
|
||||
state_d = state_q;
|
||||
%000004 case (state_d)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.case_next_other_lhs_ok_u.state_q::ANY->S0[reset_include]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.case_next_other_lhs_ok_u.state_q::S0->S1]
|
||||
%000004 // [fsm_arc t.case_next_other_lhs_ok_u.state_q::S0->S2]
|
||||
%000003 // [fsm_state t.case_next_other_lhs_ok_u.state_q::S0]
|
||||
%000000 // [fsm_state t.case_next_other_lhs_ok_u.state_q::S1] *** UNCOVERED ***
|
||||
%000004 // [fsm_state t.case_next_other_lhs_ok_u.state_q::S2]
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= S0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
logic rst;
|
||||
logic start;
|
||||
integer cyc;
|
||||
|
||||
fsm_case_next_other_assign_ok case_next_other_assign_ok_u (
|
||||
.clk(clk), .rst(rst), .start(start));
|
||||
fsm_case_next_other_lhs_ok case_next_other_lhs_ok_u (
|
||||
.clk(clk), .rst(rst), .start(start));
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage keeps grouped canonical case(state_d) forms
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate",
|
||||
test.obj_dir + "/annotated",
|
||||
test.obj_dir + "/coverage.dat",
|
||||
],
|
||||
verilator_run=True)
|
||||
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--include-reset-arcs",
|
||||
"--annotate",
|
||||
test.obj_dir + "/annotated-incl",
|
||||
test.obj_dir + "/coverage.dat",
|
||||
],
|
||||
verilator_run=True)
|
||||
|
||||
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
|
||||
test.files_identical(test.obj_dir + "/annotated-incl/" + test.name + ".v",
|
||||
"t/" + test.name + "_incl.out")
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage keeps grouped canonical case(state_d) forms
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group accepted canonical case(state_d) forms so supported next-state
|
||||
// defaults stay separate from the rejected wrong-RHS and tail-mismatch shapes.
|
||||
|
||||
module fsm_case_next_other_assign_ok (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'b00,
|
||||
S1 = 2'b01,
|
||||
S2 = 2'b10
|
||||
} state_t;
|
||||
|
||||
logic [1:0] aux;
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
initial aux = 2'b00;
|
||||
|
||||
always_comb begin
|
||||
aux[0] = start;
|
||||
state_d = state_q;
|
||||
case (state_d)
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= S0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_case_next_other_lhs_ok (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'b00,
|
||||
S1 = 2'b01,
|
||||
S2 = 2'b10
|
||||
} state_t;
|
||||
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
state_t other_d;
|
||||
|
||||
always_comb begin
|
||||
other_d = state_q;
|
||||
state_d = state_q;
|
||||
case (state_d)
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= S0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
logic rst;
|
||||
logic start;
|
||||
integer cyc;
|
||||
|
||||
fsm_case_next_other_assign_ok case_next_other_assign_ok_u (
|
||||
.clk(clk), .rst(rst), .start(start));
|
||||
fsm_case_next_other_lhs_ok case_next_other_lhs_ok_u (
|
||||
.clk(clk), .rst(rst), .start(start));
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage keeps grouped canonical case(state_d) forms
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group accepted canonical case(state_d) forms so supported next-state
|
||||
// defaults stay separate from the rejected wrong-RHS and tail-mismatch shapes.
|
||||
|
||||
module fsm_case_next_other_assign_ok (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'b00,
|
||||
S1 = 2'b01,
|
||||
S2 = 2'b10
|
||||
} state_t;
|
||||
|
||||
logic [1:0] aux;
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
initial aux = 2'b00;
|
||||
|
||||
always_comb begin
|
||||
aux[0] = start;
|
||||
state_d = state_q;
|
||||
%000004 case (state_d)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.case_next_other_assign_ok_u.state_q::ANY->S0[reset_include]]
|
||||
%000000 // [fsm_arc t.case_next_other_assign_ok_u.state_q::S0->S1]
|
||||
%000004 // [fsm_arc t.case_next_other_assign_ok_u.state_q::S0->S2]
|
||||
%000003 // [fsm_state t.case_next_other_assign_ok_u.state_q::S0]
|
||||
%000000 // [fsm_state t.case_next_other_assign_ok_u.state_q::S1] *** UNCOVERED ***
|
||||
%000004 // [fsm_state t.case_next_other_assign_ok_u.state_q::S2]
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= S0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_case_next_other_lhs_ok (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'b00,
|
||||
S1 = 2'b01,
|
||||
S2 = 2'b10
|
||||
} state_t;
|
||||
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
state_t other_d;
|
||||
|
||||
always_comb begin
|
||||
other_d = state_q;
|
||||
state_d = state_q;
|
||||
%000004 case (state_d)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.case_next_other_lhs_ok_u.state_q::ANY->S0[reset_include]]
|
||||
%000000 // [fsm_arc t.case_next_other_lhs_ok_u.state_q::S0->S1]
|
||||
%000004 // [fsm_arc t.case_next_other_lhs_ok_u.state_q::S0->S2]
|
||||
%000003 // [fsm_state t.case_next_other_lhs_ok_u.state_q::S0]
|
||||
%000000 // [fsm_state t.case_next_other_lhs_ok_u.state_q::S1] *** UNCOVERED ***
|
||||
%000004 // [fsm_state t.case_next_other_lhs_ok_u.state_q::S2]
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= S0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
logic rst;
|
||||
logic start;
|
||||
integer cyc;
|
||||
|
||||
fsm_case_next_other_assign_ok case_next_other_assign_ok_u (
|
||||
.clk(clk), .rst(rst), .start(start));
|
||||
fsm_case_next_other_lhs_ok case_next_other_lhs_ok_u (
|
||||
.clk(clk), .rst(rst), .start(start));
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
%Warning-COVERIGN: t/t_cover_fsm_combo_same_warn_bad.v:28:19: Ignoring unsupported: FSM coverage on multiple supported case statements found in the same combinational always block. Only the first candidate will be instrumented.
|
||||
28 | S1: state_d = S2;
|
||||
| ^
|
||||
t/t_cover_fsm_combo_same_warn_bad.v:24:19: ... Location of first supported candidate for 't.state_q'
|
||||
24 | 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
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: same-FSM combo multi-case warning test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
# Two supported case statements in the same combinational always block for the
|
||||
# same FSM are legal RTL, but Phase 1 only instruments the first and warns on
|
||||
# the later duplicate.
|
||||
test.compile(verilator_flags2=["--cc --coverage"],
|
||||
fails=True,
|
||||
expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// DESCRIPTION: Verilator: same-FSM combo multi-case warning test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0,
|
||||
S1,
|
||||
S2
|
||||
} state_t;
|
||||
|
||||
state_t state_q;
|
||||
state_t state_d;
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = S1;
|
||||
default: ;
|
||||
endcase
|
||||
case (state_q)
|
||||
S1: state_d = S2;
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -12,7 +12,7 @@ from pathlib import Path
|
|||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
test.top_filename = "t/t_cover_fsm_styles.v"
|
||||
test.top_filename = "t/t_cover_fsm_policy_accept_multi.v"
|
||||
|
||||
# Dump the lowered AST so AstCoverOtherDecl::dump() sees FSM metadata-bearing
|
||||
# coverage declarations directly. This avoids JSON/schema coupling while still
|
||||
|
|
@ -22,7 +22,7 @@ test.lint(v_flags=["--coverage-fsm", "--dump-tree"])
|
|||
tree_files = [Path(filename) for filename in test.glob_some(test.obj_dir + "/*.tree")]
|
||||
tree_texts = [filename.read_text(encoding="utf8") for filename in tree_files]
|
||||
|
||||
assert any("COVEROTHERDECL" in text and " fv=t.state" in text for text in tree_texts)
|
||||
assert any("COVEROTHERDECL" in text and " fv=t.style_u.state" in text for text in tree_texts)
|
||||
assert any(
|
||||
"COVEROTHERDECL" in text and " ff=ANY" in text and " ft=S0" in text and " fg=reset" in text
|
||||
for text in tree_texts)
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
%Warning-COVERIGN: t/t_cover_fsm_enum_bad.v:29:19: Ignoring unsupported: FSM coverage on enum state transitions that assign a constant not present in the declared enum
|
||||
29 | S0: state <= 2'd3;
|
||||
| ^~
|
||||
... 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
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// DESCRIPTION: Verilator: FSM enum transition rejects unknown constant values
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0,
|
||||
S1
|
||||
} state_t;
|
||||
|
||||
state_t state;
|
||||
|
||||
// FSM coverage should reject a constant next-state value that is not one of
|
||||
// the declared enum items. This keeps graph construction aligned with the
|
||||
// enum-backed state set instead of silently dropping the transition.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end
|
||||
else begin
|
||||
case (state)
|
||||
/* verilator lint_off ENUMVALUE */
|
||||
S0: state <= 2'd3;
|
||||
/* verilator lint_on ENUMVALUE */
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage forced non-enum test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic [1:0] state /*verilator fsm_state*/;
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
rst = 1'b1;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= 2'd0;
|
||||
end
|
||||
else begin
|
||||
%000002 case (state)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000002 // [fsm_arc t.state::S0->S1]
|
||||
%000002 // [fsm_arc t.state::S1->S2]
|
||||
%000001 // [fsm_state t.state::S0]
|
||||
%000002 // [fsm_state t.state::S1]
|
||||
%000002 // [fsm_state t.state::S2]
|
||||
%000000 // [fsm_state t.state::S3] *** UNCOVERED ***
|
||||
2'd0: state <= 2'd1;
|
||||
2'd1: state <= 2'd2;
|
||||
default: state <= 2'd0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage forced non-enum test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic [1:0] state /*verilator fsm_state*/;
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
rst = 1'b1;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= 2'd0;
|
||||
end
|
||||
else begin
|
||||
case (state)
|
||||
2'd0: state <= 2'd1;
|
||||
2'd1: state <= 2'd2;
|
||||
default: state <= 2'd0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vltmt')
|
||||
test.top_filename = "t/t_cover_fsm_styles.v"
|
||||
test.top_filename = "t/t_cover_fsm_policy_accept_multi.v"
|
||||
|
||||
test.compile(v_flags2=["--coverage-fsm", "--dumpi-graph", "6"], threads=2)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:26:19: Ignoring unsupported: FSM coverage on enum state transitions that assign a constant that is not present in the declared enum
|
||||
26 | S0: state_d = sel ? 2'd3 : 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.
|
||||
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:53:19: Ignoring unsupported: FSM coverage on enum state transitions that assign a constant that is not present in the declared enum
|
||||
53 | S0: state_d = sel ? S1 : 2'd3;
|
||||
| ^
|
||||
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:79:19: Ignoring unsupported: FSM coverage on enum state transitions that assign a constant that is not present in the declared enum
|
||||
79 | S0: state_d = 2'd3;
|
||||
| ^
|
||||
%Warning-COVERIGN: t/t_cover_fsm_if_unknown_enum_multi_bad.v:126:15: Ignoring unsupported: FSM coverage on enum state transitions that assign a constant that is not present in the declared enum
|
||||
126 | state_q <= 2'd3;
|
||||
| ^~
|
||||
%Error: Exiting due to
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM enum transition bad-value test
|
||||
# DESCRIPTION: Verilator: FSM direct and conditional transition bad enum tests
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
|
|
@ -11,9 +11,6 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('vlt')
|
||||
|
||||
# When an enum-backed FSM assigns a constant that is not one of the declared
|
||||
# enum items, FSM coverage should warn and skip the unsupported edge rather
|
||||
# than turning optional coverage into a hard compile failure.
|
||||
test.lint(verilator_flags2=["--coverage-fsm"], fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
// DESCRIPTION: Verilator: FSM rejects unknown enum constants in direct and conditional transitions
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group the same unsupported warning family across direct, conditional, and
|
||||
// reset-driven enum assignments that use constants outside the declared enum.
|
||||
|
||||
module unknown_then (
|
||||
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;
|
||||
case (state_q)
|
||||
/* verilator lint_off ENUMVALUE */
|
||||
S0: state_d = sel ? 2'd3 : S1;
|
||||
/* verilator lint_on ENUMVALUE */
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module unknown_else (
|
||||
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;
|
||||
case (state_q)
|
||||
/* verilator lint_off ENUMVALUE */
|
||||
S0: state_d = sel ? S1 : 2'd3;
|
||||
/* verilator lint_on ENUMVALUE */
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module unknown_direct (
|
||||
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;
|
||||
case (state_q)
|
||||
/* verilator lint_off ENUMVALUE */
|
||||
S0: state_d = 2'd3;
|
||||
/* verilator lint_on ENUMVALUE */
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module unknown_reset (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = S1;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
// Reset-arc extraction uses a separate path from steady-state case-item arc
|
||||
// extraction, so keep one reset-only bad enum target in this grouped test.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
/* verilator lint_off ENUMVALUE */
|
||||
state_q <= 2'd3;
|
||||
/* verilator lint_on ENUMVALUE */
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
logic clk;
|
||||
unknown_then unknown_then_u(.clk(clk));
|
||||
unknown_else unknown_else_u(.clk(clk));
|
||||
unknown_direct unknown_direct_u(.clk(clk));
|
||||
unknown_reset unknown_reset_u(.clk(clk));
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
%Warning-COVERIGN: t/t_cover_fsm_nextstate_overwrite_warn.v:21:13: Ignoring unsupported: FSM coverage on case('t.state_d') when the canonical 't.state_d' = 't.state_q' default is overwritten before the case statement
|
||||
21 | state_d = (state_d == S0) ? S1 : S0;
|
||||
| ^
|
||||
... 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
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM case(state_d) overwritten-default warning test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
# A canonical `state_d = state_q` default followed by another top-level write
|
||||
# to `state_d` before `case (state_d)` is legal RTL, but it is not the narrow
|
||||
# Phase 1 shape we support. Warn and reject rather than silently skipping it.
|
||||
test.compile(verilator_flags2=["--cc --coverage"],
|
||||
fails=True,
|
||||
expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// DESCRIPTION: Verilator: FSM case(state_d) overwritten-default warning test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1
|
||||
} state_t;
|
||||
|
||||
state_t state_q;
|
||||
state_t state_d;
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
state_d = (state_d == S0) ? S1 : S0;
|
||||
case (state_d)
|
||||
S0: state_d = S1;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1 @@
|
|||
# SystemC::Coverage-3
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage ignores grouped non-matching plain always shapes
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm', '-Werror-COVERIGN'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.files_identical(test.obj_dir + "/coverage.dat", "t/" + test.name + ".out")
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage ignores non-matching plain always scan shapes
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group plain-always warning-scan shapes that should be ignored silently
|
||||
// before the detector ever decides an FSM candidate is present.
|
||||
|
||||
module ignore_sel_expr (
|
||||
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_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
// Plain always with a non-VarRef selector should be ignored silently.
|
||||
always @(posedge clk) begin
|
||||
state_d = state_q;
|
||||
case (state_q + 1)
|
||||
S0: state_d = start ? S1 : S0;
|
||||
S1: state_d = S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module ignore_other_selector (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start,
|
||||
input logic other
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
// Plain always with an unrelated selector should also be ignored silently.
|
||||
always @(posedge clk) begin
|
||||
state_d = state_q;
|
||||
case (other)
|
||||
1'b0: state_d = start ? S1 : S0;
|
||||
default: state_d = S2;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
logic rst;
|
||||
logic start;
|
||||
logic other;
|
||||
integer cyc;
|
||||
|
||||
ignore_sel_expr ignore_sel_expr_u(.*);
|
||||
ignore_other_selector ignore_other_selector_u(.*);
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
other = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 4) other <= 1'b1;
|
||||
if (cyc == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
%Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:25:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb
|
||||
25 | case (state_q)
|
||||
| ^~~~
|
||||
... 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_plain_always_warn_multi_bad.v:55:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb
|
||||
55 | case (state_d)
|
||||
| ^~~~
|
||||
%Warning-COVERIGN: t/t_cover_fsm_plain_always_warn_multi_bad.v:86:5: Ignoring unsupported: FSM coverage on non-clocked always blocks requires a combinational sensitivity list or always_comb
|
||||
86 | case (state_q)
|
||||
| ^~~~
|
||||
%Error: Exiting due to
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage warns on grouped non-clocked always near-FSM shapes
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm'],
|
||||
fails=True,
|
||||
expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage warns on non-clocked always near-FSM shapes
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module warn_edge (
|
||||
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_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
// Plain edge-sensitive next-state logic should warn even when the selector
|
||||
// is the canonical state_q form.
|
||||
always @(posedge clk) begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = start ? S1 : S0;
|
||||
S1: state_d = S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module warn_case_next (
|
||||
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_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
// Plain always also warns for the near-supported case(state_d) style.
|
||||
always @(posedge clk) begin
|
||||
state_d = state_q;
|
||||
case (state_d)
|
||||
S0: state_d = start ? S1 : S0;
|
||||
S1: state_d = S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module warn_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_reset_arc*/ /*verilator fsm_arc_include_cond*/;
|
||||
state_t state_d;
|
||||
|
||||
// default becomes supported only because include_cond is set, but the block
|
||||
// still warns because it is a plain edge-sensitive always.
|
||||
always @(posedge clk) begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
default: state_d = S0;
|
||||
S0: state_d = start ? state_q : state_q;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
logic clk;
|
||||
logic rst;
|
||||
logic start;
|
||||
|
||||
warn_edge warn_edge_u(.*);
|
||||
warn_case_next warn_case_next_u(.*);
|
||||
warn_default_incl warn_default_incl_u(.*);
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# SystemC::Coverage-3
|
||||
C 'ft/t_cover_fsm_plain_always_zerohit_multi.vl28n5tfsm_arcpagev_fsm_arc/$rootot.near_canonical_state_d_case_u.state_d::S0->S1Fvt.near_canonical_state_d_case_u.state_dFfS0FtS1htop.TOP' 0
|
||||
C 'ft/t_cover_fsm_plain_always_zerohit_multi.vl28n5tfsm_arcpagev_fsm_arc/$rootot.near_canonical_state_d_case_u.state_d::S0->S2Fvt.near_canonical_state_d_case_u.state_dFfS0FtS2htop.TOP' 0
|
||||
C 'ft/t_cover_fsm_plain_always_zerohit_multi.vl28n5tfsm_statepagev_fsm_state/$rootot.near_canonical_state_d_case_u.state_d::S0Fvt.near_canonical_state_d_case_u.state_dFtS0htop.TOP' 0
|
||||
C 'ft/t_cover_fsm_plain_always_zerohit_multi.vl28n5tfsm_statepagev_fsm_state/$rootot.near_canonical_state_d_case_u.state_d::S1Fvt.near_canonical_state_d_case_u.state_dFtS1htop.TOP' 0
|
||||
C 'ft/t_cover_fsm_plain_always_zerohit_multi.vl28n5tfsm_statepagev_fsm_state/$rootot.near_canonical_state_d_case_u.state_d::S2Fvt.near_canonical_state_d_case_u.state_dFtS2htop.TOP' 0
|
||||
C 'ft/t_cover_fsm_plain_always_zerohit_multi.vl60n5tfsm_statepagev_fsm_state/$rootot.selector_matches_noassign_u.state_q::S0Fvt.selector_matches_noassign_u.state_qFtS0htop.TOP' 0
|
||||
C 'ft/t_cover_fsm_plain_always_zerohit_multi.vl60n5tfsm_statepagev_fsm_state/$rootot.selector_matches_noassign_u.state_q::S1Fvt.selector_matches_noassign_u.state_qFtS1htop.TOP' 0
|
||||
C 'ft/t_cover_fsm_plain_always_zerohit_multi.vl60n5tfsm_statepagev_fsm_state/$rootot.selector_matches_noassign_u.state_q::S2Fvt.selector_matches_noassign_u.state_qFtS2htop.TOP' 0
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage keeps zero-hit records for near-miss plain always shapes
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm', '-Werror-COVERIGN'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.files_identical(test.obj_dir + "/coverage.dat", "t/" + test.name + ".out")
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage keeps zero-hit records for near-miss plain always shapes
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group plain-always near misses that still recover enough structure to leave
|
||||
// zero-hit FSM records behind, but must not warn or model a supported FSM.
|
||||
|
||||
module near_canonical_state_d_case (
|
||||
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_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
// Unsupported for the plain-always warning scan: case(state_d) is only
|
||||
// near-supported when it follows the canonical state_d = state_q default.
|
||||
always @(posedge clk) begin
|
||||
state_d = S1;
|
||||
case (state_d)
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module selector_matches_noassign (
|
||||
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 other;
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
// The selector matches the FSM state, but the case body never assigns
|
||||
// state_d, so the warning scan should ignore it while leaving zero-hit
|
||||
// state points behind.
|
||||
always @(posedge clk) begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: other = start;
|
||||
default: other = 1'b0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
logic rst;
|
||||
logic start;
|
||||
integer cyc;
|
||||
|
||||
near_canonical_state_d_case near_canonical_state_d_case_u(.*);
|
||||
selector_matches_noassign selector_matches_noassign_u(.*);
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage keeps grouped accepted policy-style forms
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group accepted policy-driven forms that should stay inferred, even though
|
||||
// they exercise different coverage attributes or non-enum state policies.
|
||||
|
||||
module fsm_style_incl (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2,
|
||||
S3 = 2'd3
|
||||
} state_t;
|
||||
|
||||
state_t state /*verilator fsm_arc_include_cond*/;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end else begin
|
||||
%000003 case (state)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.style_u.state::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.style_u.state::S0->S1]
|
||||
%000003 // [fsm_arc t.style_u.state::S0->S2]
|
||||
%000000 // [fsm_arc t.style_u.state::S1->S3]
|
||||
%000000 // [SYNTHETIC DEFAULT ARC: t.style_u.state::default->S0]
|
||||
%000002 // [fsm_state t.style_u.state::S0]
|
||||
%000000 // [fsm_state t.style_u.state::S1] *** UNCOVERED ***
|
||||
%000003 // [fsm_state t.style_u.state::S2]
|
||||
%000000 // [fsm_state t.style_u.state::S3] *** UNCOVERED ***
|
||||
S0:
|
||||
if (start) state <= S1;
|
||||
else state <= S2;
|
||||
S1: state <= S3;
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_default_incl_ok (
|
||||
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;
|
||||
%000003 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.default_incl_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.default_incl_u.state_q::S0->S1]
|
||||
%000003 // [fsm_arc t.default_incl_u.state_q::S0->S2]
|
||||
%000000 // [SYNTHETIC DEFAULT ARC: t.default_incl_u.state_q::default->S0]
|
||||
%000002 // [fsm_state t.default_incl_u.state_q::S0]
|
||||
%000000 // [fsm_state t.default_incl_u.state_q::S1] *** UNCOVERED ***
|
||||
%000003 // [fsm_state t.default_incl_u.state_q::S2]
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_forced_ok (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
logic [1:0] state /*verilator fsm_state*/;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= 2'd0;
|
||||
end else begin
|
||||
%000002 case (state)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.forced_u.state::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000002 // [fsm_arc t.forced_u.state::S0->S1]
|
||||
%000002 // [fsm_arc t.forced_u.state::S1->S2]
|
||||
%000001 // [fsm_state t.forced_u.state::S0]
|
||||
%000002 // [fsm_state t.forced_u.state::S1]
|
||||
%000002 // [fsm_state t.forced_u.state::S2]
|
||||
%000000 // [fsm_state t.forced_u.state::S3] *** UNCOVERED ***
|
||||
2'd0: state <= 2'd1;
|
||||
2'd1: state <= 2'd2;
|
||||
default: state <= 2'd0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic start;
|
||||
|
||||
fsm_style_incl style_u (.clk(clk), .rst(rst), .start(start));
|
||||
fsm_default_incl_ok default_incl_u (.clk(clk), .rst(rst), .start(start));
|
||||
fsm_forced_ok forced_u (.clk(clk), .rst(rst));
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage forced non-enum test
|
||||
# DESCRIPTION: Verilator: FSM coverage keeps grouped accepted policy-style forms
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
|
|
@ -17,8 +17,6 @@ test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
|||
|
||||
test.execute()
|
||||
|
||||
# Use annotated-source golden output so hit-count regressions are visible in the
|
||||
# expected file instead of being hidden behind coarse coverage.dat greps.
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate",
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage keeps grouped accepted policy-style forms
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group accepted policy-driven forms that should stay inferred, even though
|
||||
// they exercise different coverage attributes or non-enum state policies.
|
||||
|
||||
module fsm_style_incl (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2,
|
||||
S3 = 2'd3
|
||||
} state_t;
|
||||
|
||||
state_t state /*verilator fsm_arc_include_cond*/;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end else begin
|
||||
case (state)
|
||||
S0:
|
||||
if (start) state <= S1;
|
||||
else state <= S2;
|
||||
S1: state <= S3;
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_default_incl_ok (
|
||||
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;
|
||||
case (state_q)
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_forced_ok (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
logic [1:0] state /*verilator fsm_state*/;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= 2'd0;
|
||||
end else begin
|
||||
case (state)
|
||||
2'd0: state <= 2'd1;
|
||||
2'd1: state <= 2'd2;
|
||||
default: state <= 2'd0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic start;
|
||||
|
||||
fsm_style_incl style_u (.clk(clk), .rst(rst), .start(start));
|
||||
fsm_default_incl_ok default_incl_u (.clk(clk), .rst(rst), .start(start));
|
||||
fsm_forced_ok forced_u (.clk(clk), .rst(rst));
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -1,12 +1,16 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage reset policy test
|
||||
// DESCRIPTION: Verilator: FSM coverage grouped reset semantics test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
%000006 input clk
|
||||
// Group accepted simulator-style reset semantics, including reset include /
|
||||
// exclude behavior and nearby fallback cases that should still preserve FSMs.
|
||||
|
||||
module fsm_reset_policy (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
|
||||
typedef enum logic [0:0] {
|
||||
|
|
@ -14,51 +18,179 @@
|
|||
S1 = 1'b1
|
||||
} state_t;
|
||||
|
||||
%000001 logic rst;
|
||||
integer cyc;
|
||||
%000001 state_t state_incl /*verilator fsm_reset_arc*/;
|
||||
%000001 state_t state_excl;
|
||||
state_t state_incl /*verilator fsm_reset_arc*/;
|
||||
state_t state_excl;
|
||||
|
||||
%000001 initial begin
|
||||
%000001 rst = 1'b1;
|
||||
%000001 cyc = 0;
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_incl <= S0;
|
||||
else
|
||||
%000001 case (state_incl)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.reset_policy_u.state_incl::ANY->S0[reset_include]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.reset_policy_u.state_incl::S0->S1]
|
||||
%000000 // [fsm_state t.reset_policy_u.state_incl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.reset_policy_u.state_incl::S1]
|
||||
S0: state_incl <= S1;
|
||||
default: state_incl <= S1;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000006 always @(posedge clk) begin
|
||||
%000006 cyc <= cyc + 1;
|
||||
%000005 if (cyc == 1) rst <= 1'b0;
|
||||
%000005 if (cyc == 5) begin
|
||||
%000001 $write("*-* All Finished *-*\n");
|
||||
%000001 $finish;
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_excl <= S0;
|
||||
else
|
||||
%000001 case (state_excl)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.reset_policy_u.state_excl::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.reset_policy_u.state_excl::S0->S1]
|
||||
%000000 // [fsm_state t.reset_policy_u.state_excl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.reset_policy_u.state_excl::S1]
|
||||
S0: state_excl <= S1;
|
||||
default: state_excl <= S1;
|
||||
endcase
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_reset_other_assign_ok (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'b00,
|
||||
S1 = 2'b01,
|
||||
S2 = 2'b10
|
||||
} state_t;
|
||||
|
||||
logic aux;
|
||||
logic start;
|
||||
state_t state_q;
|
||||
state_t state_d;
|
||||
|
||||
initial begin
|
||||
aux = 1'b1;
|
||||
start = 1'b1;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= S0;
|
||||
aux <= 1'b0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
|
||||
%000006 always_ff @(posedge clk) begin
|
||||
%000004 if (rst) state_incl <= S0;
|
||||
else
|
||||
%000004 case (state_incl)
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
%000002 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_incl::ANY->S0[reset_include]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.state_incl::S0->S1]
|
||||
%000000 // [fsm_state t.state_incl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.state_incl::S1]
|
||||
%000001 S0: state_incl <= S1;
|
||||
%000003 default: state_incl <= S1;
|
||||
endcase
|
||||
%000001 // [fsm_arc t.reset_other_assign_ok_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000002 // [fsm_arc t.reset_other_assign_ok_u.state_q::S0->S1]
|
||||
%000000 // [fsm_arc t.reset_other_assign_ok_u.state_q::S0->S2]
|
||||
%000002 // [fsm_state t.reset_other_assign_ok_u.state_q::S0]
|
||||
%000002 // [fsm_state t.reset_other_assign_ok_u.state_q::S1]
|
||||
%000000 // [fsm_state t.reset_other_assign_ok_u.state_q::S2] *** UNCOVERED ***
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_oneblock_reset_mismatch_ok (
|
||||
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 other_q;
|
||||
|
||||
initial begin
|
||||
state_q = S0;
|
||||
other_q = S2;
|
||||
end
|
||||
|
||||
%000006 always_ff @(posedge clk) begin
|
||||
%000004 if (rst) state_excl <= S0;
|
||||
else
|
||||
%000004 case (state_excl)
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
other_q <= S0;
|
||||
end else begin
|
||||
%000001 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_excl::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.state_excl::S0->S1]
|
||||
%000000 // [fsm_state t.state_excl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.state_excl::S1]
|
||||
%000001 S0: state_excl <= S1;
|
||||
%000003 default: state_excl <= S1;
|
||||
%000001 // [fsm_arc t.oneblock_reset_mismatch_ok_u.state_q::S0->S1]
|
||||
%000000 // [fsm_state t.oneblock_reset_mismatch_ok_u.state_q::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.oneblock_reset_mismatch_ok_u.state_q::S1]
|
||||
%000001 // [fsm_state t.oneblock_reset_mismatch_ok_u.state_q::S2]
|
||||
S0: state_q <= S1;
|
||||
default: state_q <= S2;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_oneblock_reset_nonconst_ok (
|
||||
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 other_q;
|
||||
|
||||
initial begin
|
||||
state_q = S0;
|
||||
other_q = S2;
|
||||
end
|
||||
|
||||
// Useful because the one-block reset fallback must ignore non-constant reset
|
||||
// assignments and still retain the supported case(state_q) FSM below.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= other_q;
|
||||
end else begin
|
||||
%000001 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000000 // [fsm_arc t.oneblock_reset_nonconst_ok_u.state_q::S0->S1]
|
||||
%000000 // [fsm_state t.oneblock_reset_nonconst_ok_u.state_q::S0] *** UNCOVERED ***
|
||||
%000000 // [fsm_state t.oneblock_reset_nonconst_ok_u.state_q::S1] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.oneblock_reset_nonconst_ok_u.state_q::S2]
|
||||
S0: state_q <= S1;
|
||||
default: state_q <= S2;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
logic rst;
|
||||
integer cyc;
|
||||
|
||||
fsm_reset_policy reset_policy_u (.clk(clk), .rst(rst));
|
||||
fsm_reset_other_assign_ok reset_other_assign_ok_u (.clk(clk), .rst(rst));
|
||||
fsm_oneblock_reset_mismatch_ok oneblock_reset_mismatch_ok_u (.clk(clk), .rst(rst));
|
||||
fsm_oneblock_reset_nonconst_ok oneblock_reset_nonconst_ok_u (.clk(clk), .rst(rst));
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 5) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage reset policy test
|
||||
# DESCRIPTION: Verilator: FSM coverage grouped reset semantics test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
|
|
@ -13,21 +13,10 @@ import vltest_bootstrap
|
|||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage'])
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--include-reset-arcs",
|
||||
test.obj_dir + "/coverage.dat",
|
||||
],
|
||||
logfile=test.obj_dir + "/summary.log",
|
||||
tee=False,
|
||||
verilator_run=True)
|
||||
|
||||
test.files_identical(test.obj_dir + "/summary.log", "t/" + test.name + "_summary.out")
|
||||
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate",
|
||||
|
|
@ -36,6 +25,17 @@ test.run(cmd=[
|
|||
],
|
||||
verilator_run=True)
|
||||
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--include-reset-arcs",
|
||||
"--annotate",
|
||||
test.obj_dir + "/annotated-incl",
|
||||
test.obj_dir + "/coverage.dat",
|
||||
],
|
||||
verilator_run=True)
|
||||
|
||||
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
|
||||
test.files_identical(test.obj_dir + "/annotated-incl/" + test.name + ".v",
|
||||
"t/" + test.name + "_incl.out")
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage reset policy test
|
||||
// DESCRIPTION: Verilator: FSM coverage grouped reset semantics test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
// Group accepted simulator-style reset semantics, including reset include /
|
||||
// exclude behavior and nearby fallback cases that should still preserve FSMs.
|
||||
|
||||
module fsm_reset_policy (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
|
||||
typedef enum logic [0:0] {
|
||||
|
|
@ -13,25 +17,9 @@ module t (
|
|||
S1 = 1'b1
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
integer cyc;
|
||||
state_t state_incl /*verilator fsm_reset_arc*/;
|
||||
state_t state_excl;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 5) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_incl <= S0;
|
||||
else
|
||||
|
|
@ -49,5 +37,132 @@ module t (
|
|||
default: state_excl <= S1;
|
||||
endcase
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_reset_other_assign_ok (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'b00,
|
||||
S1 = 2'b01,
|
||||
S2 = 2'b10
|
||||
} state_t;
|
||||
|
||||
logic aux;
|
||||
logic start;
|
||||
state_t state_q;
|
||||
state_t state_d;
|
||||
|
||||
initial begin
|
||||
aux = 1'b1;
|
||||
start = 1'b1;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= S0;
|
||||
aux <= 1'b0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_oneblock_reset_mismatch_ok (
|
||||
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 other_q;
|
||||
|
||||
initial begin
|
||||
state_q = S0;
|
||||
other_q = S2;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
other_q <= S0;
|
||||
end else begin
|
||||
case (state_q)
|
||||
S0: state_q <= S1;
|
||||
default: state_q <= S2;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_oneblock_reset_nonconst_ok (
|
||||
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 other_q;
|
||||
|
||||
initial begin
|
||||
state_q = S0;
|
||||
other_q = S2;
|
||||
end
|
||||
|
||||
// Useful because the one-block reset fallback must ignore non-constant reset
|
||||
// assignments and still retain the supported case(state_q) FSM below.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= other_q;
|
||||
end else begin
|
||||
case (state_q)
|
||||
S0: state_q <= S1;
|
||||
default: state_q <= S2;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
logic rst;
|
||||
integer cyc;
|
||||
|
||||
fsm_reset_policy reset_policy_u (.clk(clk), .rst(rst));
|
||||
fsm_reset_other_assign_ok reset_other_assign_ok_u (.clk(clk), .rst(rst));
|
||||
fsm_oneblock_reset_mismatch_ok oneblock_reset_mismatch_ok_u (.clk(clk), .rst(rst));
|
||||
fsm_oneblock_reset_nonconst_ok oneblock_reset_nonconst_ok_u (.clk(clk), .rst(rst));
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 5) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
|
|||
|
|
@ -0,0 +1,197 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage grouped reset semantics test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group accepted simulator-style reset semantics, including reset include /
|
||||
// exclude behavior and nearby fallback cases that should still preserve FSMs.
|
||||
|
||||
module fsm_reset_policy (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
|
||||
typedef enum logic [0:0] {
|
||||
S0 = 1'b0,
|
||||
S1 = 1'b1
|
||||
} state_t;
|
||||
|
||||
state_t state_incl /*verilator fsm_reset_arc*/;
|
||||
state_t state_excl;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_incl <= S0;
|
||||
else
|
||||
%000001 case (state_incl)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.reset_policy_u.state_incl::ANY->S0[reset_include]]
|
||||
%000001 // [fsm_arc t.reset_policy_u.state_incl::S0->S1]
|
||||
%000000 // [fsm_state t.reset_policy_u.state_incl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.reset_policy_u.state_incl::S1]
|
||||
S0: state_incl <= S1;
|
||||
default: state_incl <= S1;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_excl <= S0;
|
||||
else
|
||||
%000001 case (state_excl)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.reset_policy_u.state_excl::ANY->S0[reset]]
|
||||
%000001 // [fsm_arc t.reset_policy_u.state_excl::S0->S1]
|
||||
%000000 // [fsm_state t.reset_policy_u.state_excl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.reset_policy_u.state_excl::S1]
|
||||
S0: state_excl <= S1;
|
||||
default: state_excl <= S1;
|
||||
endcase
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_reset_other_assign_ok (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'b00,
|
||||
S1 = 2'b01,
|
||||
S2 = 2'b10
|
||||
} state_t;
|
||||
|
||||
logic aux;
|
||||
logic start;
|
||||
state_t state_q;
|
||||
state_t state_d;
|
||||
|
||||
initial begin
|
||||
aux = 1'b1;
|
||||
start = 1'b1;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= S0;
|
||||
aux <= 1'b0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
%000002 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.reset_other_assign_ok_u.state_q::ANY->S0[reset]]
|
||||
%000002 // [fsm_arc t.reset_other_assign_ok_u.state_q::S0->S1]
|
||||
%000000 // [fsm_arc t.reset_other_assign_ok_u.state_q::S0->S2]
|
||||
%000002 // [fsm_state t.reset_other_assign_ok_u.state_q::S0]
|
||||
%000002 // [fsm_state t.reset_other_assign_ok_u.state_q::S1]
|
||||
%000000 // [fsm_state t.reset_other_assign_ok_u.state_q::S2] *** UNCOVERED ***
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_oneblock_reset_mismatch_ok (
|
||||
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 other_q;
|
||||
|
||||
initial begin
|
||||
state_q = S0;
|
||||
other_q = S2;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
other_q <= S0;
|
||||
end else begin
|
||||
%000001 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.oneblock_reset_mismatch_ok_u.state_q::S0->S1]
|
||||
%000000 // [fsm_state t.oneblock_reset_mismatch_ok_u.state_q::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.oneblock_reset_mismatch_ok_u.state_q::S1]
|
||||
%000001 // [fsm_state t.oneblock_reset_mismatch_ok_u.state_q::S2]
|
||||
S0: state_q <= S1;
|
||||
default: state_q <= S2;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_oneblock_reset_nonconst_ok (
|
||||
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 other_q;
|
||||
|
||||
initial begin
|
||||
state_q = S0;
|
||||
other_q = S2;
|
||||
end
|
||||
|
||||
// Useful because the one-block reset fallback must ignore non-constant reset
|
||||
// assignments and still retain the supported case(state_q) FSM below.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= other_q;
|
||||
end else begin
|
||||
%000001 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000000 // [fsm_arc t.oneblock_reset_nonconst_ok_u.state_q::S0->S1]
|
||||
%000000 // [fsm_state t.oneblock_reset_nonconst_ok_u.state_q::S0] *** UNCOVERED ***
|
||||
%000000 // [fsm_state t.oneblock_reset_nonconst_ok_u.state_q::S1] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.oneblock_reset_nonconst_ok_u.state_q::S2]
|
||||
S0: state_q <= S1;
|
||||
default: state_q <= S2;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
logic rst;
|
||||
integer cyc;
|
||||
|
||||
fsm_reset_policy reset_policy_u (.clk(clk), .rst(rst));
|
||||
fsm_reset_other_assign_ok reset_other_assign_ok_u (.clk(clk), .rst(rst));
|
||||
fsm_oneblock_reset_mismatch_ok oneblock_reset_mismatch_ok_u (.clk(clk), .rst(rst));
|
||||
fsm_oneblock_reset_nonconst_ok oneblock_reset_nonconst_ok_u (.clk(clk), .rst(rst));
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 5) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -1,63 +1,6 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
integer cyc;
|
||||
state_t state /*verilator fsm_reset_arc*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 5) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
// This reset block is intentionally non-idiomatic. The detector only collects
|
||||
// reset arcs from top-level direct assignments in the reset branch, so two
|
||||
// sequential assignments are the narrowest way to force multiple reset arcs
|
||||
// into one FSM graph and exercise reuse of the synthetic ANY reset source.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
state <= S1;
|
||||
end
|
||||
else begin
|
||||
%000001 case (state)
|
||||
// [FSM coverage]
|
||||
%000000 // [fsm_arc t.state::ANY->S0[reset_include]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.state::ANY->S1[reset_include]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.state::S0->S2]
|
||||
%000001 // [fsm_arc t.state::S1->S2]
|
||||
%000000 // [fsm_state t.state::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.state::S1]
|
||||
%000001 // [fsm_state t.state::S2]
|
||||
S0: state <= S2;
|
||||
S1: state <= S2;
|
||||
default: state <= S2;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
%Warning-COVERIGN: t/t_cover_fsm_reset_multi.v:41:13: Ignoring unsupported: FSM coverage on reset branches with multiple assignments to the state variable
|
||||
41 | state <= 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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test
|
||||
# DESCRIPTION: Verilator: FSM coverage multi-reset assignment warning test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
|
|
@ -7,30 +7,14 @@
|
|||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
test.scenarios('vlt')
|
||||
|
||||
# This regression is aimed at the graph helper, not at recommending RTL style.
|
||||
# We deliberately create two reset arcs in a single FSM so graph construction
|
||||
# has to reuse the synthetic ANY reset pseudo-vertex rather than allocating it
|
||||
# only once for a one-arc machine.
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
||||
|
||||
test.execute()
|
||||
|
||||
# Use annotated-source output so the golden proves both reset arcs remain
|
||||
# visible and share the same synthetic ANY reset source.
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate",
|
||||
test.obj_dir + "/annotated",
|
||||
test.obj_dir + "/coverage.dat",
|
||||
],
|
||||
verilator_run=True)
|
||||
|
||||
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
|
||||
# Multiple reset assignments to the same FSM state variable are legal RTL but
|
||||
# not a realistic reset style to model as distinct reset arcs. Warn and ignore
|
||||
# reset-arc extraction for that branch instead of inventing multiple ANY->state
|
||||
# coverage edges.
|
||||
test.lint(verilator_flags2=["--coverage-fsm"], fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test
|
||||
// DESCRIPTION: Verilator: FSM coverage multi-reset assignment warning test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
|
|
@ -32,10 +32,9 @@ module t (
|
|||
end
|
||||
end
|
||||
|
||||
// This reset block is intentionally non-idiomatic. The detector only collects
|
||||
// reset arcs from top-level direct assignments in the reset branch, so two
|
||||
// sequential assignments are the narrowest way to force multiple reset arcs
|
||||
// into one FSM graph and exercise reuse of the synthetic ANY reset source.
|
||||
// This reset block is intentionally non-idiomatic. Multiple assignments to
|
||||
// the FSM state variable in the reset branch should warn and be ignored for
|
||||
// reset-arc extraction rather than inventing multiple reset transitions.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
Coverage Summary:
|
||||
line : 100.0% (8/8)
|
||||
toggle : 75.0% (6/8)
|
||||
branch : 100.0% (8/8)
|
||||
expr : 0.0% (0/0)
|
||||
fsm_state : 50.0% (2/4)
|
||||
fsm_arc : 100.0% (4/4)
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage style coverage test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2,
|
||||
S3 = 2'd3
|
||||
} state_t;
|
||||
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic start;
|
||||
state_t state /*verilator fsm_arc_include_cond*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end
|
||||
else begin
|
||||
%000003 case (state)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.state::S0->S1]
|
||||
%000003 // [fsm_arc t.state::S0->S2]
|
||||
%000000 // [fsm_arc t.state::S1->S3]
|
||||
%000000 // [SYNTHETIC DEFAULT ARC: t.state::default->S0]
|
||||
%000002 // [fsm_state t.state::S0]
|
||||
%000000 // [fsm_state t.state::S1] *** UNCOVERED ***
|
||||
%000003 // [fsm_state t.state::S2]
|
||||
%000000 // [fsm_state t.state::S3] *** UNCOVERED ***
|
||||
S0:
|
||||
if (start) state <= S1;
|
||||
else state <= S2;
|
||||
S1: state <= S3;
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage style coverage test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2,
|
||||
S3 = 2'd3
|
||||
} state_t;
|
||||
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic start;
|
||||
state_t state /*verilator fsm_arc_include_cond*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end
|
||||
else begin
|
||||
case (state)
|
||||
S0:
|
||||
if (start) state <= S1;
|
||||
else state <= S2;
|
||||
S1: state <= S3;
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1 @@
|
|||
# SystemC::Coverage-3
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage style coverage test
|
||||
# DESCRIPTION: Verilator: FSM coverage ignores grouped unsupported FSM extraction patterns
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
|
|
@ -7,8 +7,6 @@
|
|||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
|
@ -17,11 +15,6 @@ test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
|||
|
||||
test.execute()
|
||||
|
||||
test.run(cmd=[os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate", test.obj_dir + "/annotated",
|
||||
test.obj_dir + "/coverage.dat"],
|
||||
verilator_run=True) # yapf:disable
|
||||
|
||||
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
|
||||
test.files_identical(test.obj_dir + "/coverage.dat", "t/" + test.name + ".out")
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,819 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage ignores grouped unsupported FSM extraction patterns
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group unsupported extraction shapes that must compile and run cleanly while
|
||||
// emitting no FSM coverage points at all.
|
||||
|
||||
module fsm_caseitem_varrhs_bad (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic start;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
state_t other_d;
|
||||
|
||||
initial begin
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
other_d = S1;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = other_d;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_caseitem_then_nonconst_bad (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1
|
||||
} state_t;
|
||||
|
||||
logic sel;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
state_t other_d;
|
||||
|
||||
initial begin
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
other_d = S1;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
/* verilator lint_off CASEINCOMPLETE */
|
||||
case (state_q)
|
||||
S0: state_d = sel ? other_d : S1;
|
||||
endcase
|
||||
/* verilator lint_on CASEINCOMPLETE */
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_caseitem_else_nonconst_bad (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1
|
||||
} state_t;
|
||||
|
||||
logic sel;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
state_t other_d;
|
||||
|
||||
initial begin
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
other_d = S1;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
/* verilator lint_off CASEINCOMPLETE */
|
||||
case (state_q)
|
||||
S0: state_d = sel ? S1 : other_d;
|
||||
endcase
|
||||
/* verilator lint_on CASEINCOMPLETE */
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_oneblock_then_bad (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
logic sel;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
sel = 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;
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = sel ? S1 : S0;
|
||||
S1: state_d = S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= rst ? (sel ? S0 : S1) : state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_oneblock_else_bad (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
logic sel;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
sel = 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;
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = sel ? S1 : S0;
|
||||
S1: state_d = S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= rst ? S0 : (sel ? S1 : state_d);
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_combo_sel_expr_bad (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
logic start;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q + 1)
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_q <= S0;
|
||||
end else begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_normalized_if_noelse_bad (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, S2 = 2'd2 } state_t;
|
||||
logic rst;
|
||||
logic start;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
end
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: if (start) ; else state_d = S1;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_normalized_if_then_nonconst_bad (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, S2 = 2'd2 } state_t;
|
||||
logic sel;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
state_t tmp_a;
|
||||
state_t tmp_b;
|
||||
state_t other_d;
|
||||
|
||||
initial begin
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
other_d = S2;
|
||||
end
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
end
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: begin
|
||||
if (sel) tmp_a = other_d;
|
||||
else tmp_b = S1;
|
||||
state_d = tmp_a;
|
||||
end
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_normalized_if_else_nonconst_bad (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, S2 = 2'd2 } state_t;
|
||||
logic sel;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
state_t tmp_a;
|
||||
state_t tmp_b;
|
||||
state_t other_d;
|
||||
|
||||
initial begin
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
other_d = S2;
|
||||
end
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
end
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: begin
|
||||
if (sel) tmp_a = S1;
|
||||
else tmp_b = other_d;
|
||||
state_d = tmp_a;
|
||||
end
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_normalized_if_temp_mismatch_bad (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, S2 = 2'd2 } state_t;
|
||||
logic sel;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
state_t tmp_a;
|
||||
state_t tmp_b;
|
||||
|
||||
initial begin
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
end
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: begin
|
||||
if (sel) tmp_a = S1;
|
||||
else tmp_b = S2;
|
||||
state_d = tmp_a;
|
||||
end
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_normalized_if_then_state_else_other_bad (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, S2 = 2'd2 } state_t;
|
||||
logic sel;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
state_t tmp_a;
|
||||
|
||||
initial begin
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
end
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: begin
|
||||
if (sel) state_d = S1;
|
||||
else tmp_a = S2;
|
||||
state_d = tmp_a;
|
||||
end
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_normalized_if_same_temp_nofollow_bad (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, S2 = 2'd2 } state_t;
|
||||
logic sel;
|
||||
logic aux;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
state_t tmp_a;
|
||||
|
||||
initial begin
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
end
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: begin
|
||||
if (sel) begin
|
||||
tmp_a = S1;
|
||||
aux = 1'b1;
|
||||
end else begin
|
||||
tmp_a = S2;
|
||||
aux = 1'b0;
|
||||
end
|
||||
end
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_normalized_if_follow_nonvar_bad (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, S2 = 2'd2 } state_t;
|
||||
logic sel;
|
||||
logic aux;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
state_t tmp_a;
|
||||
|
||||
initial begin
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
end
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: begin
|
||||
if (sel) begin
|
||||
tmp_a = S1;
|
||||
aux = 1'b1;
|
||||
end else begin
|
||||
tmp_a = S2;
|
||||
aux = 1'b0;
|
||||
end
|
||||
// Final follow-up is not a plain var-to-var state assignment.
|
||||
aux = (tmp_a == S1);
|
||||
end
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_normalized_if_follow_wrongfrom_bad (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, S2 = 2'd2 } state_t;
|
||||
logic sel;
|
||||
logic aux;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
state_t tmp_a;
|
||||
state_t other_d;
|
||||
|
||||
initial begin
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
end
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: begin
|
||||
if (sel) begin
|
||||
tmp_a = S1;
|
||||
aux = 1'b1;
|
||||
end else begin
|
||||
tmp_a = S2;
|
||||
aux = 1'b0;
|
||||
end
|
||||
// Final follow-up reads from the wrong source temp.
|
||||
state_d = other_d;
|
||||
end
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_normalized_if_follow_wronglhs_bad (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, S2 = 2'd2 } state_t;
|
||||
logic sel;
|
||||
logic aux;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
state_t tmp_a;
|
||||
state_t other_d;
|
||||
|
||||
initial begin
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
end
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: begin
|
||||
if (sel) begin
|
||||
tmp_a = S1;
|
||||
aux = 1'b1;
|
||||
end else begin
|
||||
tmp_a = S2;
|
||||
aux = 1'b0;
|
||||
end
|
||||
// Final follow-up writes the wrong lhs instead of state_d.
|
||||
other_d = tmp_a;
|
||||
end
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_case_next_wrongrhs_bad (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] { S0 = 2'd0, S1 = 2'd1, S2 = 2'd2 } state_t;
|
||||
logic rst;
|
||||
logic start;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
state_t other_d;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
other_d = S1;
|
||||
end
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
end
|
||||
always_comb begin
|
||||
state_d = other_d;
|
||||
case (state_d)
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_forced_wide_bad (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic [30:0] state /*verilator fsm_state*/;
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
rst = 1'b1;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= 31'd0;
|
||||
end else begin
|
||||
case (state)
|
||||
31'd0: state <= 31'd1;
|
||||
31'd1: state <= 31'd2;
|
||||
default: state <= 31'd0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
module fsm_reset_commit_mismatch_bad (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
logic start;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t other_q;
|
||||
state_t state_d;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = start ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) other_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
module fsm_reset_then_bad (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
logic sel;
|
||||
integer cyc;
|
||||
state_t state_q /*verilator fsm_state*/;
|
||||
state_t state_d;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
sel = 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 == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = sel ? S1 : S0;
|
||||
S1: state_d = S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= sel ? S0 : S1;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
integer cyc;
|
||||
|
||||
fsm_caseitem_varrhs_bad caseitem_varrhs_bad_u (.clk(clk));
|
||||
fsm_caseitem_then_nonconst_bad caseitem_then_nonconst_bad_u (.clk(clk));
|
||||
fsm_caseitem_else_nonconst_bad caseitem_else_nonconst_bad_u (.clk(clk));
|
||||
fsm_oneblock_then_bad oneblock_then_bad_u (.clk(clk));
|
||||
fsm_oneblock_else_bad oneblock_else_bad_u (.clk(clk));
|
||||
fsm_combo_sel_expr_bad combo_sel_expr_bad_u (.clk(clk));
|
||||
fsm_normalized_if_noelse_bad normalized_if_noelse_bad_u (.clk(clk));
|
||||
fsm_normalized_if_then_nonconst_bad normalized_if_then_nonconst_bad_u (.clk(clk));
|
||||
fsm_normalized_if_else_nonconst_bad normalized_if_else_nonconst_bad_u (.clk(clk));
|
||||
fsm_normalized_if_temp_mismatch_bad normalized_if_temp_mismatch_bad_u (.clk(clk));
|
||||
fsm_normalized_if_then_state_else_other_bad normalized_if_then_state_else_other_bad_u (.clk(clk));
|
||||
fsm_normalized_if_same_temp_nofollow_bad normalized_if_same_temp_nofollow_bad_u (.clk(clk));
|
||||
fsm_normalized_if_follow_nonvar_bad normalized_if_follow_nonvar_bad_u (.clk(clk));
|
||||
fsm_normalized_if_follow_wrongfrom_bad normalized_if_follow_wrongfrom_bad_u (.clk(clk));
|
||||
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_reset_commit_mismatch_bad reset_commit_mismatch_bad_u (.clk(clk));
|
||||
fsm_reset_then_bad reset_then_bad_u (.clk(clk));
|
||||
|
||||
initial cyc = 0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,459 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage combined two-process/three-block regression
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group supported multi-process and nearby supported-core variants so the
|
||||
// main two-process / three-block extraction family stays in one place.
|
||||
|
||||
module fsm_basic (
|
||||
%000009 input logic clk,
|
||||
%000001 input logic rst,
|
||||
%000001 input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S_IDLE = 2'd0,
|
||||
S_RUN = 2'd1,
|
||||
S_DONE = 2'd2,
|
||||
S_ERR = 2'd3
|
||||
} state_t;
|
||||
|
||||
%000002 state_t state_q /*verilator fsm_reset_arc*/;
|
||||
%000002 state_t state_d;
|
||||
|
||||
000010 always_comb begin
|
||||
000010 state_d = state_q;
|
||||
~000010 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.basic_u.state_q::ANY->S_IDLE[reset_include]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.basic_u.state_q::S_RUN->S_DONE]
|
||||
%000001 // [fsm_state t.basic_u.state_q::S_DONE]
|
||||
%000001 // [fsm_state t.basic_u.state_q::S_ERR]
|
||||
%000000 // [fsm_state t.basic_u.state_q::S_IDLE] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.basic_u.state_q::S_RUN]
|
||||
%000004 S_IDLE: if (start) state_d = S_RUN;
|
||||
%000001 S_RUN: state_d = S_DONE;
|
||||
%000005 default: state_d = S_ERR;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000007 if (rst) state_q <= S_IDLE;
|
||||
%000007 else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_three_block (
|
||||
%000009 input logic clk,
|
||||
%000001 input logic rst,
|
||||
%000001 input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S_IDLE = 2'd0,
|
||||
S_BUSY = 2'd1,
|
||||
S_DONE = 2'd2
|
||||
} state_t;
|
||||
|
||||
%000001 logic busy;
|
||||
%000001 logic done;
|
||||
%000001 state_t state_q;
|
||||
%000001 state_t state_d;
|
||||
|
||||
000010 always_comb begin
|
||||
000010 state_d = state_q;
|
||||
~000010 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.three_block_u.state_q::ANY->S_IDLE[reset]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.three_block_u.state_q::S_BUSY->S_DONE]
|
||||
%000001 // [fsm_arc t.three_block_u.state_q::S_DONE->S_IDLE]
|
||||
%000001 // [fsm_state t.three_block_u.state_q::S_BUSY]
|
||||
%000001 // [fsm_state t.three_block_u.state_q::S_DONE]
|
||||
%000001 // [fsm_state t.three_block_u.state_q::S_IDLE]
|
||||
%000008 S_IDLE: if (start) state_d = S_BUSY;
|
||||
%000001 S_BUSY: state_d = S_DONE;
|
||||
%000001 S_DONE: state_d = S_IDLE;
|
||||
%000000 default: state_d = S_IDLE;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000001 always_comb begin
|
||||
%000001 busy = (state_q == S_BUSY);
|
||||
%000001 done = (state_q == S_DONE);
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000007 if (rst) state_q <= S_IDLE;
|
||||
%000007 else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_mealy (
|
||||
%000009 input logic clk,
|
||||
%000001 input logic rst,
|
||||
%000001 input logic start,
|
||||
%000001 input logic bit_done
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S_IDLE = 2'd0,
|
||||
S_SEND = 2'd1,
|
||||
S_DONE = 2'd2
|
||||
} state_t;
|
||||
|
||||
%000002 logic tx;
|
||||
%000001 state_t state_q;
|
||||
%000001 state_t state_d;
|
||||
|
||||
000010 always_comb begin
|
||||
000010 state_d = state_q;
|
||||
000010 tx = 1'b1;
|
||||
~000010 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.mealy_u.state_q::ANY->S_IDLE[reset]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.mealy_u.state_q::S_DONE->S_IDLE]
|
||||
%000001 // [fsm_state t.mealy_u.state_q::S_DONE]
|
||||
%000001 // [fsm_state t.mealy_u.state_q::S_IDLE]
|
||||
%000001 // [fsm_state t.mealy_u.state_q::S_SEND]
|
||||
%000007 S_IDLE: begin
|
||||
%000006 if (start) begin
|
||||
%000001 state_d = S_SEND;
|
||||
%000001 tx = 1'b0;
|
||||
end
|
||||
end
|
||||
%000002 S_SEND: begin
|
||||
%000002 tx = bit_done;
|
||||
%000001 if (bit_done) state_d = S_DONE;
|
||||
end
|
||||
%000001 S_DONE: state_d = S_IDLE;
|
||||
%000000 default: state_d = S_IDLE;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000007 if (rst) state_q <= S_IDLE;
|
||||
%000007 else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_reset_policy (
|
||||
%000009 input logic clk,
|
||||
%000001 input logic rst
|
||||
);
|
||||
typedef enum logic [0:0] {
|
||||
S0 = 1'b0,
|
||||
S1 = 1'b1
|
||||
} state_t;
|
||||
|
||||
%000001 state_t state_incl_q /*verilator fsm_reset_arc*/;
|
||||
%000001 state_t state_incl_d;
|
||||
%000001 state_t state_excl_q;
|
||||
%000001 state_t state_excl_d;
|
||||
|
||||
000010 always_comb begin
|
||||
000010 state_incl_d = state_incl_q;
|
||||
~000010 case (state_incl_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.reset_u.state_incl_q::ANY->S0[reset_include]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.reset_u.state_incl_q::S0->S1]
|
||||
%000000 // [fsm_state t.reset_u.state_incl_q::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.reset_u.state_incl_q::S1]
|
||||
%000003 S0: state_incl_d = S1;
|
||||
%000007 default: state_incl_d = S1;
|
||||
endcase
|
||||
end
|
||||
|
||||
000010 always_comb begin
|
||||
000010 state_excl_d = state_excl_q;
|
||||
~000010 case (state_excl_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.reset_u.state_excl_q::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.reset_u.state_excl_q::S0->S1]
|
||||
%000000 // [fsm_state t.reset_u.state_excl_q::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.reset_u.state_excl_q::S1]
|
||||
%000003 S0: state_excl_d = S1;
|
||||
%000007 default: state_excl_d = S1;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000007 if (rst) state_incl_q <= S0;
|
||||
%000007 else state_incl_q <= state_incl_d;
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000007 if (rst) state_excl_q <= S0;
|
||||
%000007 else state_excl_q <= state_excl_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_nextstate_sel_off (
|
||||
%000009 input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1
|
||||
} state_t;
|
||||
|
||||
%000001 state_t state_q;
|
||||
%000001 state_t state_d;
|
||||
|
||||
%000001 always_comb begin
|
||||
%000001 state_d = S0;
|
||||
%000001 case (state_d)
|
||||
%000001 S0: state_d = S1;
|
||||
%000000 default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000009 state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_nextstate_sel_ok (
|
||||
%000009 input logic clk,
|
||||
%000001 input logic rst,
|
||||
%000001 input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
%000001 state_t state_q;
|
||||
%000001 state_t state_d;
|
||||
|
||||
000010 always_comb begin
|
||||
000010 state_d = state_q;
|
||||
~000010 case (state_d)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.nextstate_sel_ok_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.nextstate_sel_ok_u.state_q::S1->S2]
|
||||
%000001 // [fsm_state t.nextstate_sel_ok_u.state_q::S0]
|
||||
%000001 // [fsm_state t.nextstate_sel_ok_u.state_q::S1]
|
||||
%000001 // [fsm_state t.nextstate_sel_ok_u.state_q::S2]
|
||||
%000008 S0: if (start) state_d = S1;
|
||||
%000001 S1: state_d = S2;
|
||||
%000001 default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000007 if (rst) state_q <= S0;
|
||||
%000007 else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_ternary (
|
||||
%000009 input logic clk,
|
||||
%000001 input logic rst,
|
||||
%000001 input logic sel
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
%000004 state_t state_q;
|
||||
%000004 state_t state_d;
|
||||
|
||||
000010 always_comb begin
|
||||
000010 state_d = state_q;
|
||||
~000010 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.ternary_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.ternary_u.state_q::S0->S1]
|
||||
%000004 // [fsm_arc t.ternary_u.state_q::S0->S2]
|
||||
%000003 // [fsm_state t.ternary_u.state_q::S0]
|
||||
%000000 // [fsm_state t.ternary_u.state_q::S1] *** UNCOVERED ***
|
||||
%000004 // [fsm_state t.ternary_u.state_q::S2]
|
||||
%000009 S0: state_d = sel ? S1 : S2;
|
||||
%000004 default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000007 if (rst) state_q <= S0;
|
||||
%000007 else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_plain_always (
|
||||
%000009 input logic clk,
|
||||
%000001 input logic rst,
|
||||
%000001 input logic go
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
%000001 state_t state_q;
|
||||
%000001 state_t state_d;
|
||||
|
||||
000010 always @* begin
|
||||
000010 state_d = state_q;
|
||||
~000010 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.plain_always_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.plain_always_u.state_q::S1->S2]
|
||||
%000001 // [fsm_state t.plain_always_u.state_q::S0]
|
||||
%000001 // [fsm_state t.plain_always_u.state_q::S1]
|
||||
%000001 // [fsm_state t.plain_always_u.state_q::S2]
|
||||
%000008 S0: if (go) state_d = S1;
|
||||
%000001 S1: state_d = S2;
|
||||
%000001 default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000007 if (rst) state_q <= S0;
|
||||
%000007 else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_plain_always_list (
|
||||
%000009 input logic clk,
|
||||
%000001 input logic rst,
|
||||
%000001 input logic go
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
%000004 state_t state_q;
|
||||
%000004 state_t state_d;
|
||||
|
||||
%000008 always @(state_q or go) begin
|
||||
%000008 state_d = state_q;
|
||||
%000008 case (state_q)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.plain_always_list_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.plain_always_list_u.state_q::S0->S1]
|
||||
%000004 // [fsm_arc t.plain_always_list_u.state_q::S0->S2]
|
||||
%000003 // [fsm_state t.plain_always_list_u.state_q::S0]
|
||||
%000000 // [fsm_state t.plain_always_list_u.state_q::S1] *** UNCOVERED ***
|
||||
%000004 // [fsm_state t.plain_always_list_u.state_q::S2]
|
||||
%000007 S0: state_d = go ? S1 : S2;
|
||||
%000004 default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000007 if (rst) state_q <= S0;
|
||||
%000007 else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_caseassigns_off (
|
||||
%000009 input logic clk,
|
||||
%000001 input logic rst,
|
||||
%000001 input logic go
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1
|
||||
} state_t;
|
||||
|
||||
%000001 logic out;
|
||||
%000000 state_t state_q;
|
||||
%000000 state_t state_d;
|
||||
|
||||
000010 always_comb begin
|
||||
000010 state_d = state_q;
|
||||
000010 out = 1'b0;
|
||||
000010 case (state_q)
|
||||
000010 S0: out = go;
|
||||
%000000 default: out = 1'b0;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000007 if (rst) state_q <= S0;
|
||||
%000007 else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_seqmix_off (
|
||||
%000009 input logic clk,
|
||||
%000001 input logic rst
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1
|
||||
} state_t;
|
||||
|
||||
%000004 logic side;
|
||||
%000004 state_t state_q;
|
||||
%000004 state_t state_d;
|
||||
|
||||
%000001 initial side = 1'b0;
|
||||
|
||||
000010 always_comb begin
|
||||
000010 state_d = state_q;
|
||||
000010 case (state_q)
|
||||
%000006 S0: state_d = S1;
|
||||
%000004 default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000009 always_ff @(posedge clk) begin
|
||||
%000007 if (rst) state_q <= S0;
|
||||
%000007 else begin
|
||||
%000007 side <= ~side;
|
||||
%000007 state_q <= state_d;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
%000009 input clk
|
||||
);
|
||||
integer cyc;
|
||||
%000001 logic rst;
|
||||
%000001 logic start;
|
||||
%000001 logic bit_done;
|
||||
%000001 logic sel;
|
||||
|
||||
fsm_basic basic_u (.clk(clk), .rst(rst), .start(start));
|
||||
fsm_three_block three_block_u (.clk(clk), .rst(rst), .start(start));
|
||||
fsm_mealy mealy_u (.clk(clk), .rst(rst), .start(start), .bit_done(bit_done));
|
||||
fsm_reset_policy reset_u (.clk(clk), .rst(rst));
|
||||
fsm_nextstate_sel_off nextstate_sel_off_u (.clk(clk));
|
||||
fsm_nextstate_sel_ok nextstate_sel_ok_u (.clk(clk), .rst(rst), .start(start));
|
||||
fsm_ternary ternary_u (.clk(clk), .rst(rst), .sel(sel));
|
||||
fsm_plain_always plain_always_u (.clk(clk), .rst(rst), .go(start));
|
||||
fsm_plain_always_list plain_always_list_u (.clk(clk), .rst(rst), .go(start));
|
||||
fsm_caseassigns_off caseassigns_off_u (.clk(clk), .rst(rst), .go(start));
|
||||
fsm_seqmix_off seqmix_off_u (.clk(clk), .rst(rst));
|
||||
|
||||
%000001 initial begin
|
||||
%000001 cyc = 0;
|
||||
%000001 rst = 1'b1;
|
||||
%000001 start = 1'b0;
|
||||
%000001 bit_done = 1'b0;
|
||||
%000001 sel = 1'b0;
|
||||
end
|
||||
|
||||
%000009 always @(posedge clk) begin
|
||||
%000009 cyc <= cyc + 1;
|
||||
%000008 if (cyc == 1) rst <= 1'b0;
|
||||
%000008 if (cyc == 2) start <= 1'b1;
|
||||
%000008 if (cyc == 3) start <= 1'b0;
|
||||
%000008 if (cyc == 4) bit_done <= 1'b1;
|
||||
%000008 if (cyc == 4) sel <= 1'b1;
|
||||
%000008 if (cyc == 5) bit_done <= 1'b0;
|
||||
%000008 if (cyc == 5) sel <= 1'b0;
|
||||
%000008 if (cyc == 8) begin
|
||||
%000001 $write("*-* All Finished *-*\n");
|
||||
%000001 $finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage combined two-process/three-block regression
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate",
|
||||
test.obj_dir + "/annotated",
|
||||
test.obj_dir + "/coverage.dat",
|
||||
],
|
||||
verilator_run=True)
|
||||
|
||||
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
|
||||
test.file_grep_not(test.obj_dir + "/coverage.dat", r"t\.nextstate_sel_off_u\.state_q.*fsm_")
|
||||
test.file_grep_not(test.obj_dir + "/coverage.dat", r"t\.caseassigns_off_u\.state_q.*fsm_")
|
||||
test.file_grep_not(test.obj_dir + "/coverage.dat", r"t\.seqmix_off_u\.state_q.*fsm_")
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,401 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage combined two-process/three-block regression
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group supported multi-process and nearby supported-core variants so the
|
||||
// main two-process / three-block extraction family stays in one place.
|
||||
|
||||
module fsm_basic (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S_IDLE = 2'd0,
|
||||
S_RUN = 2'd1,
|
||||
S_DONE = 2'd2,
|
||||
S_ERR = 2'd3
|
||||
} state_t;
|
||||
|
||||
state_t state_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_d;
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S_IDLE: if (start) state_d = S_RUN;
|
||||
S_RUN: state_d = S_DONE;
|
||||
default: state_d = S_ERR;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S_IDLE;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_three_block (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S_IDLE = 2'd0,
|
||||
S_BUSY = 2'd1,
|
||||
S_DONE = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic busy;
|
||||
logic done;
|
||||
state_t state_q;
|
||||
state_t state_d;
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S_IDLE: if (start) state_d = S_BUSY;
|
||||
S_BUSY: state_d = S_DONE;
|
||||
S_DONE: state_d = S_IDLE;
|
||||
default: state_d = S_IDLE;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
busy = (state_q == S_BUSY);
|
||||
done = (state_q == S_DONE);
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S_IDLE;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_mealy (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic start,
|
||||
input logic bit_done
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S_IDLE = 2'd0,
|
||||
S_SEND = 2'd1,
|
||||
S_DONE = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic tx;
|
||||
state_t state_q;
|
||||
state_t state_d;
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
tx = 1'b1;
|
||||
case (state_q)
|
||||
S_IDLE: begin
|
||||
if (start) begin
|
||||
state_d = S_SEND;
|
||||
tx = 1'b0;
|
||||
end
|
||||
end
|
||||
S_SEND: begin
|
||||
tx = bit_done;
|
||||
if (bit_done) state_d = S_DONE;
|
||||
end
|
||||
S_DONE: state_d = S_IDLE;
|
||||
default: state_d = S_IDLE;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S_IDLE;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_reset_policy (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
typedef enum logic [0:0] {
|
||||
S0 = 1'b0,
|
||||
S1 = 1'b1
|
||||
} state_t;
|
||||
|
||||
state_t state_incl_q /*verilator fsm_reset_arc*/;
|
||||
state_t state_incl_d;
|
||||
state_t state_excl_q;
|
||||
state_t state_excl_d;
|
||||
|
||||
always_comb begin
|
||||
state_incl_d = state_incl_q;
|
||||
case (state_incl_q)
|
||||
S0: state_incl_d = S1;
|
||||
default: state_incl_d = S1;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_excl_d = state_excl_q;
|
||||
case (state_excl_q)
|
||||
S0: state_excl_d = S1;
|
||||
default: state_excl_d = S1;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_incl_q <= S0;
|
||||
else state_incl_q <= state_incl_d;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_excl_q <= S0;
|
||||
else state_excl_q <= state_excl_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_nextstate_sel_off (
|
||||
input logic clk
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1
|
||||
} state_t;
|
||||
|
||||
state_t state_q;
|
||||
state_t state_d;
|
||||
|
||||
always_comb begin
|
||||
state_d = S0;
|
||||
case (state_d)
|
||||
S0: state_d = S1;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_nextstate_sel_ok (
|
||||
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;
|
||||
case (state_d)
|
||||
S0: if (start) state_d = S1;
|
||||
S1: state_d = S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_ternary (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic sel
|
||||
);
|
||||
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;
|
||||
case (state_q)
|
||||
S0: state_d = sel ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_plain_always (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic go
|
||||
);
|
||||
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 @* begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: if (go) state_d = S1;
|
||||
S1: state_d = S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_plain_always_list (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic go
|
||||
);
|
||||
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 @(state_q or go) begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = go ? S1 : S2;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_caseassigns_off (
|
||||
input logic clk,
|
||||
input logic rst,
|
||||
input logic go
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1
|
||||
} state_t;
|
||||
|
||||
logic out;
|
||||
state_t state_q;
|
||||
state_t state_d;
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
out = 1'b0;
|
||||
case (state_q)
|
||||
S0: out = go;
|
||||
default: out = 1'b0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else state_q <= state_d;
|
||||
end
|
||||
endmodule
|
||||
|
||||
module fsm_seqmix_off (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1
|
||||
} state_t;
|
||||
|
||||
logic side;
|
||||
state_t state_q;
|
||||
state_t state_d;
|
||||
|
||||
initial side = 1'b0;
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = S1;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_q <= S0;
|
||||
else begin
|
||||
side <= ~side;
|
||||
state_q <= state_d;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic start;
|
||||
logic bit_done;
|
||||
logic sel;
|
||||
|
||||
fsm_basic basic_u (.clk(clk), .rst(rst), .start(start));
|
||||
fsm_three_block three_block_u (.clk(clk), .rst(rst), .start(start));
|
||||
fsm_mealy mealy_u (.clk(clk), .rst(rst), .start(start), .bit_done(bit_done));
|
||||
fsm_reset_policy reset_u (.clk(clk), .rst(rst));
|
||||
fsm_nextstate_sel_off nextstate_sel_off_u (.clk(clk));
|
||||
fsm_nextstate_sel_ok nextstate_sel_ok_u (.clk(clk), .rst(rst), .start(start));
|
||||
fsm_ternary ternary_u (.clk(clk), .rst(rst), .sel(sel));
|
||||
fsm_plain_always plain_always_u (.clk(clk), .rst(rst), .go(start));
|
||||
fsm_plain_always_list plain_always_list_u (.clk(clk), .rst(rst), .go(start));
|
||||
fsm_caseassigns_off caseassigns_off_u (.clk(clk), .rst(rst), .go(start));
|
||||
fsm_seqmix_off seqmix_off_u (.clk(clk), .rst(rst));
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
bit_done = 1'b0;
|
||||
sel = 1'b0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 4) bit_done <= 1'b1;
|
||||
if (cyc == 4) sel <= 1'b1;
|
||||
if (cyc == 5) bit_done <= 1'b0;
|
||||
if (cyc == 5) sel <= 1'b0;
|
||||
if (cyc == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -12,7 +12,7 @@ from pathlib import Path
|
|||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
test.top_filename = "t/t_cover_fsm_styles.v"
|
||||
test.top_filename = "t/t_cover_fsm_policy_accept_multi.v"
|
||||
|
||||
# Dump generic COVEROTHERDECL nodes so AstCoverOtherDecl::dump() also sees
|
||||
# coverage declarations with no FSM metadata, exercising the empty-field side
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
%Warning-FSMMULTI: t/t_fsmmulti_combo_multi_warn_bad.v:34:21: FSM coverage: multiple supported transition candidates found in the same combinational always block. Only the first candidate will be instrumented.
|
||||
34 | B0: state_b_d = B1;
|
||||
| ^
|
||||
t/t_fsmmulti_combo_multi_warn_bad.v:30:21: ... Location of first supported candidate for 't.same_u.state_a_q'
|
||||
30 | A0: state_a_d = A1;
|
||||
| ^
|
||||
... 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_combo_multi_warn_bad.v:70:19: FSM coverage: multiple supported transition candidates found for the same FSM in combinational always blocks. Only the first candidate will be instrumented.
|
||||
70 | S0: state_d = S1;
|
||||
| ^
|
||||
t/t_fsmmulti_combo_multi_warn_bad.v:62:19: ... Location of first supported candidate for 't.split_u.state_q'
|
||||
62 | S0: state_d = S1;
|
||||
| ^
|
||||
%Error: Exiting due to
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: combined FSMMULTI warning regression
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
test.lint(verilator_flags2=["--coverage-fsm"], fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
// DESCRIPTION: Verilator: combined FSMMULTI warning regression
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// Group the combo-family multi-candidate warnings where two supported
|
||||
// transition sites compete and the detector must keep only the first one.
|
||||
|
||||
module same_always_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;
|
||||
case (state_a_q)
|
||||
A0: state_a_d = A1;
|
||||
default: state_a_d = A0;
|
||||
endcase
|
||||
case (state_b_q)
|
||||
B0: state_b_d = B1;
|
||||
default: state_b_d = B0;
|
||||
endcase
|
||||
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_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;
|
||||
case (state_q)
|
||||
S0: state_d = S1;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
state_d = state_q;
|
||||
case (state_q)
|
||||
S0: state_d = S1;
|
||||
default: state_d = S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
state_q <= state_d;
|
||||
end
|
||||
/* verilator lint_on MULTIDRIVEN */
|
||||
endmodule
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
same_always_warn same_u (.clk(clk));
|
||||
split_always_warn split_u (.clk(clk));
|
||||
endmodule
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
%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)
|
||||
| ^~~~
|
||||
t/t_fsmmulti_same_bad.v:28:7: ... Location of first supported candidate for 't.state'
|
||||
28 | 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.
|
||||
%Error: Exiting due to
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
%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)
|
||||
| ^~~~
|
||||
t/t_fsmmulti_warn_bad.v:25:5: ... Location of first supported candidate for 't.state_a'
|
||||
25 | 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.
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@
|
|||
%000005 case (state_default)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_default::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.state_default::S0->S1]
|
||||
%000003 // [fsm_arc t.state_default::S0->S2]
|
||||
%000000 // [SYNTHETIC DEFAULT ARC: t.state_default::default->S0]
|
||||
%000002 // [fsm_state t.state_default::S0]
|
||||
%000000 // [fsm_state t.state_default::S1] *** UNCOVERED ***
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@
|
|||
%000005 case (state_default)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_default::ANY->S0[reset]]
|
||||
%000000 // [fsm_arc t.state_default::S0->S1]
|
||||
%000003 // [fsm_arc t.state_default::S0->S2]
|
||||
%000000 // [SYNTHETIC DEFAULT ARC: t.state_default::default->S0]
|
||||
%000002 // [fsm_state t.state_default::S0]
|
||||
%000000 // [fsm_state t.state_default::S1] *** UNCOVERED ***
|
||||
|
|
|
|||
Loading…
Reference in New Issue