Support FSM detection in primitive wrappers (#7607)

This commit is contained in:
Yogish Sekhar 2026-05-21 17:50:31 +00:00 committed by GitHub
parent 6a74112f0b
commit f282335600
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 3636 additions and 89 deletions

View File

@ -100,6 +100,27 @@ The grammar of control commands is as follows:
Same as :option:`/*verilator&32;forceable*/` metacomment.
.. option:: fsm_register_wrapper -module "<modulename>" -d "<port>" -q "<port>" -clock "<port>" [-reset "<port>"] [-reset_value "<param>"]
Declares that the specified module is a transparent FSM state-register
wrapper for FSM coverage extraction. One declaration applies to all
instances of that module.
The ``-d`` option names the wrapper input port carrying next-state data.
The ``-q`` option names the wrapper output port carrying registered state.
The ``-clock`` option names the wrapper clock port.
Verilator does not infer wrapper semantics from module or port naming
conventions. A wrapper module must be declared with this option before
Verilator will use instances of it to extract FSM state and transition
coverage.
The ``-reset`` and ``-reset_value`` options are optional. Reset arcs are
emitted only when both the reset polarity can be inferred from the
wrapper's event control and ``-reset_value`` names a static parameter value.
If reset metadata is incomplete, Verilator may still emit FSM state and
transition coverage without reset arcs.
.. option:: full_case -file "<filename>" -lines <lineno>
Same as ``//synthesis full_case``. When these synthesis directives

View File

@ -262,6 +262,35 @@ the extracted coverage model:
- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch
arcs that would otherwise be skipped by the conservative extractor.
State registers may also be wrapped by a transparent instance, for
example a project flop wrapper or primitive. Such wrappers must be
described explicitly with a VLT command file action before Verilator will
use their data, state, clock, or reset connections for FSM extraction:
.. code-block:: sv
`verilator_config
fsm_register_wrapper -module "my_fsm_flop" -d "state_i" -q "state_o" -clock "clk_i"
The same command may be placed in a separate ``.vlt`` file:
.. code-block:: sv
fsm_register_wrapper -module "my_fsm_flop" -d "state_i" -q "state_o" -clock "clk_i"
Optional reset metadata may also be supplied:
.. code-block:: sv
fsm_register_wrapper -module "my_fsm_flop" -d "state_i" -q "state_o" -clock "clk_i" \
-reset "rst_ni" -reset_value "ResetValue"
Reset arcs are emitted only when the configured reset port has an
inferable edge in the wrapper and the configured reset value parameter is
statically resolvable. If reset metadata is incomplete, Verilator warns
and may still emit FSM state and transition coverage, but reset arcs are
omitted.
Reset transitions are included in the collected data either way. By
default, :command:`verilator_coverage` summarizes reset-only arcs rather
than printing them alongside non-reset arcs. Use

View File

@ -1964,6 +1964,7 @@ class AstVar final : public AstNode {
bool m_attrSFormat : 1; // User sformat attribute
bool m_attrSplitVar : 1; // declared with split_var metacomment
bool m_attrFsmState : 1; // declared with fsm_state metacomment
bool m_attrFsmRegisterWrapper : 1; // connected to an fsm_register_wrapper instance
bool m_attrFsmResetArc : 1; // declared with fsm_reset_arc metacomment
bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment
bool m_fileDescr : 1; // File descriptor
@ -2025,6 +2026,7 @@ class AstVar final : public AstNode {
m_attrSFormat = false;
m_attrSplitVar = false;
m_attrFsmState = false;
m_attrFsmRegisterWrapper = false;
m_attrFsmResetArc = false;
m_attrFsmArcInclCond = false;
m_fileDescr = false;
@ -2172,6 +2174,7 @@ public:
void attrSFormat(bool flag) { m_attrSFormat = flag; }
void attrSplitVar(bool flag) { m_attrSplitVar = flag; }
void attrFsmState(bool flag) { m_attrFsmState = flag; }
void attrFsmRegisterWrapper(bool flag) { m_attrFsmRegisterWrapper = flag; }
void attrFsmResetArc(bool flag) { m_attrFsmResetArc = flag; }
void attrFsmArcInclCond(bool flag) { m_attrFsmArcInclCond = flag; }
void rand(const VRandAttr flag) { m_rand = flag; }
@ -2338,6 +2341,7 @@ public:
bool attrSFormat() const { return m_attrSFormat; }
bool attrSplitVar() const { return m_attrSplitVar; }
bool attrFsmState() const { return m_attrFsmState; }
bool attrFsmRegisterWrapper() const { return m_attrFsmRegisterWrapper; }
bool attrFsmResetArc() const { return m_attrFsmResetArc; }
bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; }
bool attrIsolateAssign() const { return m_attrIsolateAssign; }

View File

@ -227,8 +227,10 @@ class V3ControlModule final {
V3ControlVarResolver m_params; // Parameters in module
V3ControlVarResolver m_ports; // Ports in module
V3ControlVarResolver m_vars; // Variables in module
V3Control::FsmRegisterWrapper m_fsmRegisterWrapper; // FSM wrapper descriptor
std::unordered_set<std::string> m_coverageOffBlocks; // List of block names for coverage_off
std::set<VPragmaType> m_modPragmas; // List of Pragmas for modules
bool m_hasFsmRegisterWrapper = false; // Whether descriptor has been set
bool m_inline = false; // Whether to force the inline
bool m_inlineValue = false; // The inline value (on/off)
@ -240,6 +242,10 @@ public:
m_params.update(m.m_params);
m_ports.update(m.m_ports);
m_vars.update(m.m_vars);
if (m.m_hasFsmRegisterWrapper) {
m_fsmRegisterWrapper = m.m_fsmRegisterWrapper;
m_hasFsmRegisterWrapper = true;
}
for (const string& i : m.m_coverageOffBlocks) m_coverageOffBlocks.insert(i);
if (!m_inline) {
m_inline = m.m_inline;
@ -261,6 +267,18 @@ public:
m_inline = true;
m_inlineValue = set;
}
void setFsmRegisterWrapper(FileLine* fl, const V3Control::FsmRegisterWrapper& desc) {
if (m_hasFsmRegisterWrapper) {
fl->v3warn(BADVLTPRAGMA, "Duplicate fsm_register_wrapper descriptor for module "
<< AstNode::prettyNameQ(desc.moduleName)
<< "; replacing previous descriptor");
}
m_fsmRegisterWrapper = desc;
m_hasFsmRegisterWrapper = true;
}
const V3Control::FsmRegisterWrapper* fsmRegisterWrapperp() const {
return m_hasFsmRegisterWrapper ? &m_fsmRegisterWrapper : nullptr;
}
void addModulePragma(VPragmaType pragma) { m_modPragmas.insert(pragma); }
void apply(AstNodeModule* modp) {
@ -877,6 +895,23 @@ void V3Control::addHierWorkers(FileLine* fl, const string& model, int workers) {
V3ControlResolver::s().addHierWorkers(fl, model, workers);
}
void V3Control::addFsmRegisterWrapper(FileLine* fl, const string& module, const string& d,
const string& q, const string& clock,
const string& reset, const string& resetValue) {
string missing;
if (module.empty()) missing += "-module";
if (d.empty()) missing = VString::dot(missing, ", ", "-d");
if (q.empty()) missing = VString::dot(missing, ", ", "-q");
if (clock.empty()) missing = VString::dot(missing, ", ", "-clock");
if (!missing.empty()) {
fl->v3error("fsm_register_wrapper missing " << missing);
return;
}
const FsmRegisterWrapper desc{module, d, q, clock, reset, resetValue};
V3ControlResolver::s().modules().at(module).setFsmRegisterWrapper(fl, desc);
}
void V3Control::addIgnore(V3ErrorCode code, bool on, const string& filename, int min, int max) {
UINFO(9, "addIgnore " << code << " " << min << "-" << max << " fn=" << filename);
if (filename == "*") { // For "lint_off/lint_on [--rule x]"
@ -1066,6 +1101,10 @@ int V3Control::getHierWorkers(const string& model) {
FileLine* V3Control::getHierWorkersFileLine(const string& model) {
return V3ControlResolver::s().getHierWorkersFileLine(model);
}
const V3Control::FsmRegisterWrapper* V3Control::getFsmRegisterWrapper(const string& module) {
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(module);
return modp ? modp->fsmRegisterWrapperp() : nullptr;
}
uint64_t V3Control::getProfileData(const string& hierDpi) {
return V3ControlResolver::s().getProfileData(hierDpi);
}

View File

@ -29,6 +29,15 @@
class V3Control final {
public:
struct FsmRegisterWrapper final {
string moduleName;
string d;
string q;
string clock;
string reset;
string resetValue;
};
enum class VarSpecKind {
PARAM, // Select only matching parameters
PORT, // Select only matching ports
@ -40,6 +49,9 @@ public:
static void addCoverageBlockOff(const string& file, int lineno);
static void addCoverageBlockOff(const string& module, const string& blockname);
static void addHierWorkers(FileLine* fl, const string& model, int workers);
static void addFsmRegisterWrapper(FileLine* fl, const string& module, const string& d,
const string& q, const string& clock,
const string& reset, const string& resetValue);
static void addIgnore(V3ErrorCode code, bool on, const string& filename, int min, int max);
static void addIgnoreMatch(V3ErrorCode code, const string& filename, const string& contents,
const string& match);
@ -64,6 +76,7 @@ public:
static int getHierWorkers(const string& model);
static FileLine* getHierWorkersFileLine(const string& model);
static const FsmRegisterWrapper* getFsmRegisterWrapper(const string& module);
static uint64_t getProfileData(const string& hierDpi);
static uint64_t getProfileData(const string& model, const string& key);
static FileLine* getProfileDataFileLine();

View File

@ -28,6 +28,7 @@
#include "V3FsmDetect.h"
#include "V3Ast.h"
#include "V3Control.h"
#include "V3Graph.h"
#include <algorithm>
@ -108,22 +109,73 @@ struct FsmSenDesc final {
struct FsmResetCondDesc final {
// Reset signal used by the FSM in the saved scoped AST.
AstVarScope* varScopep = nullptr;
bool activeLow = false;
};
class FsmResetArcDesc final {
FsmStateValue m_toValue; // Encoded reset target state.
AstNodeAssign* m_nodep = nullptr; // Source assignment for warnings and emitted metadata
AstNode* m_nodep = nullptr; // Source node for warnings and emitted metadata.
AstNodeExpr* m_valuep = nullptr; // Expression that provided the reset value.
public:
FsmResetArcDesc() = default;
FsmResetArcDesc(FsmStateValue toValue, AstNodeAssign* nodep)
: m_toValue{toValue}
, m_nodep{nodep} {}
, m_nodep{nodep}
, m_valuep{nodep->rhsp()} {}
FsmResetArcDesc(FsmStateValue toValue, AstNode* nodep, AstNodeExpr* valuep)
: m_toValue{toValue}
, m_nodep{nodep}
, m_valuep{valuep} {}
FsmStateValue toValue() const { return m_toValue; }
AstNodeAssign* nodep() const { return m_nodep; }
AstNode* nodep() const { return m_nodep; }
AstNodeExpr* valuep() const { return m_valuep; }
};
struct FsmWrapperRoles final {
string dPort;
string qPort;
string clkPort;
string rstPort;
string rstValParam;
bool hasRstActiveLow = false;
bool rstActiveLow = false;
};
static bool fsmWrapperResetPolarityFromWrapperAst(AstCell* cellp, const string& portName,
bool& activeLow) {
bool matched = false;
cellp->modp()->foreach([&](AstSenItem* itemp) {
AstNodeVarRef* const vrefp = itemp->varrefp();
if (!vrefp) return;
if (vrefp->varp()->name() != portName) return;
activeLow = itemp->edgeType() == VEdgeType::ET_NEGEDGE;
matched = true;
});
return matched;
}
static const V3Control::FsmRegisterWrapper* fsmRegisterWrapperDesc(AstCell* cellp) {
AstNodeModule* const modp = cellp->modp();
const string origName = modp->origName();
if (const V3Control::FsmRegisterWrapper* const descp
= V3Control::getFsmRegisterWrapper(origName)) {
return descp;
}
return V3Control::getFsmRegisterWrapper(modp->prettyDehashOrigOrName());
}
static FsmWrapperRoles rolesFromDesc(const V3Control::FsmRegisterWrapper& desc) {
FsmWrapperRoles roles;
roles.dPort = desc.d;
roles.qPort = desc.q;
roles.clkPort = desc.clock;
roles.rstPort = desc.reset;
roles.rstValParam = desc.resetValue;
return roles;
}
class FsmRegisterCandidate final {
AstScope* m_scopep = nullptr; // Owning scope for the paired FSM.
AstAlways* m_alwaysp = nullptr; // Register process that commits the state.
@ -135,6 +187,7 @@ class FsmRegisterCandidate final {
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.
FileLine* m_flp = nullptr; // Representative source location.
public:
AstScope* scopep() const { return m_scopep; }
@ -157,6 +210,8 @@ public:
void resetInclude(bool flag) { m_resetInclude = flag; }
bool inclCond() const { return m_inclCond; }
void inclCond(bool flag) { m_inclCond = flag; }
FileLine* fileline() const { return m_flp; }
void fileline(FileLine* flp) { m_flp = flp; }
};
class FsmComboAlways final {
@ -386,6 +441,8 @@ struct FsmIfChainCandidate final {
// Aliases are accepted only when they are equivalent to spelling the state
// comparison inline; this avoids inferring FSM semantics from arbitrary logic.
using FsmAliasMap = std::unordered_map<const AstVarScope*, FsmStateComparison>;
using FsmCellPortMap = std::unordered_map<string, AstVarScope*>;
using FsmCellPortAliasMap = std::unordered_map<const AstCell*, FsmCellPortMap>;
struct StateConstLabel final {
string text;
@ -439,11 +496,21 @@ class FsmDetectVisitor final : public VNVisitor {
std::vector<FsmComboAlways> m_oneBlockAlwayss;
std::vector<FsmComboAlways> m_comboAlwayss;
std::vector<FsmComboAlways> m_nonComboAlwayss;
// Wrapper FSM detection has a second path for designs compiled without
// inlining. In that shape the state register stays behind an AstCell, so we
// remember candidate cells and resolve them only after the surrounding
// transition logic and post-link port wiring have both been seen.
std::vector<std::pair<AstScope*, AstCell*>> m_wrapperCells;
std::unordered_map<const AstVarScope*, FsmCaseCandidate> m_comboPaired;
// Continuous aliases are order-independent, while procedural aliases must
// remain source-order scoped to avoid using assignments not yet executed.
FsmAliasMap m_stateAliases;
std::unordered_set<const AstVarScope*> m_ambiguousStateAliases;
// A surviving wrapper's semantic d/q relationship is split across the
// parent scope and the child module scope. This table is the narrow bridge
// between those scopes: only transparent port aliases are recorded, so the
// detector does not become a general cross-module dataflow engine.
FsmCellPortAliasMap m_cellPortAliases;
// METHODS
// Enum-backed FSMs may be wrapped in refs/typedefs; normalize to the
@ -460,6 +527,93 @@ class FsmDetectVisitor final : public VNVisitor {
+ firstCand.warnNodep->warnContextSecondary();
}
static bool rejectFsmWrapperCell(AstCell* cellp, const string& reason) {
cellp->v3warn(COVERIGN, "Ignoring unsupported: " + reason);
return false;
}
static bool simpleParamStateValue(AstCell* cellp, const string& name, FsmStateValue& value,
AstNodeExpr*& valuepr) {
// Cell-path reset recovery must behave like the inlined path when the
// instance relies on a parameter default. Looking into the linked module
// default preserves that equivalence while keeping the cell detector's
// contract narrow: only static, known reset encodings become reset arcs.
valuepr = nullptr;
for (AstNode* stmtp = cellp->modp()->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
AstVar* const varp = VN_CAST(stmtp, Var);
if (!varp || !varp->isParam() || varp->name() != name) continue;
valuepr = VN_AS(varp->valuep(), NodeExpr);
return constValueStatus(valuepr, value) == ConstValueStatus::OK;
}
return false;
}
static bool childPortInScope(AstVarScope* vscp, AstScope* parentScopep, AstCell*& cellpr) {
if (!vscp->varp()->isIO()) return false;
AstScope* const scopep = vscp->scopep();
UASSERT_OBJ(scopep, vscp, "VarScope without scope");
if (scopep->aboveScopep() != parentScopep) return false;
UASSERT_OBJ(scopep->aboveCellp(), vscp,
"Child port scope should retain the instance that created it");
cellpr = scopep->aboveCellp();
return true;
}
static AstVarScope* simpleAssignVarScope(AstNodeExpr* exprp) {
AstVarRef* const vrefp = VN_CAST(exprp, VarRef);
return vrefp ? vrefp->varScopep() : nullptr;
}
void addWrapperCell(AstScope* scopep, AstCell* cellp) {
m_cellPortAliases.emplace(cellp, FsmCellPortMap{});
const std::pair<AstScope*, AstCell*> item{scopep, cellp};
if (std::find(m_wrapperCells.cbegin(), m_wrapperCells.cend(), item)
!= m_wrapperCells.cend()) {
return;
}
m_wrapperCells.emplace_back(item);
}
void collectCellPortAlias(AstAssignW* nodep) {
UASSERT_OBJ(m_scopep, nodep, "Cell port alias collection requires a scoped assignment");
AstVarScope* const lhsVscp = simpleAssignVarScope(nodep->lhsp());
AstVarScope* const rhsVscp = simpleAssignVarScope(nodep->rhsp());
if (!lhsVscp || !rhsVscp) return;
AstCell* cellp = nullptr;
// The cell path is intentionally a transparent-wrapper recognizer. A
// direct parent<->child variable assignment preserves the register's
// identity across the hierarchy boundary; any expression, slice, or
// transform is outside this phase's contract and therefore not recorded.
if (childPortInScope(lhsVscp, m_scopep, cellp)) {
if (!fsmRegisterWrapperDesc(cellp)) return;
UASSERT_OBJ(lhsVscp->varp()->isInput(), nodep,
"Child-side port alias lhs should be an input");
UASSERT_OBJ(rhsVscp->scopep() == m_scopep, nodep,
"Child input port alias should connect from the parent scope");
m_cellPortAliases[cellp][lhsVscp->varp()->name()] = rhsVscp;
addWrapperCell(m_scopep, cellp);
} else if (childPortInScope(rhsVscp, m_scopep, cellp)) {
if (!fsmRegisterWrapperDesc(cellp)) return;
UASSERT_OBJ(rhsVscp->varp()->isWritable(), nodep,
"Child-side port alias rhs should be writable");
UASSERT_OBJ(lhsVscp->scopep() == m_scopep, nodep,
"Child output port alias should connect into the parent scope");
m_cellPortAliases[cellp][rhsVscp->varp()->name()] = lhsVscp;
addWrapperCell(m_scopep, cellp);
}
}
AstVarScope* roleVarScope(AstCell* cellp, const string& portName) const {
// At this point explicit AstPin expressions have been lowered away, so
// role resolution crosses the wrapper boundary only through the
// transparent alias table above. This keeps wrapper support aligned with
// direct-register detection instead of growing into interprocedural FSM
// inference.
const FsmCellPortMap& ports = m_cellPortAliases.at(cellp);
const FsmCellPortMap::const_iterator portIt = ports.find(portName);
return portIt == ports.end() ? nullptr : portIt->second;
}
class RegisterAlwaysAnalyzer final {
AstScope* const m_scopep;
@ -546,6 +700,85 @@ class FsmDetectVisitor final : public VNVisitor {
}
};
bool matchFsmWrapperCell(AstScope* scopep, AstCell* cellp, FsmRegisterCandidate& cand) const {
FsmWrapperRoles roles = rolesFromDesc(*fsmRegisterWrapperDesc(cellp));
AstVarScope* const nextVscp = roleVarScope(cellp, roles.dPort);
AstVarScope* const stateVscp = roleVarScope(cellp, roles.qPort);
if (!nextVscp || !stateVscp) {
return rejectFsmWrapperCell(
cellp, "fsm_register_wrapper d and q connections must be simple variables");
}
AstVarScope* const clkVscp = roleVarScope(cellp, roles.clkPort);
if (!clkVscp) {
return rejectFsmWrapperCell(
cellp, "fsm_register_wrapper instance requires a simple clock connection");
}
FsmSenDesc clkSense;
clkSense.edgeType = VEdgeType::ET_POSEDGE;
clkSense.varScopep = clkVscp;
cand.senses().push_back(clkSense);
AstVarScope* resetVscp = nullptr;
if (!roles.rstPort.empty()) resetVscp = roleVarScope(cellp, roles.rstPort);
if (resetVscp) {
// The descriptor identifies the reset port but not its polarity. Use
// the wrapper's own event control AST as the contract for sampling
// the connected parent signal.
bool inferredActiveLow = false;
if (fsmWrapperResetPolarityFromWrapperAst(cellp, roles.rstPort, inferredActiveLow)) {
roles.hasRstActiveLow = true;
roles.rstActiveLow = inferredActiveLow;
}
}
AstNodeExpr* resetValuep = nullptr;
FsmStateValue resetValue;
const bool hasResetValue
= !roles.rstValParam.empty()
&& simpleParamStateValue(cellp, roles.rstValParam, resetValue, resetValuep);
if (resetVscp && roles.hasRstActiveLow && hasResetValue) {
FsmSenDesc rstSense;
rstSense.edgeType = roles.rstActiveLow ? VEdgeType::ET_NEGEDGE : VEdgeType::ET_POSEDGE;
rstSense.varScopep = resetVscp;
cand.senses().push_back(rstSense);
cand.resetCond().varScopep = resetVscp;
cand.resetCond().activeLow = roles.rstActiveLow;
cand.hasResetCond(true);
cand.resetArcs().emplace_back(resetValue, cellp, resetValuep);
} else if (!roles.rstPort.empty() || !roles.rstValParam.empty()) {
string reason;
if (roles.rstPort.empty()) {
reason = "reset port is not configured";
} else if (!resetVscp) {
reason = "reset connection is missing or not a simple variable";
} else if (!roles.hasRstActiveLow) {
reason = "reset polarity could not be inferred from the wrapper";
} else if (roles.rstValParam.empty()) {
reason = "reset_value parameter is not configured";
} else {
reason = "reset_value parameter is missing or not static";
}
cellp->v3warn(COVERIGN,
"Ignoring unsupported: fsm_register_wrapper reset arcs require both "
"reset polarity and static reset value; " + reason);
}
// This candidate represents a register proven through an instance
// boundary, so there is no parent always_ff body to annotate. Lowering
// treats null alwaysp as the explicit cell-path contract and builds its
// sampling block from the recovered clock/reset interface instead.
cand.scopep(scopep);
cand.alwaysp(nullptr);
cand.stateVscp(stateVscp);
cand.nextVscp(nextVscp);
cand.resetInclude(stateVscp->varp()->attrFsmResetArc());
cand.inclCond(stateVscp->varp()->attrFsmArcInclCond());
cand.fileline(cellp->fileline());
return true;
}
class ComboAlwaysAnalyzer final {
public:
struct ComboMatch final {
@ -748,6 +981,7 @@ class FsmDetectVisitor final : public VNVisitor {
static AstNodeAssign* directCondStateVarAssign(AstNode* nodep, AstVarScope*& stateVscp,
AstVarScope*& fromVscp, AstNodeExpr*& condp,
bool& resetActiveLow,
FsmStateValue& resetValue) {
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
if (!assp) return nullptr;
@ -756,12 +990,18 @@ class FsmDetectVisitor final : public VNVisitor {
"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 || constValueStatus(rhsp->thenp(), resetValue) != ConstValueStatus::OK) {
if (AstVarRef* const elsep = VN_CAST(rhsp->elsep(), VarRef)) {
if (constValueStatus(rhsp->thenp(), resetValue) != ConstValueStatus::OK) return nullptr;
fromVscp = elsep->varScopep();
resetActiveLow = false;
} else if (AstVarRef* const thenp = VN_CAST(rhsp->thenp(), VarRef)) {
if (constValueStatus(rhsp->elsep(), resetValue) != ConstValueStatus::OK) return nullptr;
fromVscp = thenp->varScopep();
resetActiveLow = true;
} else {
return nullptr;
}
stateVscp = lhsp->varScopep();
fromVscp = elsep->varScopep();
condp = rhsp->condp();
return assp;
}
@ -1169,11 +1409,20 @@ class FsmDetectVisitor final : public VNVisitor {
cand.hasResetCond(cand.resetCond().varScopep != nullptr);
} else {
AstNodeExpr* resetCondp = nullptr;
bool resetActiveLow = false;
FsmStateValue resetValue;
if (AstNodeAssign* const assp
= directCondStateVarAssign(nodep, stateVscp, nextVscp, resetCondp, resetValue)) {
= directCondStateVarAssign(nodep, stateVscp, nextVscp, resetCondp, resetActiveLow,
resetValue)) {
// Inlined wrappers can normalize into a compact active-low
// assignment form that earlier direct-register FSM support did
// not accept. The pre-inline marker is the architectural fence:
// it lets wrapper-derived registers use that shape without
// changing the meaning of unrelated legacy RTL.
if (resetActiveLow && !stateVscp->varp()->attrFsmRegisterWrapper()) return false;
cand.resetArcs().emplace_back(resetValue, assp);
cand.resetCond() = describeResetCond(resetCondp);
cand.resetCond().activeLow = resetActiveLow;
cand.hasResetCond(cand.resetCond().varScopep != nullptr);
} else if (!nodeStateVarAssign(nodep, stateVscp, nextVscp)) {
return false;
@ -1186,6 +1435,7 @@ class FsmDetectVisitor final : public VNVisitor {
cand.senses() = describeSenTree(alwaysp->sentreep());
cand.resetInclude(stateVscp->varp()->attrFsmResetArc());
cand.inclCond(stateVscp->varp()->attrFsmArcInclCond());
cand.fileline(alwaysp->fileline());
return true;
}
@ -1253,7 +1503,7 @@ class FsmDetectVisitor final : public VNVisitor {
FsmStateSpace& stateSpace) {
for (const FsmResetArcDesc& resetArc : resetArcs) {
StateConstLabel label{resetArc.toValue().ascii(), false, 0};
if (AstConst* const constp = VN_CAST(resetArc.nodep()->rhsp(), Const)) {
if (AstConst* const constp = VN_CAST(resetArc.valuep(), Const)) {
label = stateLabelForConst(constp);
}
UASSERT_OBJ(
@ -1768,6 +2018,14 @@ class FsmDetectVisitor final : public VNVisitor {
// Continuous aliases are unordered hardware connections, so source
// order should not affect whether an if-chain FSM is recognized.
collectAliasFromAssign(nodep, m_stateAliases, m_ambiguousStateAliases);
collectCellPortAlias(nodep);
iterateChildren(nodep);
}
void visit(AstCell* nodep) override {
// Cells are matched after the full traversal because linkdot lowers
// uninlined port connections into sibling continuous assignments.
if (m_scopep && fsmRegisterWrapperDesc(nodep)) addWrapperCell(m_scopep, nodep);
iterateChildren(nodep);
}
@ -1781,6 +2039,12 @@ public:
FsmDetectVisitor(FsmState& state, AstNetlist* rootp)
: m_state{state} {
iterate(rootp);
for (const std::pair<AstScope*, AstCell*>& wrapperCell : m_wrapperCells) {
FsmRegisterCandidate reg;
if (matchFsmWrapperCell(wrapperCell.first, wrapperCell.second, reg)) {
m_registerCandidates.emplace_back(reg);
}
}
for (const FsmComboAlways& oneBlock : m_oneBlockAlwayss) processOneBlockAlways(oneBlock);
for (const FsmComboAlways& combo : m_comboAlwayss) processComboAlways(combo);
for (const FsmComboAlways& combo : m_nonComboAlwayss) warnUnsupportedComboAlways(combo);
@ -1812,8 +2076,9 @@ class FsmLowerVisitor final {
}
static AstNodeExpr* buildResetCond(FileLine* flp, AstVarScope* resetVscp,
const FsmResetCondDesc&) {
return new AstVarRef{flp, resetVscp, VAccess::READ};
const FsmResetCondDesc& desc) {
AstNodeExpr* const refp = new AstVarRef{flp, resetVscp, VAccess::READ};
return desc.activeLow ? static_cast<AstNodeExpr*>(new AstLogNot{flp, refp}) : refp;
}
// Rebuild the original event control from the saved sense description so
@ -1862,14 +2127,25 @@ class FsmLowerVisitor final {
scopep->addBlocksp(initActivep);
AstAlwaysPost* const covPostp = new AstAlwaysPost{flp};
// Save the previous state as plain sequential logic at the front of
// the original always_ff body, then evaluate coverage in post logic
// after the delayed state update commits. This avoids a scheduler race
// between a separate AstAlwaysPre task and the real state commit.
AstNode* const bodysp = alwaysp->stmtsp()->unlinkFrBackWithNext();
alwaysp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, prevVscp, VAccess::WRITE},
new AstVarRef{flp, stateVscp, VAccess::READ}});
alwaysp->addStmtsp(bodysp);
bool updatePrevAfterPost = false;
if (alwaysp) {
// Save the previous state as plain sequential logic at the front of
// the original always_ff body, then evaluate coverage in post logic
// after the delayed state update commits. This avoids a scheduler
// race between a separate AstAlwaysPre task and the real state
// commit for direct parent-level registers.
AstNode* const bodysp = alwaysp->stmtsp()->unlinkFrBackWithNext();
alwaysp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, prevVscp, VAccess::WRITE},
new AstVarRef{flp, stateVscp, VAccess::READ}});
alwaysp->addStmtsp(bodysp);
} else {
// Wrapper-derived register candidates do not have a parent
// always_ff body to splice into. Sample coverage first, then save
// the current state for the next clock tick; this survives cell
// boundary scheduling where the real flop update lives elsewhere.
updatePrevAfterPost = true;
prevVscp->varp()->setIgnorePostRead();
}
for (const V3GraphVertex& vtx : graph.vertices()) {
const FsmVertex* const vertexp = vtx.as<FsmVertex>();
@ -1960,6 +2236,10 @@ class FsmLowerVisitor final {
covPostp->addStmtsp(new AstIf{flp, guardp, new AstCoverInc{flp, declp}});
}
}
if (updatePrevAfterPost) {
covPostp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, prevVscp, VAccess::WRITE},
new AstVarRef{flp, stateVscp, VAccess::READ}});
}
AstSenTree* const sentreep = buildSenTree(flp, graph.senses());
AstActive* const activep = new AstActive{flp, "fsm-coverage", sentreep};
@ -1979,8 +2259,48 @@ public:
}
};
// Wrapper FSM support has two architectural paths. If V3Inline removes the
// wrapper, the main detector will later see an ordinary parent-scope always_ff;
// this pre-inline visitor leaves just enough provenance on the q-side state
// variable for that direct path to accept wrapper-specific normalized shapes.
// If the wrapper survives, this marker is harmless and the cell-path detector
// builds a register candidate from the instance itself.
class FsmWrapperMarkerVisitor final : public VNVisitor {
static AstPin* findPin(AstCell* cellp, const string& name) {
for (AstPin* pinp = cellp->pinsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
if (pinp->name() == name) return pinp;
}
return nullptr;
}
void visit(AstCell* cellp) override {
if (const V3Control::FsmRegisterWrapper* const descp = fsmRegisterWrapperDesc(cellp)) {
AstPin* const qp = findPin(cellp, descp->q);
if (qp && VN_IS(qp->exprp(), VarRef)) {
AstVarRef* const qrefp = VN_AS(qp->exprp(), VarRef);
// The q-side parent variable is the point where the wrapper
// abstraction collapses into direct RTL after inlining.
// Marking only that variable keeps the provenance narrow:
// transition detection still has to prove the d/q FSM pair.
qrefp->varp()->attrFsmRegisterWrapper(true);
}
}
iterateChildren(cellp);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
explicit FsmWrapperMarkerVisitor(AstNetlist* rootp) { iterate(rootp); }
};
} // namespace
void V3FsmDetect::markWrapperStateVars(AstNetlist* rootp) {
UINFO(2, __FUNCTION__ << ":");
FsmWrapperMarkerVisitor marker{rootp};
}
void V3FsmDetect::detect(AstNetlist* rootp) {
UINFO(2, __FUNCTION__ << ":");
FsmState state;

View File

@ -24,6 +24,10 @@ class AstNetlist;
class V3FsmDetect final {
public:
// Mark parent-scope state variables connected through configured wrapper
// instances before inlining lowers those instances away.
static void markWrapperStateVars(AstNetlist* rootp) VL_MT_DISABLED;
// Detect FSMs while the original clocked/case structure is still visible,
// then immediately lower the recovered graphs into concrete coverage
// instrumentation as a second local phase in the same pass.

View File

@ -39,6 +39,7 @@
#include "V3PreShell.h"
#include "V3Stats.h"
#include <cctype>
#include <sstream>
VL_DEFINE_DEBUG_FUNCTIONS;

View File

@ -316,6 +316,7 @@ static void process() {
// Module inlining
// Cannot remove dead variables after this, as alias information for final
// V3Scope's V3LinkDot is in the AstVar.
if (v3Global.opt.coverageFsm()) V3FsmDetect::markWrapperStateVars(v3Global.rootp());
if (v3Global.opt.fInline()) {
V3Inline::inlineAll(v3Global.rootp());
V3LinkDot::linkDotArrayed(v3Global.rootp()); // Cleanup as made new modules

View File

@ -136,6 +136,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
"coverage_off" { FL; return yVLT_COVERAGE_OFF; }
"coverage_on" { FL; return yVLT_COVERAGE_ON; }
"forceable" { FL; return yVLT_FORCEABLE; }
"fsm_register_wrapper" { FL; return yVLT_FSM_REGISTER_WRAPPER; }
"full_case" { FL; return yVLT_FULL_CASE; }
"hier_block" { FL; return yVLT_HIER_BLOCK; }
"hier_params" { FL; return yVLT_HIER_PARAMS; }
@ -166,6 +167,8 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
-?"-block" { FL; return yVLT_D_BLOCK; }
-?"-contents" { FL; return yVLT_D_CONTENTS; }
-?"-cost" { FL; return yVLT_D_COST; }
-?"-clock" { FL; return yVLT_D_CLOCK; }
-?"-d" { FL; return yVLT_D_D; }
-?"-file" { FL; return yVLT_D_FILE; }
-?"-function" { FL; return yVLT_D_FUNCTION; }
-?"-hier-dpi" { FL; return yVLT_D_HIER_DPI; }
@ -178,6 +181,9 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
-?"-param" { FL; return yVLT_D_PARAM; }
-?"-port" { FL; return yVLT_D_PORT; }
-?"-rule" { FL; return yVLT_D_RULE; }
-?"-q" { FL; return yVLT_D_Q; }
-?"-reset" { FL; return yVLT_D_RESET; }
-?"-reset_value" { FL; return yVLT_D_RESET_VALUE; }
-?"-scope" { FL; return yVLT_D_SCOPE; }
-?"-task" { FL; return yVLT_D_TASK; }
-?"-var" { FL; return yVLT_D_VAR; }

View File

@ -245,6 +245,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yVLT_COVERAGE_OFF "coverage_off"
%token<fl> yVLT_COVERAGE_ON "coverage_on"
%token<fl> yVLT_FORCEABLE "forceable"
%token<fl> yVLT_FSM_REGISTER_WRAPPER "fsm_register_wrapper"
%token<fl> yVLT_FULL_CASE "full_case"
%token<fl> yVLT_HIER_BLOCK "hier_block"
%token<fl> yVLT_HIER_PARAMS "hier_params"
@ -275,6 +276,8 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yVLT_D_BLOCK "--block"
%token<fl> yVLT_D_CONTENTS "--contents"
%token<fl> yVLT_D_COST "--cost"
%token<fl> yVLT_D_CLOCK "--clock"
%token<fl> yVLT_D_D "--d"
%token<fl> yVLT_D_FILE "--file"
%token<fl> yVLT_D_FUNCTION "--function"
%token<fl> yVLT_D_HIER_DPI "--hier-dpi"
@ -287,6 +290,9 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
%token<fl> yVLT_D_PARAM "--param"
%token<fl> yVLT_D_PORT "--port"
%token<fl> yVLT_D_RULE "--rule"
%token<fl> yVLT_D_Q "--q"
%token<fl> yVLT_D_RESET "--reset"
%token<fl> yVLT_D_RESET_VALUE "--reset_value"
%token<fl> yVLT_D_SCOPE "--scope"
%token<fl> yVLT_D_TASK "--task"
%token<fl> yVLT_D_VAR "--var"
@ -8291,6 +8297,8 @@ vltItem:
{ V3Control::addProfileData($<fl>1, *$2, $3->toUQuad()); }
| yVLT_PROFILE_DATA vltDModel vltDMtask vltDCost
{ V3Control::addProfileData($<fl>1, *$2, *$3, $4->toUQuad()); }
| yVLT_FSM_REGISTER_WRAPPER vltDModule vltDFsmD vltDFsmQ vltDFsmClock vltDFsmResetE vltDFsmResetValueE
{ V3Control::addFsmRegisterWrapper($<fl>1, *$2, *$3, *$4, *$5, *$6, *$7); }
| yVLT_VERILATOR_LIB vltDModule
{ V3Control::addModulePragma(*$2, VPragmaType::VERILATOR_LIB); }
;
@ -8331,6 +8339,28 @@ vltDFile<strp>: // --file <arg>
yVLT_D_FILE str { $$ = $2; }
;
vltDFsmClock<strp>: // --clock <arg>
yVLT_D_CLOCK str { $$ = $2; }
;
vltDFsmD<strp>: // --d <arg>
yVLT_D_D str { $$ = $2; }
;
vltDFsmQ<strp>: // --q <arg>
yVLT_D_Q str { $$ = $2; }
;
vltDFsmResetE<strp>: // [--reset <arg>]
/* empty */ { static string empty; $$ = &empty; }
| yVLT_D_RESET str { $$ = $2; }
;
vltDFsmResetValueE<strp>: // [--reset_value <arg>]
/* empty */ { static string empty; $$ = &empty; }
| yVLT_D_RESET_VALUE str { $$ = $2; }
;
vltDHierDpi<strp>: // --hier-dpi <arg>
yVLT_D_HIER_DPI str { $$ = $2; }
;

View File

@ -4,7 +4,7 @@
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module fsm_if_mixed_vars_bad (
input logic clk
);
@ -13,20 +13,20 @@
S1 = 2'd1,
S2 = 2'd2
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t other_q;
state_t state_d;
always_comb begin
state_d = state_q;
if (state_q == S0) state_d = S1;
else if (other_q == S1) state_d = S2;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_one_branch_bad (
input logic clk
);
@ -34,18 +34,18 @@
S0 = 2'd0,
S1 = 2'd1
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
if (state_q == S0) state_d = S1;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_duplicate_bad (
input logic clk
);
@ -53,19 +53,19 @@
S0 = 2'd0,
S1 = 2'd1
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
if (state_q == S0) state_d = S1;
else if (state_q == S0) state_d = S0;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_two_comparisons_bad (
input logic clk,
input logic start
@ -75,19 +75,19 @@
S1 = 2'd1,
S2 = 2'd2
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
if ((state_q == S0) && (state_q == S1)) state_d = S2;
else if (state_q == S1) state_d = S0;
end
always_ff @(posedge clk) state_q <= start ? state_d : state_q;
endmodule
module fsm_if_or_bad (
input logic clk
);
@ -96,19 +96,19 @@
S1 = 2'd1,
S2 = 2'd2
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
if ((state_q == S0) || (state_q == S1)) state_d = S2;
else if (state_q == S2) state_d = S0;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_alias_guard_bad (
input logic clk,
input logic start
@ -118,21 +118,21 @@
S1 = 2'd1,
S2 = 2'd2
} state_t;
logic idle_state;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
idle_state = (state_q == S0) && start;
if (idle_state) state_d = S1;
else if (state_q == S1) state_d = S2;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_ambiguous_alias_bad (
input logic clk
);
@ -141,11 +141,11 @@
S1 = 2'd1,
S2 = 2'd2
} state_t;
logic idle_state;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
idle_state = (state_q == S0);
@ -154,10 +154,10 @@
if (idle_state) state_d = S2;
else if (state_q == S2) state_d = S0;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_missing_default_bad (
input logic clk
);
@ -166,19 +166,19 @@
S1 = 2'd1,
S2 = 2'd2
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = S0;
if (state_d == S0) state_d = S1;
else if (state_d == S1) state_d = S2;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_no_assign_bad (
input logic clk
);
@ -186,21 +186,21 @@
S0 = 2'd0,
S1 = 2'd1
} state_t;
logic flag;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
flag = 1'b0;
state_d = state_q;
if (state_q == S0) flag = 1'b1;
else if (state_q == S1) state_d = S0;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_nonvar_compare_bad (
input logic clk
);
@ -208,10 +208,10 @@
S0 = 2'd0,
S1 = 2'd1
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
%000003 if ((state_q + 2'd0) == S0) state_d = S1;
@ -222,10 +222,10 @@
%000003 // [fsm_state t.nonvar_compare_u.state_q::S1]
else if (state_q == S1) state_d = S0;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_var_rhs_compare_bad (
input logic clk
);
@ -233,19 +233,19 @@
S0 = 2'd0,
S1 = 2'd1
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
if (state_q == state_d) state_d = S1;
else if (state_q == S1) state_d = S0;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_var_target_bad (
input logic clk,
input logic [1:0] dyn
@ -255,19 +255,19 @@
S1 = 2'd1,
S2 = 2'd2
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
if (state_q == S0) state_d = state_t'(dyn);
else if (state_q == S1) state_d = S2;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_alias_other_state_bad (
input logic clk
);
@ -275,12 +275,12 @@
S0 = 2'd0,
S1 = 2'd1
} state_t;
logic idle_state;
state_t state_q /*verilator fsm_state*/;
state_t other_q;
state_t state_d;
always_comb begin
state_d = state_q;
idle_state = (state_q == S0);
@ -288,10 +288,10 @@
if (idle_state) state_d = S1;
else if (state_q == S1) state_d = S0;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_bit_or_bad (
input logic clk,
input logic start
@ -301,19 +301,19 @@
S1 = 2'd1,
S2 = 2'd2
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
if ((state_q == S0) | start) state_d = S1;
else if (state_q == S1) state_d = S2;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_if_reduction_bad (
input logic clk
);
@ -322,10 +322,10 @@
S1 = 2'd1,
S2 = 2'd2
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
if (&state_q) state_d = S1;
@ -335,26 +335,45 @@
if (^state_q) state_d = S0;
else if (state_q == S0) state_d = S1;
end
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_direct_active_low_dynamic_reset_bad (
input logic clk,
input logic rst_n,
input logic [1:0] dyn_reset
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
if (state_q == S0) state_d = S1;
else state_d = S0;
end
always_ff @(posedge clk or negedge rst_n) begin
state_q <= rst_n ? state_d : state_t'(dyn_reset);
end
endmodule
module t (
input logic clk
);
typedef enum logic [2:0] {
S0 = 3'd0,
S1 = 3'd1,
S2 = 3'd2
} state_t;
int cyc;
logic start;
logic side;
logic [2:0] dyn_case;
state_t state /*verilator fsm_reset_arc*/;
fsm_if_mixed_vars_bad mixed_vars_u (.clk(clk));
fsm_if_one_branch_bad one_branch_u (.clk(clk));
fsm_if_duplicate_bad duplicate_u (.clk(clk));
@ -370,14 +389,16 @@
fsm_if_alias_other_state_bad alias_other_state_u (.clk(clk));
fsm_if_bit_or_bad bit_or_u (.clk(clk), .start(start));
fsm_if_reduction_bad reduction_u (.clk(clk));
fsm_direct_active_low_dynamic_reset_bad active_low_dynamic_reset_u (
.clk(clk), .rst_n(cyc != 0), .dyn_reset(dyn_case[1:0]));
initial begin
cyc = 0;
start = 1'b0;
side = 1'b0;
dyn_case = 3'd7;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) side <= 1'b1;
@ -387,7 +408,7 @@
$finish;
end
end
// The grouped S0/dyn_case arm keeps the supported S0 baseline while the
// non-constant case item is skipped. The S1 and default arms are
// deliberately unsupported extractor shapes: one has two meaningful
@ -416,6 +437,6 @@
endcase
end
end
endmodule

View File

@ -32,6 +32,13 @@ test.run(cmd=[
],
verilator_run=True)
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
annotated_filename = test.obj_dir + "/annotated/" + test.name + ".v"
normalized_filename = test.obj_dir + "/annotated/" + test.name + ".normalized.v"
with open(annotated_filename, encoding="utf-8") as in_file:
with open(normalized_filename, "w", encoding="utf-8") as out_file:
for line in in_file:
out_file.write(line.rstrip() + "\n")
test.files_identical(normalized_filename, test.golden_filename)
test.passes()

View File

@ -332,7 +332,26 @@ module fsm_if_reduction_bad (
always_ff @(posedge clk) state_q <= state_d;
endmodule
module fsm_direct_active_low_dynamic_reset_bad (
input logic clk,
input logic rst_n,
input logic [1:0] dyn_reset
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1
} state_t;
state_t state_q /*verilator fsm_state*/;
state_t state_d;
always_comb begin
state_d = state_q;
if (state_q == S0) state_d = S1;
else state_d = S0;
end
always_ff @(posedge clk or negedge rst_n) begin
state_q <= rst_n ? state_d : state_t'(dyn_reset);
end
endmodule
module t (
input logic clk
);
@ -364,6 +383,8 @@ module t (
fsm_if_alias_other_state_bad alias_other_state_u (.clk(clk));
fsm_if_bit_or_bad bit_or_u (.clk(clk), .start(start));
fsm_if_reduction_bad reduction_u (.clk(clk));
fsm_direct_active_low_dynamic_reset_bad active_low_dynamic_reset_u (
.clk(clk), .rst_n(cyc != 0), .dyn_reset(dyn_case[1:0]));
initial begin
cyc = 0;

View File

@ -0,0 +1,522 @@
// // verilator_coverage annotation
// DESCRIPTION: Verilator: FSM coverage for fsm_register_wrapper state register wrappers
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module my_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk_i,
input logic rst_ni,
input logic [Width-1:0] state_i,
output logic [Width-1:0] state_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) state_o <= ResetValue;
else state_o <= state_i;
end
endmodule
module prim_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk_i,
input logic rst_ni,
input logic [Width-1:0] d_i,
output logic [Width-1:0] q_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) q_o <= ResetValue;
else q_o <= d_i;
end
endmodule
module prim_sparse_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk_i,
input logic rst_ni,
input logic [Width-1:0] state_i,
output logic [Width-1:0] state_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) state_o <= ResetValue;
else state_o <= state_i;
end
endmodule
module odd_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst_n,
input logic [Width-1:0] din,
output logic [Width-1:0] dout
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) dout <= ResetValue;
else dout <= din;
end
endmodule
module ambiguous_fsm_flop (
input logic clk_i,
input logic rst_ni,
input logic [1:0] state_i,
input logic [1:0] d_i,
output logic [1:0] state_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) state_o <= 2'd0;
else state_o <= state_i ^ d_i;
end
endmodule
module fsm_auto (
input logic clk,
input logic rst_n,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.auto_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.auto_u.state_q::S0->S0]
%000001 // [fsm_arc t.auto_u.state_q::S0->S1]
%000001 // [fsm_arc t.auto_u.state_q::S1->S2]
%000001 // [fsm_state t.auto_u.state_q::S0]
%000001 // [fsm_state t.auto_u.state_q::S1]
%000001 // [fsm_state t.auto_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_noargs_hint (
input logic clk,
input logic rst_n,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.noargs_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.noargs_u.state_q::S0->S0]
%000001 // [fsm_arc t.noargs_u.state_q::S0->S1]
%000001 // [fsm_arc t.noargs_u.state_q::S1->S2]
%000001 // [fsm_state t.noargs_u.state_q::S0]
%000001 // [fsm_state t.noargs_u.state_q::S1]
%000001 // [fsm_state t.noargs_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_prim (
input logic clk,
input logic rst_n,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.prim_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.prim_u.state_q::S0->S0]
%000001 // [fsm_arc t.prim_u.state_q::S0->S1]
%000001 // [fsm_arc t.prim_u.state_q::S1->S2]
%000001 // [fsm_state t.prim_u.state_q::S0]
%000001 // [fsm_state t.prim_u.state_q::S1]
%000001 // [fsm_state t.prim_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.d_i(state_d),
.q_o(state_q)
);
endmodule
module fsm_sparse_prim (
input logic clk,
input logic rst_n,
input logic start
);
typedef enum logic [5:0] {
S0 = 6'b00_0001,
S1 = 6'b10_0100,
S2 = 6'b11_0010
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
%000005 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.sparse_prim_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000005 // [fsm_arc t.sparse_prim_u.state_q::S0->S0]
%000001 // [fsm_arc t.sparse_prim_u.state_q::S0->S1]
%000001 // [fsm_arc t.sparse_prim_u.state_q::S1->S2]
%000002 // [fsm_state t.sparse_prim_u.state_q::S0]
%000001 // [fsm_state t.sparse_prim_u.state_q::S1]
%000001 // [fsm_state t.sparse_prim_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_sparse_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_ifchain (
input logic clk,
input logic rst_n,
input logic start
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
if (state_q == S0) begin
state_d = start ? S1 : S0;
end
else if (state_q == S1) begin
state_d = S2;
end
else begin
state_d = S0;
end
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_wide_sparse (
input logic clk,
input logic rst_n,
input logic start
);
typedef enum logic [39:0] {
S0 = 40'h0000_0000_01,
S1 = 40'h8000_0000_02,
S2 = 40'hffff_0000_03
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
%000005 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.wide_sparse_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000005 // [fsm_arc t.wide_sparse_u.state_q::S0->S0]
%000001 // [fsm_arc t.wide_sparse_u.state_q::S0->S1]
%000001 // [fsm_arc t.wide_sparse_u.state_q::S1->S2]
%000002 // [fsm_state t.wide_sparse_u.state_q::S0]
%000001 // [fsm_state t.wide_sparse_u.state_q::S1]
%000001 // [fsm_state t.wide_sparse_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_annotated (
input logic clk,
input logic rst_n,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.annotated_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.annotated_u.state_q::S0->S0]
%000001 // [fsm_arc t.annotated_u.state_q::S0->S1]
%000001 // [fsm_arc t.annotated_u.state_q::S1->S2]
%000001 // [fsm_state t.annotated_u.state_q::S0]
%000001 // [fsm_state t.annotated_u.state_q::S1]
%000001 // [fsm_state t.annotated_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
odd_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.rst_n(rst_n),
.din(state_d),
.dout(state_q)
);
endmodule
module fsm_non_simple (
input logic clk,
input logic rst_n,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.non_simple_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.non_simple_u.state_q::S0->S0]
%000001 // [fsm_arc t.non_simple_u.state_q::S0->S1]
%000001 // [fsm_arc t.non_simple_u.state_q::S1->S2]
%000001 // [fsm_state t.non_simple_u.state_q::S0]
%000001 // [fsm_state t.non_simple_u.state_q::S1]
%000001 // [fsm_state t.non_simple_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d[1:0]),
.state_o(state_q)
);
endmodule
module fsm_ambiguous (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
ambiguous_fsm_flop u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.d_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_ignored (
input logic clk,
input logic rst_n,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.ignored_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.ignored_u.state_q::S0->S0]
%000001 // [fsm_arc t.ignored_u.state_q::S0->S1]
%000001 // [fsm_arc t.ignored_u.state_q::S1->S2]
%000001 // [fsm_state t.ignored_u.state_q::S0]
%000001 // [fsm_state t.ignored_u.state_q::S1]
%000001 // [fsm_state t.ignored_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
odd_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.rst_n(rst_n),
.din(state_d),
.dout(state_q)
);
endmodule
module t (
input logic clk
);
logic rst_n;
logic start;
integer cyc;
initial begin
rst_n = 1'b0;
start = 1'b0;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst_n <= 1'b1;
if (cyc == 2) start <= 1'b1;
if (cyc == 3) start <= 1'b0;
if (cyc == 8) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
fsm_auto auto_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_noargs_hint noargs_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_prim prim_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_sparse_prim sparse_prim_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_ifchain ifchain_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_wide_sparse wide_sparse_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_annotated annotated_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_non_simple non_simple_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_ambiguous ambiguous_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_ignored ignored_u (.clk(clk), .rst_n(rst_n), .start(start));
endmodule

View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage for fsm_register_wrapper state register wrappers
#
# 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 t/t_fsm_register_wrapper.vlt'])
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.passes()

View File

@ -0,0 +1,456 @@
// DESCRIPTION: Verilator: FSM coverage for fsm_register_wrapper state register wrappers
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module my_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk_i,
input logic rst_ni,
input logic [Width-1:0] state_i,
output logic [Width-1:0] state_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) state_o <= ResetValue;
else state_o <= state_i;
end
endmodule
module prim_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk_i,
input logic rst_ni,
input logic [Width-1:0] d_i,
output logic [Width-1:0] q_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) q_o <= ResetValue;
else q_o <= d_i;
end
endmodule
module prim_sparse_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk_i,
input logic rst_ni,
input logic [Width-1:0] state_i,
output logic [Width-1:0] state_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) state_o <= ResetValue;
else state_o <= state_i;
end
endmodule
module odd_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst_n,
input logic [Width-1:0] din,
output logic [Width-1:0] dout
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) dout <= ResetValue;
else dout <= din;
end
endmodule
module ambiguous_fsm_flop (
input logic clk_i,
input logic rst_ni,
input logic [1:0] state_i,
input logic [1:0] d_i,
output logic [1:0] state_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) state_o <= 2'd0;
else state_o <= state_i ^ d_i;
end
endmodule
module fsm_auto (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_noargs_hint (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_prim (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.d_i(state_d),
.q_o(state_q)
);
endmodule
module fsm_sparse_prim (
input logic clk,
input logic rst_n,
input logic start
);
typedef enum logic [5:0] {
S0 = 6'b00_0001,
S1 = 6'b10_0100,
S2 = 6'b11_0010
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_sparse_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_ifchain (
input logic clk,
input logic rst_n,
input logic start
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
if (state_q == S0) begin
state_d = start ? S1 : S0;
end
else if (state_q == S1) begin
state_d = S2;
end
else begin
state_d = S0;
end
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_wide_sparse (
input logic clk,
input logic rst_n,
input logic start
);
typedef enum logic [39:0] {
S0 = 40'h0000_0000_01,
S1 = 40'h8000_0000_02,
S2 = 40'hffff_0000_03
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_annotated (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
odd_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.rst_n(rst_n),
.din(state_d),
.dout(state_q)
);
endmodule
module fsm_non_simple (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d[1:0]),
.state_o(state_q)
);
endmodule
module fsm_ambiguous (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
ambiguous_fsm_flop u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.d_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_ignored (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
odd_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.rst_n(rst_n),
.din(state_d),
.dout(state_q)
);
endmodule
module t (
input logic clk
);
logic rst_n;
logic start;
integer cyc;
initial begin
rst_n = 1'b0;
start = 1'b0;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst_n <= 1'b1;
if (cyc == 2) start <= 1'b1;
if (cyc == 3) start <= 1'b0;
if (cyc == 8) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
fsm_auto auto_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_noargs_hint noargs_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_prim prim_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_sparse_prim sparse_prim_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_ifchain ifchain_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_wide_sparse wide_sparse_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_annotated annotated_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_non_simple non_simple_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_ambiguous ambiguous_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_ignored ignored_u (.clk(clk), .rst_n(rst_n), .start(start));
endmodule

View File

@ -0,0 +1,12 @@
// DESCRIPTION: Verilator: FSM register wrapper VLT descriptors
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
fsm_register_wrapper -module "my_fsm_flop" -d "state_i" -q "state_o" -clock "clk_i" -reset "rst_ni" -reset_value "ResetValue"
fsm_register_wrapper -module "prim_flop" -d "d_i" -q "q_o" -clock "clk_i" -reset "rst_ni" -reset_value "ResetValue"
fsm_register_wrapper -module "prim_sparse_fsm_flop" -d "state_i" -q "state_o" -clock "clk_i" -reset "rst_ni" -reset_value "ResetValue"
fsm_register_wrapper -module "odd_fsm_flop" -d "din" -q "dout" -clock "clk" -reset "rst_n" -reset_value "ResetValue"

View File

@ -0,0 +1,850 @@
// // verilator_coverage annotation
// DESCRIPTION: Verilator: FSM coverage for non-inlined fsm_register_wrapper state register wrappers
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module my_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk_i,
input logic rst_ni,
input logic [Width-1:0] state_i,
output logic [Width-1:0] state_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) state_o <= ResetValue;
else state_o <= state_i;
end
endmodule
module prim_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk_i,
input logic rst_ni,
input logic [Width-1:0] d_i,
output logic [Width-1:0] q_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) q_o <= ResetValue;
else q_o <= d_i;
end
endmodule
module prim_sparse_fsm_flop #(
parameter int Width = 1,
parameter type StateEnumT = logic [Width-1:0],
parameter logic [Width-1:0] ResetValue = '0,
parameter bit EnableAlertTriggerSVA = 1
) (
input logic clk_i,
input logic rst_ni,
input StateEnumT state_i,
output StateEnumT state_o
);
logic unused_alert_sva;
logic [Width-1:0] state_raw;
prim_flop #(
.Width(Width),
.ResetValue(ResetValue)
) u_state_flop (
.clk_i(clk_i),
.rst_ni(rst_ni),
.d_i(state_i),
.q_o(state_raw)
);
assign state_o = StateEnumT'(state_raw);
assign unused_alert_sva = EnableAlertTriggerSVA;
endmodule
module active_high_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk or posedge rst) begin
if (rst) q <= ResetValue;
else q <= d;
end
endmodule
module active_high_rst_i_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst_i,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk or posedge rst_i) begin
if (rst_i) q <= ResetValue;
else q <= d;
end
endmodule
module custom_reset_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic my_reset,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
logic [Width-1:0] d_shadow;
always_comb begin
d_shadow = d;
end
always_ff @(posedge clk or negedge my_reset) begin
if (!my_reset) q <= ResetValue;
else q <= d_shadow;
end
endmodule
module custom_reset_active_high_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic my_reset,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk or posedge my_reset) begin
if (my_reset) q <= ResetValue;
else q <= d;
end
endmodule
module no_reset_fsm_flop #(
parameter int Width = 1
) (
input logic clk,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk) begin
q <= d;
end
endmodule
module reset_no_param_fsm_flop #(
parameter int Width = 1
) (
input logic clk,
input logic rst_n,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= '0;
else q <= d;
end
endmodule
module sync_reset_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic my_reset,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk) begin
if (my_reset) q <= ResetValue;
else q <= d;
end
endmodule
module ambiguous_fsm_flop (
input logic clk_i,
input logic rst_ni,
input logic [1:0] state_i,
input logic [1:0] d_i,
output logic [1:0] state_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) state_o <= 2'd0;
else state_o <= state_i ^ d_i;
end
endmodule
module fsm_auto (
input logic clk,
input logic rst_n,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.auto_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.auto_u.state_q::S0->S0]
%000001 // [fsm_arc t.auto_u.state_q::S0->S1]
%000001 // [fsm_arc t.auto_u.state_q::S1->S2]
%000001 // [fsm_state t.auto_u.state_q::S0]
%000001 // [fsm_state t.auto_u.state_q::S1]
%000001 // [fsm_state t.auto_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_prim (
input logic clk,
input logic rst_n,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.prim_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.prim_u.state_q::S0->S0]
%000001 // [fsm_arc t.prim_u.state_q::S0->S1]
%000001 // [fsm_arc t.prim_u.state_q::S1->S2]
%000001 // [fsm_state t.prim_u.state_q::S0]
%000001 // [fsm_state t.prim_u.state_q::S1]
%000001 // [fsm_state t.prim_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.d_i(state_d),
.q_o(state_q)
);
endmodule
module fsm_prim_override (
input logic clk,
input logic rst_n,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.prim_override_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.prim_override_u.state_q::S0->S0]
%000001 // [fsm_arc t.prim_override_u.state_q::S0->S1]
%000001 // [fsm_arc t.prim_override_u.state_q::S1->S2]
%000001 // [fsm_state t.prim_override_u.state_q::S0]
%000001 // [fsm_state t.prim_override_u.state_q::S1]
%000001 // [fsm_state t.prim_override_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.d_i(state_d),
.q_o(state_q)
);
endmodule
module fsm_sparse_prim (
input logic clk,
input logic rst_n,
input logic start
);
typedef enum logic [5:0] {
S0 = 6'b00_0001,
S1 = 6'b10_0100,
S2 = 6'b11_0010
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
%000005 unique case (state_q)
// [FSM coverage]
%000005 // [fsm_arc t.sparse_prim_u.state_q::S0->S0]
%000001 // [fsm_arc t.sparse_prim_u.state_q::S0->S1]
%000001 // [fsm_arc t.sparse_prim_u.state_q::S1->S2]
%000002 // [fsm_state t.sparse_prim_u.state_q::S0]
%000001 // [fsm_state t.sparse_prim_u.state_q::S1]
%000001 // [fsm_state t.sparse_prim_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_sparse_fsm_flop #(
.StateEnumT(state_t),
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_sparse_prim_override (
input logic clk,
input logic rst_n,
input logic start
);
typedef enum logic [5:0] {
S0 = 6'b00_0001,
S1 = 6'b10_0100,
S2 = 6'b11_0010
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
%000005 unique case (state_q)
// [FSM coverage]
%000005 // [fsm_arc t.sparse_prim_override_u.state_q::S0->S0]
%000001 // [fsm_arc t.sparse_prim_override_u.state_q::S0->S1]
%000001 // [fsm_arc t.sparse_prim_override_u.state_q::S1->S2]
%000002 // [fsm_state t.sparse_prim_override_u.state_q::S0]
%000001 // [fsm_state t.sparse_prim_override_u.state_q::S1]
%000001 // [fsm_state t.sparse_prim_override_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_sparse_fsm_flop #(
.StateEnumT(state_t),
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_active_high (
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;
%000007 unique case (state_q)
// [FSM coverage]
%000002 // [fsm_arc t.active_high_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000007 // [fsm_arc t.active_high_u.state_q::S0->S0]
%000001 // [fsm_arc t.active_high_u.state_q::S0->S1]
%000001 // [fsm_arc t.active_high_u.state_q::S1->S2]
%000001 // [fsm_state t.active_high_u.state_q::S0]
%000001 // [fsm_state t.active_high_u.state_q::S1]
%000001 // [fsm_state t.active_high_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
active_high_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.rst(rst),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_active_high_rst_i (
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;
%000007 unique case (state_q)
// [FSM coverage]
%000002 // [fsm_arc t.active_high_rst_i_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000007 // [fsm_arc t.active_high_rst_i_u.state_q::S0->S0]
%000001 // [fsm_arc t.active_high_rst_i_u.state_q::S0->S1]
%000001 // [fsm_arc t.active_high_rst_i_u.state_q::S1->S2]
%000001 // [fsm_state t.active_high_rst_i_u.state_q::S0]
%000001 // [fsm_state t.active_high_rst_i_u.state_q::S1]
%000001 // [fsm_state t.active_high_rst_i_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
active_high_rst_i_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.rst_i(rst),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_no_reset (
input logic clk,
input logic start
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
%000006 unique case (state_q)
// [FSM coverage]
%000006 // [fsm_arc t.no_reset_u.state_q::S0->S0]
%000001 // [fsm_arc t.no_reset_u.state_q::S0->S1]
%000001 // [fsm_arc t.no_reset_u.state_q::S1->S2]
%000001 // [fsm_state t.no_reset_u.state_q::S0]
%000001 // [fsm_state t.no_reset_u.state_q::S1]
%000001 // [fsm_state t.no_reset_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
no_reset_fsm_flop #(
.Width($bits(state_t))
) u_state_regs (
.clk(clk),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_reset_no_param (
input logic clk,
input logic rst_n,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000006 // [fsm_arc t.reset_no_param_u.state_q::S0->S0]
%000001 // [fsm_arc t.reset_no_param_u.state_q::S0->S1]
%000001 // [fsm_arc t.reset_no_param_u.state_q::S1->S2]
%000001 // [fsm_state t.reset_no_param_u.state_q::S0]
%000001 // [fsm_state t.reset_no_param_u.state_q::S1]
%000001 // [fsm_state t.reset_no_param_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
reset_no_param_fsm_flop #(
.Width($bits(state_t))
) u_state_regs (
.clk(clk),
.rst_n(rst_n),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_sync_reset_unknown_polarity (
input logic clk,
input logic parent_reset,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000006 // [fsm_arc t.sync_reset_u.state_q::S0->S0]
%000001 // [fsm_arc t.sync_reset_u.state_q::S0->S1]
%000001 // [fsm_arc t.sync_reset_u.state_q::S1->S2]
%000001 // [fsm_state t.sync_reset_u.state_q::S0]
%000001 // [fsm_state t.sync_reset_u.state_q::S1]
%000001 // [fsm_state t.sync_reset_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
sync_reset_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.my_reset(parent_reset),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_custom_reset_parent_polarity (
input logic clk,
input logic parent_reset,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.custom_reset_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.custom_reset_u.state_q::S0->S0]
%000001 // [fsm_arc t.custom_reset_u.state_q::S0->S1]
%000001 // [fsm_arc t.custom_reset_u.state_q::S1->S2]
%000001 // [fsm_state t.custom_reset_u.state_q::S0]
%000001 // [fsm_state t.custom_reset_u.state_q::S1]
%000001 // [fsm_state t.custom_reset_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
custom_reset_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.my_reset(parent_reset),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_custom_reset_active_high (
input logic clk,
input logic parent_reset,
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;
%000007 unique case (state_q)
// [FSM coverage]
%000002 // [fsm_arc t.custom_reset_active_high_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000007 // [fsm_arc t.custom_reset_active_high_u.state_q::S0->S0]
%000001 // [fsm_arc t.custom_reset_active_high_u.state_q::S0->S1]
%000001 // [fsm_arc t.custom_reset_active_high_u.state_q::S1->S2]
%000001 // [fsm_state t.custom_reset_active_high_u.state_q::S0]
%000001 // [fsm_state t.custom_reset_active_high_u.state_q::S1]
%000001 // [fsm_state t.custom_reset_active_high_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
custom_reset_active_high_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.my_reset(parent_reset),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_ambiguous_ignored (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
ambiguous_fsm_flop u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.d_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_missing_reset_param (
input logic clk,
input logic rst_n,
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.missing_reset_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.missing_reset_u.state_q::S0->S0]
%000001 // [fsm_arc t.missing_reset_u.state_q::S0->S1]
%000001 // [fsm_arc t.missing_reset_u.state_q::S1->S2]
%000001 // [fsm_state t.missing_reset_u.state_q::S0]
%000001 // [fsm_state t.missing_reset_u.state_q::S1]
%000001 // [fsm_state t.missing_reset_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_competing_direct (
input logic clk,
input logic rst_n,
input logic start
);
/* verilator lint_off BLKANDNBLK */
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;
%000006 unique case (state_q)
// [FSM coverage]
%000001 // [fsm_arc t.competing_u.state_q::ANY->S0[reset]] [reset arc, excluded from %]
%000006 // [fsm_arc t.competing_u.state_q::S0->S0]
%000001 // [fsm_arc t.competing_u.state_q::S0->S1]
%000001 // [fsm_arc t.competing_u.state_q::S1->S2]
%000001 // [fsm_state t.competing_u.state_q::S0]
%000001 // [fsm_state t.competing_u.state_q::S1]
%000001 // [fsm_state t.competing_u.state_q::S2]
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) state_q <= S0;
else state_q <= state_d;
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
/* verilator lint_on BLKANDNBLK */
endmodule
module t (
input logic clk
);
logic rst_n;
logic rst;
logic start;
integer cyc;
initial begin
rst_n = 1'b0;
rst = 1'b1;
start = 1'b0;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst_n <= 1'b1;
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
fsm_auto auto_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_prim prim_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_prim_override prim_override_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_sparse_prim sparse_prim_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_sparse_prim_override sparse_prim_override_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_active_high active_high_u (.clk(clk), .rst(rst), .start(start));
fsm_active_high_rst_i active_high_rst_i_u (.clk(clk), .rst(rst), .start(start));
fsm_no_reset no_reset_u (.clk(clk), .start(start));
fsm_reset_no_param reset_no_param_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_sync_reset_unknown_polarity sync_reset_u (.clk(clk), .parent_reset(rst), .start(start));
fsm_custom_reset_parent_polarity custom_reset_u (.clk(clk), .parent_reset(rst_n), .start(start));
fsm_custom_reset_active_high custom_reset_active_high_u (
.clk(clk), .parent_reset(rst), .start(start));
fsm_ambiguous_ignored ambiguous_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_missing_reset_param missing_reset_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_competing_direct competing_u (.clk(clk), .rst_n(rst_n), .start(start));
endmodule

View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM coverage for non-inlined fsm_register_wrapper state register wrappers
#
# 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 -fno-inline t/t_fsm_register_wrapper_noinline.vlt'])
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.passes()

View File

@ -0,0 +1,741 @@
// DESCRIPTION: Verilator: FSM coverage for non-inlined fsm_register_wrapper state register wrappers
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module my_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk_i,
input logic rst_ni,
input logic [Width-1:0] state_i,
output logic [Width-1:0] state_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) state_o <= ResetValue;
else state_o <= state_i;
end
endmodule
module prim_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk_i,
input logic rst_ni,
input logic [Width-1:0] d_i,
output logic [Width-1:0] q_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) q_o <= ResetValue;
else q_o <= d_i;
end
endmodule
module prim_sparse_fsm_flop #(
parameter int Width = 1,
parameter type StateEnumT = logic [Width-1:0],
parameter logic [Width-1:0] ResetValue = '0,
parameter bit EnableAlertTriggerSVA = 1
) (
input logic clk_i,
input logic rst_ni,
input StateEnumT state_i,
output StateEnumT state_o
);
logic unused_alert_sva;
logic [Width-1:0] state_raw;
prim_flop #(
.Width(Width),
.ResetValue(ResetValue)
) u_state_flop (
.clk_i(clk_i),
.rst_ni(rst_ni),
.d_i(state_i),
.q_o(state_raw)
);
assign state_o = StateEnumT'(state_raw);
assign unused_alert_sva = EnableAlertTriggerSVA;
endmodule
module active_high_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk or posedge rst) begin
if (rst) q <= ResetValue;
else q <= d;
end
endmodule
module active_high_rst_i_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst_i,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk or posedge rst_i) begin
if (rst_i) q <= ResetValue;
else q <= d;
end
endmodule
module custom_reset_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic my_reset,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
logic [Width-1:0] d_shadow;
always_comb begin
d_shadow = d;
end
always_ff @(posedge clk or negedge my_reset) begin
if (!my_reset) q <= ResetValue;
else q <= d_shadow;
end
endmodule
module custom_reset_active_high_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic my_reset,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk or posedge my_reset) begin
if (my_reset) q <= ResetValue;
else q <= d;
end
endmodule
module no_reset_fsm_flop #(
parameter int Width = 1
) (
input logic clk,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk) begin
q <= d;
end
endmodule
module reset_no_param_fsm_flop #(
parameter int Width = 1
) (
input logic clk,
input logic rst_n,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= '0;
else q <= d;
end
endmodule
module sync_reset_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic my_reset,
input logic [Width-1:0] d,
output logic [Width-1:0] q
);
always_ff @(posedge clk) begin
if (my_reset) q <= ResetValue;
else q <= d;
end
endmodule
module ambiguous_fsm_flop (
input logic clk_i,
input logic rst_ni,
input logic [1:0] state_i,
input logic [1:0] d_i,
output logic [1:0] state_o
);
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) state_o <= 2'd0;
else state_o <= state_i ^ d_i;
end
endmodule
module fsm_auto (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_prim (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.d_i(state_d),
.q_o(state_q)
);
endmodule
module fsm_prim_override (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.d_i(state_d),
.q_o(state_q)
);
endmodule
module fsm_sparse_prim (
input logic clk,
input logic rst_n,
input logic start
);
typedef enum logic [5:0] {
S0 = 6'b00_0001,
S1 = 6'b10_0100,
S2 = 6'b11_0010
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_sparse_fsm_flop #(
.StateEnumT(state_t),
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_sparse_prim_override (
input logic clk,
input logic rst_n,
input logic start
);
typedef enum logic [5:0] {
S0 = 6'b00_0001,
S1 = 6'b10_0100,
S2 = 6'b11_0010
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
prim_sparse_fsm_flop #(
.StateEnumT(state_t),
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_active_high (
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
active_high_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.rst(rst),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_active_high_rst_i (
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
active_high_rst_i_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.rst_i(rst),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_no_reset (
input logic clk,
input logic start
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1,
S2 = 2'd2
} state_t;
state_t state_q;
state_t state_d;
always_comb begin
state_d = state_q;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
no_reset_fsm_flop #(
.Width($bits(state_t))
) u_state_regs (
.clk(clk),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_reset_no_param (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
reset_no_param_fsm_flop #(
.Width($bits(state_t))
) u_state_regs (
.clk(clk),
.rst_n(rst_n),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_sync_reset_unknown_polarity (
input logic clk,
input logic parent_reset,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
sync_reset_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.my_reset(parent_reset),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_custom_reset_parent_polarity (
input logic clk,
input logic parent_reset,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
custom_reset_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.my_reset(parent_reset),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_custom_reset_active_high (
input logic clk,
input logic parent_reset,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
custom_reset_active_high_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk(clk),
.my_reset(parent_reset),
.d(state_d),
.q(state_q)
);
endmodule
module fsm_ambiguous_ignored (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
ambiguous_fsm_flop u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.d_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_missing_reset_param (
input logic clk,
input logic rst_n,
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
endmodule
module fsm_competing_direct (
input logic clk,
input logic rst_n,
input logic start
);
/* verilator lint_off BLKANDNBLK */
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;
unique case (state_q)
S0: state_d = start ? S1 : S0;
S1: state_d = S2;
default: state_d = S0;
endcase
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) state_q <= S0;
else state_q <= state_d;
end
my_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_state_regs (
.clk_i(clk),
.rst_ni(rst_n),
.state_i(state_d),
.state_o(state_q)
);
/* verilator lint_on BLKANDNBLK */
endmodule
module t (
input logic clk
);
logic rst_n;
logic rst;
logic start;
integer cyc;
initial begin
rst_n = 1'b0;
rst = 1'b1;
start = 1'b0;
cyc = 0;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc == 1) rst_n <= 1'b1;
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
fsm_auto auto_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_prim prim_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_prim_override prim_override_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_sparse_prim sparse_prim_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_sparse_prim_override sparse_prim_override_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_active_high active_high_u (.clk(clk), .rst(rst), .start(start));
fsm_active_high_rst_i active_high_rst_i_u (.clk(clk), .rst(rst), .start(start));
fsm_no_reset no_reset_u (.clk(clk), .start(start));
fsm_reset_no_param reset_no_param_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_sync_reset_unknown_polarity sync_reset_u (.clk(clk), .parent_reset(rst), .start(start));
fsm_custom_reset_parent_polarity custom_reset_u (.clk(clk), .parent_reset(rst_n), .start(start));
fsm_custom_reset_active_high custom_reset_active_high_u (
.clk(clk), .parent_reset(rst), .start(start));
fsm_ambiguous_ignored ambiguous_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_missing_reset_param missing_reset_u (.clk(clk), .rst_n(rst_n), .start(start));
fsm_competing_direct competing_u (.clk(clk), .rst_n(rst_n), .start(start));
endmodule

View File

@ -0,0 +1,18 @@
// DESCRIPTION: Verilator: FSM register wrapper VLT descriptors
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
fsm_register_wrapper -module "my_fsm_flop" -d "state_i" -q "state_o" -clock "clk_i" -reset "rst_ni" -reset_value "ResetValue"
fsm_register_wrapper -module "prim_flop" -d "d_i" -q "q_o" -clock "clk_i" -reset "rst_ni" -reset_value "ResetValue"
fsm_register_wrapper -module "prim_sparse_fsm_flop" -d "state_i" -q "state_o" -clock "clk_i"
fsm_register_wrapper -module "active_high_fsm_flop" -d "d" -q "q" -clock "clk" -reset "rst" -reset_value "ResetValue"
fsm_register_wrapper -module "active_high_rst_i_fsm_flop" -d "d" -q "q" -clock "clk" -reset "rst_i" -reset_value "ResetValue"
fsm_register_wrapper -module "custom_reset_fsm_flop" -d "d" -q "q" -clock "clk" -reset "my_reset" -reset_value "ResetValue"
fsm_register_wrapper -module "custom_reset_active_high_fsm_flop" -d "d" -q "q" -clock "clk" -reset "my_reset" -reset_value "ResetValue"
fsm_register_wrapper -module "no_reset_fsm_flop" -d "d" -q "q" -clock "clk"
fsm_register_wrapper -module "reset_no_param_fsm_flop" -d "d" -q "q" -clock "clk"
fsm_register_wrapper -module "sync_reset_fsm_flop" -d "d" -q "q" -clock "clk"

View File

@ -0,0 +1,21 @@
%Error: t/t_fsm_register_wrapper_vlt_bad.vlt:9:1: fsm_register_wrapper missing -module
9 | fsm_register_wrapper -module "" -d "din" -q "dout" -clock "clk"
| ^~~~~~~~~~~~~~~~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_fsm_register_wrapper_vlt_bad.vlt:10:1: fsm_register_wrapper missing -module, -d, -q, -clock
10 | fsm_register_wrapper -module "" -d "" -q "" -clock ""
| ^~~~~~~~~~~~~~~~~~~~
%Error: t/t_fsm_register_wrapper_vlt_bad.vlt:11:1: fsm_register_wrapper missing -d
11 | fsm_register_wrapper -module "missing_d_fsm_flop" -d "" -q "dout" -clock "clk"
| ^~~~~~~~~~~~~~~~~~~~
%Error: t/t_fsm_register_wrapper_vlt_bad.vlt:12:1: fsm_register_wrapper missing -q
12 | fsm_register_wrapper -module "missing_q_fsm_flop" -d "din" -q "" -clock "clk"
| ^~~~~~~~~~~~~~~~~~~~
%Error: t/t_fsm_register_wrapper_vlt_bad.vlt:13:1: fsm_register_wrapper missing -clock
13 | fsm_register_wrapper -module "missing_clock_fsm_flop" -d "din" -q "dout" -clock ""
| ^~~~~~~~~~~~~~~~~~~~
%Error-BADVLTPRAGMA: t/t_fsm_register_wrapper_vlt_bad.vlt:15:1: Duplicate fsm_register_wrapper descriptor for module 'duplicate_fsm_flop'; replacing previous descriptor
15 | fsm_register_wrapper -module "duplicate_fsm_flop" -d "din" -q "dout" -clock "clk"
| ^~~~~~~~~~~~~~~~~~~~
... For error description see https://verilator.org/warn/BADVLTPRAGMA?v=latest
%Error: Exiting due to

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM register wrapper VLT bad descriptor coverage
#
# 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=["t/t_fsm_register_wrapper_vlt_bad.vlt"],
fails=True,
expect_filename=test.golden_filename,
)
test.passes()

View File

@ -0,0 +1,8 @@
// DESCRIPTION: Verilator: FSM register wrapper VLT bad descriptor coverage
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t;
endmodule

View File

@ -0,0 +1,15 @@
// DESCRIPTION: Verilator: FSM register wrapper VLT bad descriptor coverage
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
fsm_register_wrapper -module "" -d "din" -q "dout" -clock "clk"
fsm_register_wrapper -module "" -d "" -q "" -clock ""
fsm_register_wrapper -module "missing_d_fsm_flop" -d "" -q "dout" -clock "clk"
fsm_register_wrapper -module "missing_q_fsm_flop" -d "din" -q "" -clock "clk"
fsm_register_wrapper -module "missing_clock_fsm_flop" -d "din" -q "dout" -clock ""
fsm_register_wrapper -module "duplicate_fsm_flop" -d "din" -q "dout" -clock "clk"
fsm_register_wrapper -module "duplicate_fsm_flop" -d "din" -q "dout" -clock "clk"

View File

@ -0,0 +1,35 @@
%Warning-COVERIGN: t/t_fsm_register_wrapper_warn_bad.v:156:5: Ignoring unsupported: fsm_register_wrapper d and q connections must be simple variables
: ... note: In instance 't'
156 | ) u_bad_simple (
| ^~~~~~~~~~~~
... 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_fsm_register_wrapper_warn_bad.v:166:5: Ignoring unsupported: fsm_register_wrapper d and q connections must be simple variables
: ... note: In instance 't'
166 | ) u_missing_q_port (
| ^~~~~~~~~~~~~~~~
%Warning-COVERIGN: t/t_fsm_register_wrapper_warn_bad.v:176:5: Ignoring unsupported: fsm_register_wrapper instance requires a simple clock connection
: ... note: In instance 't'
176 | ) u_missing_clock_port (
| ^~~~~~~~~~~~~~~~~~~~
%Warning-COVERIGN: t/t_fsm_register_wrapper_warn_bad.v:186:5: Ignoring unsupported: fsm_register_wrapper reset arcs require both reset polarity and static reset value; reset_value parameter is not configured
: ... note: In instance 't'
186 | ) u_partial_reset (
| ^~~~~~~~~~~~~~~
%Warning-COVERIGN: t/t_fsm_register_wrapper_warn_bad.v:196:5: Ignoring unsupported: fsm_register_wrapper reset arcs require both reset polarity and static reset value; reset_value parameter is missing or not static
: ... note: In instance 't'
196 | ) u_missing_reset_value (
| ^~~~~~~~~~~~~~~~~~~~~
%Warning-COVERIGN: t/t_fsm_register_wrapper_warn_bad.v:206:5: Ignoring unsupported: fsm_register_wrapper reset arcs require both reset polarity and static reset value; reset port is not configured
: ... note: In instance 't'
206 | ) u_value_no_reset (
| ^~~~~~~~~~~~~~~~
%Warning-COVERIGN: t/t_fsm_register_wrapper_warn_bad.v:216:5: Ignoring unsupported: fsm_register_wrapper reset arcs require both reset polarity and static reset value; reset polarity could not be inferred from the wrapper
: ... note: In instance 't'
216 | ) u_sync_reset (
| ^~~~~~~~~~~~
%Warning-COVERIGN: t/t_fsm_register_wrapper_warn_bad.v:226:5: Ignoring unsupported: fsm_register_wrapper reset arcs require both reset polarity and static reset value; reset connection is missing or not a simple variable
: ... note: In instance 't'
226 | ) u_missing_reset_connection (
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: FSM register wrapper VLT warning coverage
#
# 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", "-fno-inline", "t/t_fsm_register_wrapper_warn_bad.vlt"],
fails=True,
expect_filename=test.golden_filename,
)
test.passes()

View File

@ -0,0 +1,232 @@
// DESCRIPTION: Verilator: FSM register wrapper VLT warning coverage
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module odd_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst_n,
input logic [Width-1:0] din,
output logic [Width-1:0] dout
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) dout <= ResetValue;
else dout <= din;
end
endmodule
module missing_q_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst_n,
input logic [Width-1:0] din,
output logic [Width-1:0] dout
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) dout <= ResetValue;
else dout <= din;
end
endmodule
module missing_clock_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst_n,
input logic [Width-1:0] din,
output logic [Width-1:0] dout
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) dout <= ResetValue;
else dout <= din;
end
endmodule
module partial_reset_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst_n,
input logic [Width-1:0] din,
output logic [Width-1:0] dout
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) dout <= ResetValue;
else dout <= din;
end
endmodule
module missing_reset_value_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst_n,
input logic [Width-1:0] din,
output logic [Width-1:0] dout
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) dout <= ResetValue;
else dout <= din;
end
endmodule
module value_no_reset_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst_n,
input logic [Width-1:0] din,
output logic [Width-1:0] dout
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) dout <= ResetValue;
else dout <= din;
end
endmodule
module sync_reset_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst,
input logic [Width-1:0] din,
output logic [Width-1:0] dout
);
always_ff @(posedge clk) begin
if (rst) dout <= ResetValue;
else dout <= din;
end
endmodule
module missing_reset_connection_fsm_flop #(
parameter int Width = 1,
parameter logic [Width-1:0] ResetValue = '0
) (
input logic clk,
input logic rst_n,
input logic [Width-1:0] din,
output logic [Width-1:0] dout
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) dout <= ResetValue;
else dout <= din;
end
endmodule
module t (
input logic clk
);
typedef enum logic [1:0] {
S0 = 2'd0,
S1 = 2'd1
} state_t;
logic rst_n;
logic rst;
state_t state_q;
state_t missing_d;
state_t missing_q;
state_t missing_clock_d;
state_t missing_clock_q;
state_t partial_reset_d;
state_t partial_reset_q;
state_t missing_reset_value_d;
state_t missing_reset_value_q;
state_t value_no_reset_d;
state_t value_no_reset_q;
state_t sync_reset_d;
state_t sync_reset_q;
state_t missing_reset_connection_d;
state_t missing_reset_connection_q;
odd_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_bad_simple (
.clk(clk),
.rst_n(rst_n),
.din(S0),
.dout(state_q)
);
missing_q_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_missing_q_port (
.clk(clk),
.rst_n(rst_n),
.din(missing_d),
.dout(missing_q)
);
missing_clock_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_missing_clock_port (
.clk(clk),
.rst_n(rst_n),
.din(missing_clock_d),
.dout(missing_clock_q)
);
partial_reset_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_partial_reset (
.clk(clk),
.rst_n(rst_n),
.din(partial_reset_d),
.dout(partial_reset_q)
);
missing_reset_value_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_missing_reset_value (
.clk(clk),
.rst_n(rst_n),
.din(missing_reset_value_d),
.dout(missing_reset_value_q)
);
value_no_reset_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_value_no_reset (
.clk(clk),
.rst_n(rst_n),
.din(value_no_reset_d),
.dout(value_no_reset_q)
);
sync_reset_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_sync_reset (
.clk(clk),
.rst(rst),
.din(sync_reset_d),
.dout(sync_reset_q)
);
missing_reset_connection_fsm_flop #(
.Width($bits(state_t)),
.ResetValue(S0)
) u_missing_reset_connection (
.clk(clk),
.rst_n(rst_n),
.din(missing_reset_connection_d),
.dout(missing_reset_connection_q)
);
endmodule

View File

@ -0,0 +1,16 @@
// DESCRIPTION: Verilator: FSM register wrapper VLT warning coverage
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`verilator_config
fsm_register_wrapper -module "odd_fsm_flop" -d "din" -q "dout" -clock "clk" -reset "rst_n" -reset_value "ResetValue"
fsm_register_wrapper -module "missing_q_fsm_flop" -d "din" -q "missing" -clock "clk" -reset "rst_n" -reset_value "ResetValue"
fsm_register_wrapper -module "missing_clock_fsm_flop" -d "din" -q "dout" -clock "missing" -reset "rst_n" -reset_value "ResetValue"
fsm_register_wrapper -module "partial_reset_fsm_flop" -d "din" -q "dout" -clock "clk" -reset "rst_n"
fsm_register_wrapper -module "missing_reset_value_fsm_flop" -d "din" -q "dout" -clock "clk" -reset "rst_n" -reset_value "MissingParam"
fsm_register_wrapper -module "value_no_reset_fsm_flop" -d "din" -q "dout" -clock "clk" -reset_value "ResetValue"
fsm_register_wrapper -module "sync_reset_fsm_flop" -d "din" -q "dout" -clock "clk" -reset "rst" -reset_value "ResetValue"
fsm_register_wrapper -module "missing_reset_connection_fsm_flop" -d "din" -q "dout" -clock "clk" -reset "missing_rst" -reset_value "ResetValue"