Support native FSM state and arc coverage (#7412)
This commit is contained in:
parent
41ddf4d9b6
commit
a680919edc
|
|
@ -358,6 +358,7 @@ detailed descriptions of these arguments.
|
|||
--coverage Enable all coverage
|
||||
--coverage-expr Enable expression coverage
|
||||
--coverage-expr-max <value> Maximum permutations allowed for an expression
|
||||
--coverage-fsm Enable FSM state/arc coverage
|
||||
--coverage-line Enable line coverage
|
||||
--coverage-max-width <width> Maximum array depth for coverage
|
||||
--coverage-toggle Enable toggle coverage
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ L<https://verilator.org/guide/latest/exe_verilator_coverage.html>.
|
|||
--annotate-points Annotates info from each coverage point.
|
||||
--filter-type <regex> Keep only records of given coverage type.
|
||||
--help Displays this message and version and exits.
|
||||
--include-reset-arcs Include reset arcs in FSM arc summaries.
|
||||
--rank Compute relative importance of tests.
|
||||
--unlink With --write, unlink all inputs
|
||||
--version Displays program version and exits.
|
||||
|
|
|
|||
|
|
@ -281,7 +281,8 @@ Summary:
|
|||
.. option:: --coverage
|
||||
|
||||
Enables all forms of coverage, an alias for :vlopt:`--coverage-line`
|
||||
:vlopt:`--coverage-toggle` :vlopt:`--coverage-expr` :vlopt:`--coverage-user`.
|
||||
:vlopt:`--coverage-toggle` :vlopt:`--coverage-expr` :vlopt:`--coverage-fsm`
|
||||
:vlopt:`--coverage-user`.
|
||||
|
||||
.. option:: --coverage-expr
|
||||
|
||||
|
|
@ -293,6 +294,10 @@ Summary:
|
|||
covered for a given expression. Defaults to 32. Increasing may slow
|
||||
coverage simulations and make analyzing the results unwieldy.
|
||||
|
||||
.. option:: --coverage-fsm
|
||||
|
||||
Enables native FSM state and arc coverage. See :ref:`FSM Coverage`.
|
||||
|
||||
.. option:: --coverage-line
|
||||
|
||||
Enables basic block line coverage analysis. See :ref:`Line Coverage`.
|
||||
|
|
|
|||
|
|
@ -129,13 +129,20 @@ verilator_coverage Arguments
|
|||
.. option:: --filter-type <regex>
|
||||
|
||||
Skips records of coverage types that matches with <regex>
|
||||
Possible values are `toggle`, `line`, `branch`, `expr`, `user` and
|
||||
a wildcard with `\*` or `?`. The default value is `\*`.
|
||||
Possible values are `toggle`, `line`, `branch`, `expr`, `user`,
|
||||
`fsm_state`, `fsm_arc` and a wildcard with `\*` or `?`. The default
|
||||
value is `\*`.
|
||||
|
||||
.. option:: --help
|
||||
|
||||
Displays a help summary, the program version, and exits.
|
||||
|
||||
.. option:: --include-reset-arcs
|
||||
|
||||
Includes FSM reset arcs in the printed summaries and annotated output.
|
||||
By default, reset arcs are tracked but summarized separately from the
|
||||
non-reset FSM arcs.
|
||||
|
||||
.. option:: --rank
|
||||
|
||||
Prints an experimental report listing the relative importance of each
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ SystemVerilog code coverage. With :vlopt:`--coverage`, Verilator enables
|
|||
all forms of coverage:
|
||||
|
||||
- :ref:`User Coverage`
|
||||
- :ref:`FSM Coverage`
|
||||
- :ref:`Line Coverage`
|
||||
- :ref:`Toggle Coverage`
|
||||
|
||||
|
|
@ -208,6 +209,47 @@ point under the coverage name "DefaultClock":
|
|||
|
||||
DefaultClock: cover property (@(posedge clk) cyc==3);
|
||||
|
||||
.. _fsm coverage:
|
||||
|
||||
FSM Coverage
|
||||
------------
|
||||
|
||||
With :vlopt:`--coverage` or :vlopt:`--coverage-fsm`, Verilator can
|
||||
instrument a conservative subset of single-process FSMs and report both
|
||||
state coverage (`fsm_state`) and transition coverage (`fsm_arc`).
|
||||
|
||||
This feature is currently experimental and might change in subsequent
|
||||
releases. In particular, the native FSM coverage extraction heuristics,
|
||||
:vlopt:`--coverage-fsm`, and the Verilator-specific FSM metacomments below
|
||||
should be treated as subject to change while the interface settles.
|
||||
|
||||
FSM extraction is intentionally narrow. The current implementation targets
|
||||
clocked, enum-driven state machines that can be recovered directly from the
|
||||
RTL. It does not claim broad support for two-process FSMs, one-hot
|
||||
inference, helper-function next-state recovery, or deeply nested control
|
||||
recovery.
|
||||
|
||||
The following metacomments may be attached to the state variable to steer
|
||||
the extracted coverage model:
|
||||
|
||||
- ``/*verilator fsm_state*/`` forces the variable to be treated as
|
||||
FSM state.
|
||||
- ``/*verilator fsm_reset_arc*/`` marks reset transitions as
|
||||
user-visible reset arcs instead of defaulting to a hidden reset-only
|
||||
summary.
|
||||
- ``/*verilator fsm_arc_include_cond*/`` keeps conditional branch
|
||||
arcs that would otherwise be skipped by the conservative extractor.
|
||||
|
||||
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
|
||||
:option:`verilator_coverage --include-reset-arcs` to include
|
||||
those arcs in the printed summary and annotated output.
|
||||
|
||||
Annotated output produced by :command:`verilator_coverage --annotate` will
|
||||
label FSM points with `fsm_state` and `fsm_arc`, and synthetic fallback
|
||||
transitions with `SYNTHETIC DEFAULT ARC`.
|
||||
|
||||
|
||||
.. _line coverage:
|
||||
|
||||
|
|
|
|||
|
|
@ -306,7 +306,7 @@ List Of Warnings
|
|||
else
|
||||
array[address] <= data;
|
||||
|
||||
While this is supported in typical synthesizeable code (including the
|
||||
While this is supported in typical synthesizable code (including the
|
||||
example above), some complicated cases are not supported. Namely:
|
||||
|
||||
1. If the above loop is inside a suspendable process or fork statement.
|
||||
|
|
@ -837,6 +837,18 @@ List Of Warnings
|
|||
with a newline."
|
||||
|
||||
|
||||
.. option:: FSMMULTI
|
||||
|
||||
Warns that the same always block contains multiple enum-typed case
|
||||
statements that look like FSM candidates for native FSM coverage when
|
||||
:vlopt:`--coverage-fsm` or :vlopt:`--coverage` is enabled.
|
||||
|
||||
Verilator's FSM coverage instruments only the first such candidate in
|
||||
source order. Split the FSMs into separate always blocks, or explicitly
|
||||
annotate the intended state variables and restructure the RTL for full
|
||||
coverage of such multiple state machines.
|
||||
|
||||
|
||||
.. option:: FUNCTIMECTL
|
||||
|
||||
Error that a function contains a time-controlling statement or call of a
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ ABCp
|
|||
Aadi
|
||||
Accellera
|
||||
Aditya
|
||||
allocator
|
||||
Affe
|
||||
Aleksander
|
||||
Alexandre
|
||||
|
|
@ -362,6 +363,7 @@ Olofsson
|
|||
Ondrej
|
||||
Oron
|
||||
Oyvind
|
||||
output
|
||||
PLI
|
||||
Pakanati
|
||||
Palaniappan
|
||||
|
|
@ -402,8 +404,10 @@ Ranjan
|
|||
Rapp
|
||||
Redhat
|
||||
Reitan
|
||||
reentrant
|
||||
Renga
|
||||
Requin
|
||||
reusability
|
||||
Riaz
|
||||
Rodas
|
||||
Rodionov
|
||||
|
|
|
|||
|
|
@ -499,6 +499,26 @@ void VerilatedCovContext::_insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7
|
|||
C(13), C(14), C(15), C(16), C(17), C(18), C(19), N(20), N(21), N(22), N(23), N(24),
|
||||
N(25), N(26), N(27), N(28), N(29));
|
||||
}
|
||||
// Backward compatibility for mixed inserts with integer-valued
|
||||
// lineno/column pairs and C-string-valued metadata pairs.
|
||||
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6),
|
||||
A(7)) VL_MT_SAFE {
|
||||
const std::string val2str = std::to_string(val2);
|
||||
const std::string val3str = std::to_string(val3);
|
||||
_insertp(C(0), C(1), key2, val2str.c_str(), key3, val3str.c_str(), C(4), C(5), C(6), C(7),
|
||||
N(8), N(9), N(10), N(11), N(12), N(13), N(14), N(15), N(16), N(17), N(18), N(19),
|
||||
N(20), N(21), N(22), N(23), N(24), N(25), N(26), N(27), N(28), N(29));
|
||||
}
|
||||
// Backward compatibility for mixed inserts with integer-valued
|
||||
// lineno/column pairs and additional FSM metadata pairs.
|
||||
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6),
|
||||
A(7), A(8), A(9), A(10), A(11)) VL_MT_SAFE {
|
||||
const std::string val2str = std::to_string(val2);
|
||||
const std::string val3str = std::to_string(val3);
|
||||
_insertp(C(0), C(1), key2, val2str.c_str(), key3, val3str.c_str(), C(4), C(5), C(6), C(7),
|
||||
C(8), C(9), C(10), C(11), N(12), N(13), N(14), N(15), N(16), N(17), N(18), N(19),
|
||||
N(20), N(21), N(22), N(23), N(24), N(25), N(26), N(27), N(28), N(29));
|
||||
}
|
||||
// Backward compatibility for Verilator
|
||||
void VerilatedCovContext::_insertp(A(0), A(1), K(2), int val2, K(3), int val3, K(4),
|
||||
const std::string& val4, A(5), A(6), A(7)) VL_MT_SAFE {
|
||||
|
|
|
|||
|
|
@ -191,6 +191,13 @@ public:
|
|||
void _insertp(A(0), A(1), A(2), A(3), A(4), A(5), A(6), A(7), A(8), A(9), A(10), A(11), A(12),
|
||||
A(13), A(14), A(15), A(16), A(17), A(18), A(19), A(20), D(21), D(22), D(23),
|
||||
D(24), D(25), D(26), D(27), D(28), D(29)) VL_MT_SAFE;
|
||||
// Backward compatibility for mixed inserts with integer-valued
|
||||
// lineno/column pairs and C-string-valued metadata pairs.
|
||||
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), A(7)) VL_MT_SAFE;
|
||||
// Backward compatibility for mixed inserts with integer-valued
|
||||
// lineno/column pairs and additional FSM metadata pairs.
|
||||
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, A(4), A(5), A(6), A(7), A(8), A(9),
|
||||
A(10), A(11)) VL_MT_SAFE;
|
||||
// Backward compatibility for Verilator
|
||||
void _insertp(A(0), A(1), K(2), int val2, K(3), int val3, K(4), const std::string& val4, A(5),
|
||||
A(6), A(7)) VL_MT_SAFE;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ VLCOVGEN_ITEM("'name':'thresh', 'short':'s', 'group':1, 'default':None, 'd
|
|||
VLCOVGEN_ITEM("'name':'type', 'short':'t', 'group':1, 'default':'', 'descr':'Type of coverage (block, line, fsm, etc)'")
|
||||
// Bin attributes
|
||||
VLCOVGEN_ITEM("'name':'comment', 'short':'o', 'group':0, 'default':'', 'descr':'Textual description for the item'")
|
||||
VLCOVGEN_ITEM("'name':'fsm_from', 'short':'Ff', 'group':0, 'default':'', 'descr':'FSM source state name for structured FSM coverage points'")
|
||||
VLCOVGEN_ITEM("'name':'fsm_tag', 'short':'Fg', 'group':0, 'default':'', 'descr':'FSM point tag such as reset, reset_include, or default'")
|
||||
VLCOVGEN_ITEM("'name':'fsm_to', 'short':'Ft', 'group':0, 'default':'', 'descr':'FSM destination state name for structured FSM coverage points'")
|
||||
VLCOVGEN_ITEM("'name':'fsm_var', 'short':'Fv', 'group':0, 'default':'', 'descr':'FSM state variable name for structured FSM coverage points'")
|
||||
VLCOVGEN_ITEM("'name':'hier', 'short':'h', 'group':0, 'default':'', 'descr':'Hierarchy path name for the item'")
|
||||
VLCOVGEN_ITEM("'name':'lineno', 'short':'l', 'group':0, 'default':0, 'descr':'Line number for the item'")
|
||||
VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'descr':'For totaling items, weight of this item'")
|
||||
|
|
@ -49,6 +53,10 @@ VLCOVGEN_ITEM("'name':'weight', 'short':'w', 'group':0, 'default':None, 'd
|
|||
#define VL_CIK_COLUMN "n"
|
||||
#define VL_CIK_COMMENT "o"
|
||||
#define VL_CIK_FILENAME "f"
|
||||
#define VL_CIK_FSM_FROM "Ff"
|
||||
#define VL_CIK_FSM_TAG "Fg"
|
||||
#define VL_CIK_FSM_TO "Ft"
|
||||
#define VL_CIK_FSM_VAR "Fv"
|
||||
#define VL_CIK_HIER "h"
|
||||
#define VL_CIK_LINENO "l"
|
||||
#define VL_CIK_LINESCOV "S"
|
||||
|
|
@ -70,6 +78,10 @@ public:
|
|||
if (key == "column") return VL_CIK_COLUMN;
|
||||
if (key == "comment") return VL_CIK_COMMENT;
|
||||
if (key == "filename") return VL_CIK_FILENAME;
|
||||
if (key == "fsm_from") return VL_CIK_FSM_FROM;
|
||||
if (key == "fsm_tag") return VL_CIK_FSM_TAG;
|
||||
if (key == "fsm_to") return VL_CIK_FSM_TO;
|
||||
if (key == "fsm_var") return VL_CIK_FSM_VAR;
|
||||
if (key == "hier") return VL_CIK_HIER;
|
||||
if (key == "lineno") return VL_CIK_LINENO;
|
||||
if (key == "linescov") return VL_CIK_LINESCOV;
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ set(HEADERS
|
|||
V3File.h
|
||||
V3FileLine.h
|
||||
V3Force.h
|
||||
V3FsmDetect.h
|
||||
V3Fork.h
|
||||
V3FuncOpt.h
|
||||
V3FunctionTraits.h
|
||||
|
|
@ -277,6 +278,7 @@ set(COMMON_SOURCES
|
|||
V3File.cpp
|
||||
V3FileLine.cpp
|
||||
V3Force.cpp
|
||||
V3FsmDetect.cpp
|
||||
V3Fork.cpp
|
||||
V3FuncOpt.cpp
|
||||
V3Gate.cpp
|
||||
|
|
|
|||
|
|
@ -280,6 +280,7 @@ RAW_OBJS_PCH_ASTNOMT = \
|
|||
V3ExecGraph.o \
|
||||
V3Expand.o \
|
||||
V3Force.o \
|
||||
V3FsmDetect.o \
|
||||
V3Fork.o \
|
||||
V3Gate.o \
|
||||
V3HierBlock.o \
|
||||
|
|
|
|||
|
|
@ -311,6 +311,9 @@ public:
|
|||
//
|
||||
VAR_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes
|
||||
VAR_FORCEABLE, // V3LinkParse moves to AstVar::isForceable
|
||||
VAR_FSM_ARC_INCLUDE_COND, // V3LinkParse moves to AstVar::attrFsmArcInclCond
|
||||
VAR_FSM_RESET_ARC, // V3LinkParse moves to AstVar::attrFsmResetArc
|
||||
VAR_FSM_STATE, // V3LinkParse moves to AstVar::attrFsmState
|
||||
VAR_PORT_DTYPE, // V3LinkDot for V3Width to check port dtype
|
||||
VAR_PUBLIC, // V3LinkParse moves to AstVar::sigPublic
|
||||
VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic
|
||||
|
|
@ -336,10 +339,10 @@ public:
|
|||
"ENUM_NEXT", "ENUM_PREV", "ENUM_NAME", "ENUM_VALID",
|
||||
"FUNC_ARG_PROTO", "FUNC_RETURN_PROTO",
|
||||
"TYPEID", "TYPENAME",
|
||||
"VAR_BASE", "VAR_FORCEABLE", "VAR_PORT_DTYPE", "VAR_PUBLIC",
|
||||
"VAR_PUBLIC_FLAT", "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW",
|
||||
"VAR_ISOLATE_ASSIGNMENTS", "VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT",
|
||||
"VAR_SPLIT_VAR"
|
||||
"VAR_BASE", "VAR_FORCEABLE", "VAR_FSM_ARC_INCLUDE_COND", "VAR_FSM_RESET_ARC",
|
||||
"VAR_FSM_STATE", "VAR_PORT_DTYPE", "VAR_PUBLIC", "VAR_PUBLIC_FLAT",
|
||||
"VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_ISOLATE_ASSIGNMENTS",
|
||||
"VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR"
|
||||
};
|
||||
// clang-format on
|
||||
return names[m_e];
|
||||
|
|
|
|||
|
|
@ -1934,6 +1934,9 @@ class AstVar final : public AstNode {
|
|||
bool m_attrIsolateAssign : 1; // User isolate_assignments attribute
|
||||
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_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
|
||||
bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration
|
||||
bool m_isConst : 1; // Table contains constant data
|
||||
|
|
@ -1992,6 +1995,9 @@ class AstVar final : public AstNode {
|
|||
m_attrIsolateAssign = false;
|
||||
m_attrSFormat = false;
|
||||
m_attrSplitVar = false;
|
||||
m_attrFsmState = false;
|
||||
m_attrFsmResetArc = false;
|
||||
m_attrFsmArcInclCond = false;
|
||||
m_fileDescr = false;
|
||||
m_gotNansiType = false;
|
||||
m_isConst = false;
|
||||
|
|
@ -2136,6 +2142,9 @@ public:
|
|||
void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; }
|
||||
void attrSFormat(bool flag) { m_attrSFormat = flag; }
|
||||
void attrSplitVar(bool flag) { m_attrSplitVar = flag; }
|
||||
void attrFsmState(bool flag) { m_attrFsmState = flag; }
|
||||
void attrFsmResetArc(bool flag) { m_attrFsmResetArc = flag; }
|
||||
void attrFsmArcInclCond(bool flag) { m_attrFsmArcInclCond = flag; }
|
||||
void rand(const VRandAttr flag) { m_rand = flag; }
|
||||
void usedParam(bool flag) { m_usedParam = flag; }
|
||||
void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; }
|
||||
|
|
@ -2299,6 +2308,9 @@ public:
|
|||
bool attrFileDescr() const { return m_fileDescr; }
|
||||
bool attrSFormat() const { return m_attrSFormat; }
|
||||
bool attrSplitVar() const { return m_attrSplitVar; }
|
||||
bool attrFsmState() const { return m_attrFsmState; }
|
||||
bool attrFsmResetArc() const { return m_attrFsmResetArc; }
|
||||
bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; }
|
||||
bool attrIsolateAssign() const { return m_attrIsolateAssign; }
|
||||
AstIface* sensIfacep() const { return m_sensIfacep; }
|
||||
VRandAttr rand() const { return m_rand; }
|
||||
|
|
@ -2384,12 +2396,22 @@ class AstCoverOtherDecl final : public AstNodeCoverDecl {
|
|||
// Coverage analysis point declaration
|
||||
// Used for other than toggle types of coverage
|
||||
string m_linescov;
|
||||
string m_fsmVar;
|
||||
string m_fsmFrom;
|
||||
string m_fsmTo;
|
||||
string m_fsmTag;
|
||||
int m_offset; // Offset column numbers to uniq-ify IFs
|
||||
public:
|
||||
AstCoverOtherDecl(FileLine* fl, const string& page, const string& comment,
|
||||
const string& linescov, int offset)
|
||||
const string& linescov, int offset, const string& fsmVar = "",
|
||||
const string& fsmFrom = "", const string& fsmTo = "",
|
||||
const string& fsmTag = "")
|
||||
: ASTGEN_SUPER_CoverOtherDecl(fl, page, comment)
|
||||
, m_linescov{linescov}
|
||||
, m_fsmVar{fsmVar}
|
||||
, m_fsmFrom{fsmFrom}
|
||||
, m_fsmTo{fsmTo}
|
||||
, m_fsmTag{fsmTag}
|
||||
, m_offset{offset} {}
|
||||
ASTGEN_MEMBERS_AstCoverOtherDecl;
|
||||
void dump(std::ostream& str) const override;
|
||||
|
|
@ -2397,6 +2419,10 @@ public:
|
|||
int offset() const { return m_offset; }
|
||||
int size() const override { return 1; }
|
||||
const string& linescov() const { return m_linescov; }
|
||||
const string& fsmVar() const { return m_fsmVar; }
|
||||
const string& fsmFrom() const { return m_fsmFrom; }
|
||||
const string& fsmTo() const { return m_fsmTo; }
|
||||
const string& fsmTag() const { return m_fsmTag; }
|
||||
bool sameNode(const AstNode* samep) const override {
|
||||
const AstCoverOtherDecl* const asamep = VN_DBG_AS(samep, CoverOtherDecl);
|
||||
return AstNodeCoverDecl::sameNode(samep) && linescov() == asamep->linescov();
|
||||
|
|
|
|||
|
|
@ -3004,6 +3004,9 @@ void AstVar::dump(std::ostream& str) const {
|
|||
if (processQueue()) str << " [PROCQ]";
|
||||
if (sampled()) str << " [SAMPLED]";
|
||||
if (attrIsolateAssign()) str << " [aISO]";
|
||||
if (attrFsmState()) str << " [aFSMSTATE]";
|
||||
if (attrFsmResetArc()) str << " [aFSMRESETARC]";
|
||||
if (attrFsmArcInclCond()) str << " [aFSMARCCOND]";
|
||||
if (attrFileDescr()) str << " [aFD]";
|
||||
if (isFuncReturn()) {
|
||||
str << " [FUNCRTN]";
|
||||
|
|
@ -3036,6 +3039,9 @@ void AstVar::dumpJson(std::ostream& str) const {
|
|||
dumpJsonBoolFuncIf(str, processQueue);
|
||||
dumpJsonBoolFuncIf(str, sampled);
|
||||
dumpJsonBoolFuncIf(str, attrIsolateAssign);
|
||||
dumpJsonBoolFuncIf(str, attrFsmState);
|
||||
dumpJsonBoolFuncIf(str, attrFsmResetArc);
|
||||
dumpJsonBoolFuncIf(str, attrFsmArcInclCond);
|
||||
dumpJsonBoolFuncIf(str, attrFileDescr);
|
||||
dumpJsonBoolFuncIf(str, isDpiOpenArray);
|
||||
dumpJsonBoolFuncIf(str, isFuncReturn);
|
||||
|
|
@ -3283,10 +3289,18 @@ void AstNodeCoverDecl::dumpJson(std::ostream& str) const {
|
|||
void AstCoverOtherDecl::dump(std::ostream& str) const {
|
||||
this->AstNodeCoverDecl::dump(str);
|
||||
if (!linescov().empty()) str << " lc=" << linescov();
|
||||
if (!fsmVar().empty()) str << " fv=" << fsmVar();
|
||||
if (!fsmFrom().empty()) str << " ff=" << fsmFrom();
|
||||
if (!fsmTo().empty()) str << " ft=" << fsmTo();
|
||||
if (!fsmTag().empty()) str << " fg=" << fsmTag();
|
||||
}
|
||||
void AstCoverOtherDecl::dumpJson(std::ostream& str) const {
|
||||
this->AstNodeCoverDecl::dumpJson(str);
|
||||
dumpJsonStrFunc(str, linescov);
|
||||
dumpJsonStrFunc(str, fsmVar);
|
||||
dumpJsonStrFunc(str, fsmFrom);
|
||||
dumpJsonStrFunc(str, fsmTo);
|
||||
dumpJsonStrFunc(str, fsmTag);
|
||||
}
|
||||
void AstCoverToggleDecl::dump(std::ostream& str) const {
|
||||
this->AstNodeCoverDecl::dump(str);
|
||||
|
|
|
|||
|
|
@ -826,6 +826,14 @@ public:
|
|||
putsQuoted(VIdProtect::protectWordsIf(nodep->comment(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(nodep->linescov());
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmVar(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmFrom(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTo(), nodep->protect()));
|
||||
puts(", ");
|
||||
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTag(), nodep->protect()));
|
||||
puts(");\n");
|
||||
}
|
||||
void visit(AstCoverToggleDecl* nodep) override {
|
||||
|
|
|
|||
|
|
@ -197,7 +197,9 @@ class EmitCHeader final : public EmitCConstInit {
|
|||
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
|
||||
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
|
||||
puts("const char* hierp, const char* pagep, const char* commentp, const char* "
|
||||
"linescovp);\n");
|
||||
"linescovp,\n");
|
||||
puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* "
|
||||
"fsmTagp);\n");
|
||||
}
|
||||
|
||||
if (v3Global.opt.coverageToggle() && !VN_IS(modp, Class)) {
|
||||
|
|
|
|||
|
|
@ -180,7 +180,9 @@ class EmitCImp final : public EmitCFunc {
|
|||
puts(v3Global.opt.threads() > 1 ? "std::atomic<uint32_t>" : "uint32_t");
|
||||
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
|
||||
puts("const char* hierp, const char* pagep, const char* commentp, const char* "
|
||||
"linescovp) {\n");
|
||||
"linescovp,\n");
|
||||
puts("const char* fsmVarp, const char* fsmFromp, const char* fsmTop, const char* "
|
||||
"fsmTagp) {\n");
|
||||
if (v3Global.opt.threads() > 1) {
|
||||
puts("assert(sizeof(uint32_t) == sizeof(std::atomic<uint32_t>));\n");
|
||||
puts("uint32_t* count32p = reinterpret_cast<uint32_t*>(countp);\n");
|
||||
|
|
@ -198,10 +200,14 @@ class EmitCImp final : public EmitCFunc {
|
|||
puts(" \"filename\",filenamep,");
|
||||
puts(" \"lineno\",lineno,");
|
||||
puts(" \"column\",column,\n");
|
||||
puts("\"hier\",fullhier,");
|
||||
puts("\"hier\",fullhier.c_str(),");
|
||||
puts(" \"page\",pagep,");
|
||||
puts(" \"comment\",commentp,");
|
||||
puts(" (linescovp[0] ? \"linescov\" : \"\"), linescovp);\n");
|
||||
puts(" (linescovp[0] ? \"linescov\" : \"\"), linescovp,");
|
||||
puts(" (fsmVarp[0] ? \"fsm_var\" : \"\"), fsmVarp,");
|
||||
puts(" (fsmFromp[0] ? \"fsm_from\" : \"\"), fsmFromp,");
|
||||
puts(" (fsmTop[0] ? \"fsm_to\" : \"\"), fsmTop,");
|
||||
puts(" (fsmTagp[0] ? \"fsm_tag\" : \"\"), fsmTagp);\n");
|
||||
puts("}\n");
|
||||
}
|
||||
if (v3Global.opt.coverageToggle()) {
|
||||
|
|
@ -237,7 +243,7 @@ class EmitCImp final : public EmitCFunc {
|
|||
puts(" \"filename\",filenamep,");
|
||||
puts(" \"lineno\",lineno,");
|
||||
puts(" \"column\",column,\n");
|
||||
puts("\"hier\",fullhier,");
|
||||
puts("\"hier\",fullhier.c_str(),");
|
||||
puts(" \"page\",pagep,");
|
||||
puts(" \"comment\",commentWithIndex.c_str(),");
|
||||
puts(" \"\", \"\");\n"); // linescov argument, but in toggle coverage it is always
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ public:
|
|||
ENUMITEMWIDTH, // Error: enum item width mismatch
|
||||
ENUMVALUE, // Error: enum type needs explicit cast
|
||||
EOFNEWLINE, // End-of-file missing newline
|
||||
FSMMULTI, // Multiple FSM candidates in one always block
|
||||
FUNCTIMECTL, // Functions cannot have timing/delay/wait
|
||||
FUTURE, // Feature is under development and not yet supported
|
||||
GENCLK, // Generated Clock. Historical, never issued.
|
||||
|
|
@ -223,16 +224,16 @@ public:
|
|||
"BSSPACE", "CASEINCOMPLETE", "CASEOVERLAP", "CASEWITHX", "CASEX", "CASTCONST",
|
||||
"CDCRSTLOGIC", "CLKDATA", "CMPCONST", "COLONPLUS", "COMBDLY", "CONSTRAINTIGN",
|
||||
"CONTASSREG", "COVERIGN", "DECLFILENAME", "DEFOVERRIDE", "DEFPARAM", "DEPRECATED",
|
||||
"ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FUNCTIMECTL",
|
||||
"FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH", "IGNOREDRETURN",
|
||||
"IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "INCABSPATH",
|
||||
"INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH", "LITENDIAN",
|
||||
"MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN", "MULTITOP",
|
||||
"NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT", "PARAMNODEFAULT",
|
||||
"PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL",
|
||||
"PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED",
|
||||
"ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FSMMULTI",
|
||||
"FUNCTIMECTL", "FUTURE", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH",
|
||||
"IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE",
|
||||
"INCABSPATH","INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH",
|
||||
"LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN",
|
||||
"MULTITOP", "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT",
|
||||
"PARAMNODEFAULT", "PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND",
|
||||
"PKGNODECL", "PREPROCZERO", "PROCASSINIT", "PROCASSWIRE", "PROFOUTOFDATE", "PROTECTED",
|
||||
"PROTOTYPEMIS", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY", "SELRANGE",
|
||||
"SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR", "STMTDLY",
|
||||
"SHORTREAL", "SIDEEFFECT", "SPECIFYIGN", "SPLITVAR", "STATICVAR","STMTDLY",
|
||||
"SUPERNFIRST", "SYMRSVDWORD", "SYNCASYNCNET", "TICKCOUNT", "TIMESCALEMOD", "UNDRIVEN",
|
||||
"UNOPT", "UNOPTFLAT", "UNOPTTHREADS", "UNPACKED", "UNSATCONSTR", "UNSIGNED", "UNUSED",
|
||||
"UNUSEDGENVAR", "UNUSEDLOOP", "UNUSEDPARAM", "UNUSEDSIGNAL", "USERERROR", "USERFATAL",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,785 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: FSM coverage detect pass
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//*************************************************************************
|
||||
// FSM COVERAGE DETECT:
|
||||
// Walk clocked always blocks while the original FSM structure is still
|
||||
// present, build a per-FSM V3Graph representation of the extracted
|
||||
// states/transitions, then immediately lower that completed graph state
|
||||
// into the final coverage declarations, previous-state tracking, and
|
||||
// active blocks needed to implement FSM state and arc coverage in the
|
||||
// generated model.
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "V3PchAstNoMT.h"
|
||||
|
||||
#include "V3FsmDetect.h"
|
||||
|
||||
#include "V3Ast.h"
|
||||
#include "V3Graph.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
namespace {
|
||||
|
||||
// Captures one sensitivity-list entry so the lowering phase can later rebuild
|
||||
// an active block with the same triggering event control.
|
||||
struct FsmSenDesc final {
|
||||
// Encoded edge kind copied from AstSenItem::edgeType() so lowering can
|
||||
// rebuild the same trigger semantics on the synthesized coverage block.
|
||||
VEdgeType::en edgeType = static_cast<VEdgeType::en>(0);
|
||||
// Triggering signal in the saved scoped AST.
|
||||
AstVarScope* varScopep = nullptr;
|
||||
};
|
||||
|
||||
// Captures the simple reset predicate shape that survives to this pass after
|
||||
// earlier normalization so reset arcs can be reconstructed during lowering.
|
||||
struct FsmResetCondDesc final {
|
||||
// Reset signal used by the FSM in the saved scoped AST.
|
||||
AstVarScope* varScopep = nullptr;
|
||||
};
|
||||
|
||||
class FsmGraph;
|
||||
|
||||
class FsmVertex VL_NOT_FINAL : public V3GraphVertex {
|
||||
VL_RTTI_IMPL(FsmVertex, V3GraphVertex)
|
||||
|
||||
public:
|
||||
enum class Kind : uint8_t { STATE, RESET_ANY, DEFAULT_ANY };
|
||||
|
||||
private:
|
||||
Kind m_kind; // State vs synthetic ANY/default vertex role.
|
||||
string m_label; // User-facing state or pseudo-state label.
|
||||
int m_value = 0; // Encoded state value for real state vertices.
|
||||
|
||||
protected:
|
||||
FsmVertex(V3Graph* graphp, Kind kind, string label, int value) VL_MT_DISABLED
|
||||
: V3GraphVertex{graphp}
|
||||
, m_kind{kind}
|
||||
, m_label{label}
|
||||
, m_value{value} {}
|
||||
~FsmVertex() override = default;
|
||||
|
||||
public:
|
||||
Kind kind() const { return m_kind; }
|
||||
bool isState() const { return m_kind == Kind::STATE; }
|
||||
bool isResetAny() const { return m_kind == Kind::RESET_ANY; }
|
||||
bool isDefaultAny() const { return m_kind == Kind::DEFAULT_ANY; }
|
||||
const string& label() const { return m_label; }
|
||||
int value() const { return m_value; }
|
||||
|
||||
string name() const override VL_MT_SAFE { return m_label + "=" + cvtToStr(m_value); }
|
||||
};
|
||||
|
||||
class FsmStateVertex final : public FsmVertex {
|
||||
VL_RTTI_IMPL(FsmStateVertex, FsmVertex)
|
||||
|
||||
public:
|
||||
FsmStateVertex(V3Graph* graphp, string label, int value) VL_MT_DISABLED
|
||||
: FsmVertex{graphp, Kind::STATE, label, value} {}
|
||||
~FsmStateVertex() override = default;
|
||||
|
||||
string dotColor() const override { return "lightblue"; }
|
||||
string dotShape() const override { return "ellipse"; }
|
||||
};
|
||||
|
||||
class FsmPseudoVertex final : public FsmVertex {
|
||||
VL_RTTI_IMPL(FsmPseudoVertex, FsmVertex)
|
||||
|
||||
public:
|
||||
FsmPseudoVertex(V3Graph* graphp, Kind kind, string label) VL_MT_DISABLED
|
||||
: FsmVertex{graphp, kind, label, 0} {}
|
||||
~FsmPseudoVertex() override = default;
|
||||
|
||||
string name() const override VL_MT_SAFE { return label(); }
|
||||
string dotColor() const override { return isResetAny() ? "darkgreen" : "orange"; }
|
||||
string dotShape() const override { return "diamond"; }
|
||||
};
|
||||
|
||||
class FsmArcEdge final : public V3GraphEdge {
|
||||
VL_RTTI_IMPL(FsmArcEdge, V3GraphEdge)
|
||||
bool m_isReset = false; // Arc originates from the synthetic reset source.
|
||||
bool m_isCond = false; // Arc came from a conditional next-state split.
|
||||
bool m_isDefault = false; // Arc represents a case default source.
|
||||
FileLine* m_flp = nullptr; // Source location for emitted coverage metadata.
|
||||
|
||||
public:
|
||||
FsmArcEdge(V3Graph* graphp, FsmVertex* fromp, FsmStateVertex* top, bool isReset,
|
||||
bool isCond, bool isDefault, FileLine* flp) VL_MT_DISABLED
|
||||
: V3GraphEdge{graphp, fromp, top, 1}
|
||||
, m_isReset{isReset}
|
||||
, m_isCond{isCond}
|
||||
, m_isDefault{isDefault}
|
||||
, m_flp{flp} {}
|
||||
~FsmArcEdge() override = default;
|
||||
|
||||
bool isReset() const { return m_isReset; }
|
||||
bool isCond() const { return m_isCond; }
|
||||
bool isDefault() const { return m_isDefault; }
|
||||
FileLine* fileline() const { return m_flp; }
|
||||
|
||||
string dotLabel() const override {
|
||||
if (m_isReset) return "reset";
|
||||
if (m_isDefault) return "default";
|
||||
if (m_isCond) return "cond";
|
||||
return "";
|
||||
}
|
||||
string dotColor() const override {
|
||||
if (m_isReset) return "darkgreen";
|
||||
if (m_isDefault) return "orange";
|
||||
if (m_isCond) return "blue";
|
||||
return "black";
|
||||
}
|
||||
};
|
||||
|
||||
// One graph per detected FSM. Graph-level metadata captures the non-graph
|
||||
// context needed to lower states/arcs back into the AST after detection.
|
||||
class FsmGraph final : public V3Graph {
|
||||
AstScope* m_scopep = nullptr; // Owning scoped block for the detected FSM.
|
||||
AstAlways* m_alwaysp = nullptr; // Original always block being instrumented.
|
||||
string m_stateVarName; // Pretty state variable name for user-visible output.
|
||||
string m_stateVarInternalName; // Internal state symbol name for dump tags.
|
||||
AstVarScope* m_stateVarScopep = nullptr; // Scoped state variable being tracked.
|
||||
std::vector<FsmSenDesc> m_senses; // Saved event controls for recreated active blocks.
|
||||
FsmResetCondDesc m_resetCond; // Saved reset predicate shape, if one exists.
|
||||
bool m_hasResetCond = false; // Whether the detected FSM had a reset branch.
|
||||
bool m_resetInclude = false; // Whether reset arcs count toward coverage totals.
|
||||
bool m_inclCond = false; // Whether conditional arcs should be kept explicitly.
|
||||
FileLine* m_flp = nullptr; // Representative source location for declarations/arcs.
|
||||
std::unordered_map<int, FsmStateVertex*> m_stateVertices; // Value to state-vertex map.
|
||||
FsmPseudoVertex* m_resetVertexp = nullptr; // Synthetic ANY source for reset arcs.
|
||||
FsmPseudoVertex* m_defaultVertexp = nullptr; // Synthetic default source for case defaults.
|
||||
|
||||
public:
|
||||
FsmGraph() VL_MT_DISABLED
|
||||
: m_resetVertexp{new FsmPseudoVertex{this, FsmVertex::Kind::RESET_ANY, "ANY"}}
|
||||
, m_defaultVertexp{new FsmPseudoVertex{this, FsmVertex::Kind::DEFAULT_ANY, "default"}} {}
|
||||
|
||||
AstScope* scopep() const { return m_scopep; }
|
||||
void scopep(AstScope* scopep) { m_scopep = scopep; }
|
||||
AstAlways* alwaysp() const { return m_alwaysp; }
|
||||
void alwaysp(AstAlways* alwaysp) { m_alwaysp = alwaysp; }
|
||||
const string& stateVarName() const { return m_stateVarName; }
|
||||
void stateVarName(const string& name) { m_stateVarName = name; }
|
||||
const string& stateVarInternalName() const { return m_stateVarInternalName; }
|
||||
void stateVarInternalName(const string& name) { m_stateVarInternalName = name; }
|
||||
AstVarScope* stateVarScopep() const { return m_stateVarScopep; }
|
||||
void stateVarScopep(AstVarScope* vscp) { m_stateVarScopep = vscp; }
|
||||
const std::vector<FsmSenDesc>& senses() const { return m_senses; }
|
||||
std::vector<FsmSenDesc>& senses() { return m_senses; }
|
||||
const FsmResetCondDesc& resetCond() const { return m_resetCond; }
|
||||
FsmResetCondDesc& resetCond() { return m_resetCond; }
|
||||
bool hasResetCond() const { return m_hasResetCond; }
|
||||
void hasResetCond(bool flag) { m_hasResetCond = flag; }
|
||||
bool resetInclude() const { return m_resetInclude; }
|
||||
void resetInclude(bool flag) { m_resetInclude = flag; }
|
||||
bool inclCond() const { return m_inclCond; }
|
||||
void inclCond(bool flag) { m_inclCond = flag; }
|
||||
FileLine* fileline() const { return m_flp; }
|
||||
void fileline(FileLine* flp) { m_flp = flp; }
|
||||
|
||||
FsmStateVertex* addStateVertex(string label, int value) VL_MT_DISABLED {
|
||||
FsmStateVertex* const vertexp = new FsmStateVertex{this, label, value};
|
||||
m_stateVertices.emplace(value, vertexp);
|
||||
return vertexp;
|
||||
}
|
||||
FsmPseudoVertex* resetAnyVertex() VL_MT_DISABLED { return m_resetVertexp; }
|
||||
FsmPseudoVertex* defaultAnyVertex() VL_MT_DISABLED { return m_defaultVertexp; }
|
||||
FsmArcEdge* addArc(int fromValue, int toValue, bool isReset, bool isCond, bool isDefault,
|
||||
FileLine* flp) VL_MT_DISABLED {
|
||||
FsmStateVertex* const top = m_stateVertices.at(toValue);
|
||||
FsmVertex* fromp = nullptr;
|
||||
if (isReset) {
|
||||
fromp = resetAnyVertex();
|
||||
} else if (isDefault) {
|
||||
fromp = defaultAnyVertex();
|
||||
} else {
|
||||
fromp = m_stateVertices.at(fromValue);
|
||||
}
|
||||
return new FsmArcEdge{this, fromp, top, isReset, isCond, isDefault, flp};
|
||||
}
|
||||
|
||||
string name() const VL_MT_SAFE {
|
||||
return "FSM "
|
||||
+ (m_stateVarName.empty() ? (m_stateVarScopep ? m_stateVarScopep->name() : "")
|
||||
: m_stateVarName);
|
||||
}
|
||||
string dumpTag(size_t index) const {
|
||||
string tag = stateVarInternalName();
|
||||
for (char& ch : tag) {
|
||||
if (!std::isalnum(static_cast<unsigned char>(ch))) ch = '_';
|
||||
}
|
||||
return "fsm_" + cvtToStr(index) + "_" + tag;
|
||||
}
|
||||
};
|
||||
|
||||
struct DetectedFsm final {
|
||||
std::unique_ptr<FsmGraph> graphp; // Extracted graph for one detected FSM candidate.
|
||||
};
|
||||
using DetectedFsmMap = std::map<string, DetectedFsm>;
|
||||
|
||||
// Local shared state between the two adjacent FSM coverage phases. Detection
|
||||
// fills this with recovered FSM graphs; lowering consumes the completed graphs
|
||||
// immediately afterward without needing any AST serialization bridge.
|
||||
class FsmState final {
|
||||
// All detected FSMs keyed by state varscope name. This is the only bridge
|
||||
// between the adjacent detect and lower phases, so the second phase never
|
||||
// needs to rediscover or serialize the extracted machine.
|
||||
DetectedFsmMap m_fsms;
|
||||
|
||||
public:
|
||||
DetectedFsmMap& fsms() { return m_fsms; }
|
||||
const DetectedFsmMap& fsms() const { return m_fsms; }
|
||||
};
|
||||
|
||||
// Detection runs while the original clocked/case structure is still intact and
|
||||
// populates graph-backed FSM models without mutating the tree mid-traversal.
|
||||
// This pass is intentionally conservative: for this PR we only lock down the
|
||||
// small set of transition/selector forms that are already stable in the
|
||||
// normalized AST we see here. The remaining reject branches are therefore
|
||||
// mostly future-feature boundaries, not accidental dead code.
|
||||
class FsmDetectVisitor final : public VNVisitor {
|
||||
// STATE - for current visit position (use VL_RESTORER)
|
||||
FsmState& m_state;
|
||||
AstScope* m_scopep = nullptr;
|
||||
|
||||
// METHODS
|
||||
// Enum-backed FSMs may be wrapped in refs/typedefs; normalize to the
|
||||
// underlying enum type before deciding whether a case is a candidate.
|
||||
static AstNodeDType* unwrapEnumCandidate(AstNodeDType* dtypep) {
|
||||
return dtypep->skipRefToEnump();
|
||||
}
|
||||
|
||||
// Reset arcs are only modeled for the simple signal form that survives to
|
||||
// this pass after earlier normalization.
|
||||
static bool isSimpleResetCond(AstNodeExpr* condp) {
|
||||
return VN_IS(condp, VarRef);
|
||||
}
|
||||
|
||||
// Normalize the reset condition into a compact description so the lowering
|
||||
// phase can regenerate the same predicate after detection. By the time
|
||||
// this pass runs, active-low source forms such as "!rst_n" have already
|
||||
// been canonicalized to a positive-condition if/else shape, so only a
|
||||
// plain VarRef survives here.
|
||||
static FsmResetCondDesc describeResetCond(AstNodeExpr* condp) {
|
||||
FsmResetCondDesc desc;
|
||||
if (AstVarRef* const vrefp = VN_CAST(condp, VarRef)) {
|
||||
desc.varScopep = vrefp->varScopep();
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
// Snapshot the original event control so the lowering phase can rebuild an
|
||||
// active block with the same edge semantics.
|
||||
static std::vector<FsmSenDesc> describeSenTree(AstSenTree* sentreep) {
|
||||
std::vector<FsmSenDesc> senses;
|
||||
for (AstSenItem* itemp = sentreep->sensesp(); itemp;
|
||||
itemp = VN_AS(itemp->nextp(), SenItem)) {
|
||||
AstNodeVarRef* const vrefp = itemp->varrefp();
|
||||
if (!vrefp) continue;
|
||||
FsmSenDesc desc;
|
||||
desc.edgeType = itemp->edgeType().m_e;
|
||||
desc.varScopep = vrefp->varScopep();
|
||||
senses.push_back(desc);
|
||||
}
|
||||
return senses;
|
||||
}
|
||||
|
||||
// Ignore existing coverage increments so FSM detection sees the user logic
|
||||
// rather than other instrumentation already attached to the block.
|
||||
static bool isIgnorableStmt(AstNode* nodep) { return VN_IS(nodep, CoverInc); }
|
||||
|
||||
// Conservative extractor: only treat a branch as simple when exactly one
|
||||
// non-coverage statement remains after unwrapping. Richer multi-statement
|
||||
// or control-flow forms are intentionally left for follow-on FSM-detection
|
||||
// work instead of being partially inferred here.
|
||||
static AstNode* singleMeaningfulStmt(AstNode* stmtp) {
|
||||
AstNode* resultp = nullptr;
|
||||
for (AstNode* nodep = stmtp; nodep; nodep = nodep->nextp()) {
|
||||
if (isIgnorableStmt(nodep)) continue;
|
||||
if (resultp) return nullptr;
|
||||
resultp = nodep;
|
||||
}
|
||||
return resultp;
|
||||
}
|
||||
|
||||
// Recognize the direct "state <= X" form that gives us an unambiguous arc
|
||||
// target without needing deeper control-flow reasoning. Branches that fall
|
||||
// out here represent currently unsupported next-state shapes rather than
|
||||
// bugs in the implemented subset.
|
||||
static AstNodeAssign* directStateAssign(AstNode* stmtp, AstVarScope* stateVscp) {
|
||||
AstNode* const nodep = singleMeaningfulStmt(stmtp);
|
||||
if (!nodep) return nullptr;
|
||||
AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign);
|
||||
if (!assp) return nullptr;
|
||||
AstVarRef* const vrefp = VN_CAST(assp->lhsp(), VarRef);
|
||||
if (!vrefp || vrefp->varScopep() != stateVscp) return nullptr;
|
||||
return assp;
|
||||
}
|
||||
|
||||
// Prefer enum labels in reports; fall back to synthetic labels for forced
|
||||
// non-enum FSMs so coverage points remain human-readable.
|
||||
static string labelForValue(const std::unordered_map<int, string>& labels, int value) {
|
||||
const std::unordered_map<int, string>::const_iterator it = labels.find(value);
|
||||
return it == labels.end() ? ("S" + cvtToStr(value)) : it->second;
|
||||
}
|
||||
|
||||
// The extractor only models constant-valued state transitions, and by the
|
||||
// time detect runs those values have already been constant-folded.
|
||||
static bool exprConstValue(AstNodeExpr* exprp, int& value) {
|
||||
if (AstConst* const constp = VN_CAST(exprp, Const)) {
|
||||
value = constp->toSInt();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enum-backed FSMs should only transition to values that were interned as
|
||||
// known states. If a constant transition targets some other encoding, warn
|
||||
// and skip FSM instrumentation for that edge rather than silently dropping
|
||||
// it or turning optional coverage into a hard compile failure.
|
||||
static bool validateKnownStateValue(AstNode* nodep,
|
||||
const std::unordered_map<int, string>& labels, int value) {
|
||||
if (labels.find(value) != labels.end()) return true;
|
||||
nodep->v3warn(COVERIGN,
|
||||
"Ignoring unsupported: FSM coverage on enum state transitions "
|
||||
"that assign a constant not present in the declared enum");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract supported case-item transitions in one place so the conservative
|
||||
// policy for direct and ternary forms stays consistent. The false exits in
|
||||
// this helper are deliberate subset boundaries: they document shapes we do
|
||||
// not yet model in this PR and that future FSM-detection work may widen.
|
||||
static bool emitCaseItemArcs(FsmGraph& graph, AstCaseItem* itemp, AstVarScope* stateVscp,
|
||||
const std::unordered_map<int, string>& labels, bool inclCond) {
|
||||
std::vector<std::pair<string, int>> froms;
|
||||
if (itemp->isDefault()) {
|
||||
if (!inclCond) return false;
|
||||
froms.emplace_back("default", 0);
|
||||
} else {
|
||||
for (AstNodeExpr* condp = itemp->condsp(); condp;
|
||||
condp = VN_CAST(condp->nextp(), NodeExpr)) {
|
||||
int value = 0;
|
||||
if (!exprConstValue(condp, value)) continue;
|
||||
froms.emplace_back(labelForValue(labels, value), value);
|
||||
}
|
||||
if (froms.empty()) return false;
|
||||
}
|
||||
|
||||
if (AstNodeAssign* const assp = directStateAssign(itemp->stmtsp(), stateVscp)) {
|
||||
int toValue = 0;
|
||||
if (exprConstValue(assp->rhsp(), toValue)) {
|
||||
if (!validateKnownStateValue(assp, labels, toValue)) return true;
|
||||
for (const std::pair<string, int>& from : froms) {
|
||||
graph.addArc(from.second, toValue, false, false, itemp->isDefault(),
|
||||
assp->fileline());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (AstCond* const condp = VN_CAST(assp->rhsp(), Cond)) {
|
||||
int thenValue = 0;
|
||||
int elseValue = 0;
|
||||
const bool simpleCond = exprConstValue(condp->thenp(), thenValue)
|
||||
&& exprConstValue(condp->elsep(), elseValue);
|
||||
if (simpleCond || inclCond) {
|
||||
if (!validateKnownStateValue(condp->thenp(), labels, thenValue)) return true;
|
||||
if (!validateKnownStateValue(condp->elsep(), labels, elseValue)) return true;
|
||||
for (const int branchValue : {thenValue, elseValue}) {
|
||||
for (const std::pair<string, int>& from : froms) {
|
||||
graph.addArc(from.second, branchValue, false, true,
|
||||
itemp->isDefault(), assp->fileline());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset transitions are described separately because they live in the reset
|
||||
// branch outside the steady-state case statement.
|
||||
static void addResetArcs(FsmGraph& graph, AstNode* stmtsp, AstVarScope* stateVscp,
|
||||
const std::unordered_map<int, string>& labels) {
|
||||
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
|
||||
if (AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign)) {
|
||||
AstVarRef* const vrefp = VN_CAST(assp->lhsp(), VarRef);
|
||||
int toValue = 0;
|
||||
if (vrefp && vrefp->varScopep() == stateVscp && exprConstValue(assp->rhsp(), toValue)) {
|
||||
if (!validateKnownStateValue(assp, labels, toValue)) continue;
|
||||
graph.addArc(0, toValue, true, false, false, assp->fileline());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Turn one candidate case statement into the graph representation that the
|
||||
// later lowering phase will consume directly, while reviewers can still
|
||||
// inspect the extracted machine via DOT dumps.
|
||||
void processCase(AstCase* casep, AstNodeExpr* resetCondp, AstAlways* alwaysp) {
|
||||
AstVarRef* const selp = VN_CAST(casep->exprp(), VarRef);
|
||||
if (!selp) return;
|
||||
AstVarScope* const stateVscp = selp->varScopep();
|
||||
AstVar* const stateVarp = selp->varp();
|
||||
AstEnumDType* enump = VN_CAST(unwrapEnumCandidate(stateVscp->dtypep()), EnumDType);
|
||||
if (!enump) enump = VN_CAST(unwrapEnumCandidate(stateVarp->dtypep()), EnumDType);
|
||||
const bool forced = stateVarp->attrFsmState();
|
||||
if (!enump && !forced) return;
|
||||
|
||||
std::vector<std::pair<string, int>> states;
|
||||
std::unordered_map<int, string> labels;
|
||||
if (enump) {
|
||||
if (stateVscp->width() < 1 || stateVscp->width() > 32) {
|
||||
casep->v3warn(COVERIGN,
|
||||
"Ignoring unsupported: FSM coverage on enum-typed state "
|
||||
"variables wider than 32 bits");
|
||||
return;
|
||||
}
|
||||
for (AstEnumItem* itemp = enump->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), EnumItem)) {
|
||||
const AstConst* const constp = VN_AS(itemp->valuep(), Const);
|
||||
const int value = constp->toSInt();
|
||||
states.emplace_back(itemp->name(), value);
|
||||
labels.emplace(value, itemp->name());
|
||||
}
|
||||
if (states.size() < 2) return;
|
||||
} else {
|
||||
const int width = stateVarp->width();
|
||||
if (width < 1 || width >= 31) return;
|
||||
const unsigned stateCount = 1U << width;
|
||||
for (unsigned value = 0; value < stateCount; ++value) {
|
||||
const string label = "S" + cvtToStr(value);
|
||||
states.emplace_back(label, static_cast<int>(value));
|
||||
labels.emplace(static_cast<int>(value), label);
|
||||
}
|
||||
}
|
||||
|
||||
DetectedFsm& entry = m_state.fsms()[stateVscp->name()];
|
||||
if (!entry.graphp) {
|
||||
entry.graphp.reset(new FsmGraph{});
|
||||
entry.graphp->scopep(m_scopep);
|
||||
entry.graphp->alwaysp(alwaysp);
|
||||
entry.graphp->stateVarName(stateVscp->prettyName());
|
||||
entry.graphp->stateVarInternalName(stateVarp->name());
|
||||
entry.graphp->stateVarScopep(stateVscp);
|
||||
entry.graphp->senses() = describeSenTree(alwaysp->sentreep());
|
||||
entry.graphp->resetCond() = describeResetCond(resetCondp);
|
||||
entry.graphp->hasResetCond(entry.graphp->resetCond().varScopep != nullptr);
|
||||
entry.graphp->resetInclude(stateVarp->attrFsmResetArc());
|
||||
entry.graphp->inclCond(stateVarp->attrFsmArcInclCond());
|
||||
entry.graphp->fileline(casep->fileline());
|
||||
for (const std::pair<string, int>& state : states) {
|
||||
entry.graphp->addStateVertex(state.first, state.second);
|
||||
}
|
||||
}
|
||||
for (AstCaseItem* itemp = casep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) {
|
||||
emitCaseItemArcs(*entry.graphp, itemp, stateVscp, labels, entry.graphp->inclCond());
|
||||
}
|
||||
}
|
||||
|
||||
// Find the first supported FSM candidate in a clocked always block, warn on
|
||||
// additional candidates, and attach reset arcs when present. Candidate
|
||||
// filtering stays narrow on purpose: we prefer to skip ambiguous shapes now
|
||||
// and expand detection in a later PR rather than over-infer coverage from
|
||||
// forms we do not yet model confidently.
|
||||
void processAlways(AstAlways* alwaysp) {
|
||||
if (!alwaysp->sentreep() || !alwaysp->sentreep()->hasClocked()) return;
|
||||
std::vector<std::pair<AstCase*, AstNodeExpr*>> candidates;
|
||||
AstNode* stmtsp = alwaysp->stmtsp();
|
||||
AstIf* const firstIfp = VN_CAST(stmtsp, If);
|
||||
if (firstIfp) {
|
||||
if (AstCase* const casep = VN_CAST(firstIfp->elsesp(), Case)) {
|
||||
candidates.emplace_back(casep, isSimpleResetCond(firstIfp->condp()) ? firstIfp->condp()
|
||||
: nullptr);
|
||||
}
|
||||
}
|
||||
for (AstNode* nodep = stmtsp; nodep; nodep = nodep->nextp()) {
|
||||
if (AstCase* const casep = VN_CAST(nodep, Case)) candidates.emplace_back(casep, nullptr);
|
||||
}
|
||||
if (candidates.empty()) return;
|
||||
|
||||
AstVarScope* firstVscp = nullptr;
|
||||
for (const std::pair<AstCase*, AstNodeExpr*>& cand : candidates) {
|
||||
AstVarRef* const selp = VN_CAST(cand.first->exprp(), VarRef);
|
||||
AstVarScope* const vscp = selp ? selp->varScopep() : nullptr;
|
||||
if (!vscp) continue;
|
||||
if (!firstVscp) {
|
||||
firstVscp = vscp;
|
||||
processCase(cand.first, cand.second, alwaysp);
|
||||
} else if (vscp != firstVscp) {
|
||||
cand.first->v3warn(FSMMULTI,
|
||||
"FSM coverage: multiple enum-typed case statements found in "
|
||||
"the same always block. Only the first candidate will be "
|
||||
"instrumented.");
|
||||
} else {
|
||||
cand.first->v3warn(
|
||||
COVERIGN,
|
||||
"Ignoring unsupported: FSM coverage on multiple supported case "
|
||||
"statements found in the same always block. Only the first "
|
||||
"candidate will be instrumented.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!(firstIfp && firstVscp)) return;
|
||||
const DetectedFsmMap& fsms = m_state.fsms();
|
||||
const DetectedFsmMap::const_iterator it = fsms.find(firstVscp->name());
|
||||
if (it == fsms.end()) return;
|
||||
FsmGraph* const graphp = it->second.graphp.get();
|
||||
if (!graphp->hasResetCond()) return;
|
||||
std::unordered_map<int, string> labels;
|
||||
for (const V3GraphVertex& vtx : graphp->vertices()) {
|
||||
const FsmVertex* const vertexp = vtx.as<FsmVertex>();
|
||||
if (!vertexp->isState()) continue;
|
||||
labels.emplace(vertexp->value(), vertexp->label());
|
||||
}
|
||||
addResetArcs(*graphp, firstIfp->thensp(), firstVscp, labels);
|
||||
}
|
||||
|
||||
// Track the current scope so each detected FSM records the module/scope
|
||||
// where instrumentation must later be inserted.
|
||||
void visit(AstScope* nodep) override {
|
||||
VL_RESTORER(m_scopep);
|
||||
m_scopep = nodep;
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
|
||||
// FSM extraction only cares about clocked always processes.
|
||||
void visit(AstAlways* nodep) override { processAlways(nodep); }
|
||||
|
||||
// Continue the walk through the rest of the design hierarchy.
|
||||
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
// Collect all FSM graphs into the shared local state before the lowering
|
||||
// phase starts mutating the AST with coverage machinery.
|
||||
FsmDetectVisitor(FsmState& state, AstNetlist* rootp)
|
||||
: m_state{state} {
|
||||
iterate(rootp);
|
||||
}
|
||||
};
|
||||
|
||||
// Lower the completed FSM graphs into the concrete coverage declarations,
|
||||
// previous-state tracking, and pre/post-triggered instrumentation that the
|
||||
// runtime uses to record state and transition coverage.
|
||||
class FsmLowerVisitor final {
|
||||
// STATE - across all visitors
|
||||
const FsmState& m_state;
|
||||
|
||||
// METHODS
|
||||
// Rebuild a state-typed constant using the tracked state variable
|
||||
// width/sign so emitted comparisons match the original representation.
|
||||
static AstConst* makeStateConst(FileLine* flp, AstVarScope* vscp, int value) {
|
||||
V3Number num{flp, vscp->width(), static_cast<uint32_t>(value)};
|
||||
num.isSigned(vscp->dtypep()->isSigned());
|
||||
return new AstConst{flp, num};
|
||||
}
|
||||
|
||||
// Build guards incrementally without forcing callers to special-case the
|
||||
// first predicate; this keeps emitted state/arc conditions readable.
|
||||
static AstNodeExpr* andExpr(FileLine* flp, AstNodeExpr* lhsp, AstNodeExpr* rhsp) {
|
||||
if (!lhsp) return rhsp;
|
||||
return new AstLogAnd{flp, lhsp, rhsp};
|
||||
}
|
||||
|
||||
static AstNodeExpr* buildResetCond(FileLine* flp, AstVarScope* resetVscp,
|
||||
const FsmResetCondDesc&) {
|
||||
return new AstVarRef{flp, resetVscp, VAccess::READ};
|
||||
}
|
||||
|
||||
// Rebuild the original event control from the saved sense description so
|
||||
// post-state coverage sampling runs on the same triggering edges.
|
||||
static AstSenTree* buildSenTree(
|
||||
FileLine* flp, const std::vector<FsmSenDesc>& senses) {
|
||||
AstSenTree* const sentreep = new AstSenTree{flp, nullptr};
|
||||
for (const FsmSenDesc& sense : senses) {
|
||||
AstSenItem* const senItemp = new AstSenItem{
|
||||
flp, VEdgeType{sense.edgeType},
|
||||
new AstVarRef{flp, sense.varScopep, VAccess::READ}};
|
||||
sentreep->addSensesp(senItemp);
|
||||
}
|
||||
return sentreep;
|
||||
}
|
||||
|
||||
// Lower one fully detected FSM graph into the concrete coverage machinery
|
||||
// used by generated models: declarations, previous-state tracking, and the
|
||||
// pre/post-triggered increment logic for states and arcs.
|
||||
void buildOne(const FsmGraph& graph) {
|
||||
AstAlways* const alwaysp = graph.alwaysp();
|
||||
AstScope* const scopep = graph.scopep();
|
||||
AstVarScope* const stateVscp = graph.stateVarScopep();
|
||||
FileLine* const flp = graph.fileline();
|
||||
AstNodeModule* const modp = scopep->modp();
|
||||
AstNodeDType* const prevDTypep
|
||||
= scopep->findLogicDType(stateVscp->width(), stateVscp->width(),
|
||||
stateVscp->dtypep()->numeric());
|
||||
AstVarScope* const prevVscp
|
||||
= scopep->createTemp("__Vfsmcov_prev__" + stateVscp->varp()->shortName(), prevDTypep);
|
||||
// The saved previous-state temp crosses the scheduler's pre/post split
|
||||
// in the same way as Verilator's built-in NBA shadow variables, so keep
|
||||
// both vars marked as post-life participants for stable MT ordering.
|
||||
stateVscp->optimizeLifePost(true);
|
||||
prevVscp->optimizeLifePost(true);
|
||||
|
||||
AstActive* const initActivep
|
||||
= new AstActive{flp, "fsm-coverage-init",
|
||||
new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Initial{}}}};
|
||||
initActivep->senTreeStorep(initActivep->sentreep());
|
||||
// Seed the previous-state temp during initialization so the first
|
||||
// clock edge compares against a defined state value.
|
||||
initActivep->addStmtsp(new AstInitialStatic{
|
||||
flp, new AstAssign{flp, new AstVarRef{flp, prevVscp, VAccess::WRITE},
|
||||
new AstVarRef{flp, stateVscp, VAccess::READ}}});
|
||||
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);
|
||||
|
||||
for (const V3GraphVertex& vtx : graph.vertices()) {
|
||||
const FsmVertex* const vertexp = vtx.as<FsmVertex>();
|
||||
if (!vertexp->isState()) continue;
|
||||
const FsmStateVertex* const statep = vtx.as<FsmStateVertex>();
|
||||
// State coverage fires when the FSM enters a state from any other
|
||||
// value, so repeated self-holds do not count as new entries.
|
||||
AstCoverOtherDecl* const declp = new AstCoverOtherDecl{
|
||||
flp, "v_fsm_state/" + modp->prettyName(),
|
||||
graph.stateVarName() + "::" + statep->label(), "", 0, graph.stateVarName(), "",
|
||||
statep->label()};
|
||||
declp->hier(scopep->prettyName());
|
||||
modp->addStmtsp(declp);
|
||||
AstNodeExpr* const guardp
|
||||
= andExpr(flp,
|
||||
new AstNeq{flp, new AstVarRef{flp, prevVscp, VAccess::READ},
|
||||
makeStateConst(flp, prevVscp, statep->value())},
|
||||
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
|
||||
makeStateConst(flp, stateVscp, statep->value())});
|
||||
covPostp->addStmtsp(new AstIf{flp, guardp, new AstCoverInc{flp, declp}});
|
||||
}
|
||||
|
||||
for (const V3GraphVertex& vtx : graph.vertices()) {
|
||||
const FsmVertex* const fromVertexp = vtx.as<FsmVertex>();
|
||||
for (const V3GraphEdge& edge : fromVertexp->outEdges()) {
|
||||
const FsmArcEdge* const arcp = edge.as<FsmArcEdge>();
|
||||
const FsmStateVertex* const toStatep = arcp->top()->as<FsmStateVertex>();
|
||||
// Arc coverage mirrors the extracted graph exactly, including
|
||||
// reset and synthetic-default sources, so reports match the
|
||||
// reviewer-visible graph dump and the user-visible annotation.
|
||||
const string resetTag
|
||||
= arcp->isReset() ? (graph.resetInclude() ? "[reset_include]" : "[reset]") : "";
|
||||
const string fsmTag = arcp->isReset() ? (graph.resetInclude() ? "reset_include"
|
||||
: "reset")
|
||||
: arcp->isDefault() ? "default"
|
||||
: "";
|
||||
AstCoverOtherDecl* const declp = new AstCoverOtherDecl{
|
||||
flp, "v_fsm_arc/" + modp->prettyName(),
|
||||
graph.stateVarName() + "::" + fromVertexp->label() + "->" + toStatep->label()
|
||||
+ resetTag,
|
||||
"",
|
||||
0,
|
||||
graph.stateVarName(),
|
||||
fromVertexp->label(),
|
||||
toStatep->label(),
|
||||
fsmTag};
|
||||
declp->hier(scopep->prettyName());
|
||||
modp->addStmtsp(declp);
|
||||
AstNodeExpr* guardp = nullptr;
|
||||
if (fromVertexp->isResetAny()) {
|
||||
// Reset arcs are modeled as pseudo-source edges in the
|
||||
// graph, then reconstructed here into the original simple
|
||||
// reset predicate combined with the destination state.
|
||||
guardp = buildResetCond(flp, graph.resetCond().varScopep, graph.resetCond());
|
||||
guardp = andExpr(flp, guardp,
|
||||
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
|
||||
makeStateConst(flp, stateVscp, toStatep->value())});
|
||||
} else if (fromVertexp->isDefaultAny()) {
|
||||
// Synthetic default arcs mean "none of the explicit
|
||||
// source states matched", so rebuild that as a conjunction
|
||||
// of previous-state != known-state tests.
|
||||
for (const V3GraphVertex& stateVtx : graph.vertices()) {
|
||||
const FsmVertex* const stateVertexp = stateVtx.as<FsmVertex>();
|
||||
if (!stateVertexp->isState()) continue;
|
||||
guardp = andExpr(
|
||||
flp, guardp,
|
||||
new AstNeq{flp, new AstVarRef{flp, prevVscp, VAccess::READ},
|
||||
makeStateConst(flp, prevVscp, stateVertexp->value())});
|
||||
}
|
||||
guardp = andExpr(flp, guardp,
|
||||
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
|
||||
makeStateConst(flp, stateVscp, toStatep->value())});
|
||||
} else {
|
||||
guardp = andExpr(
|
||||
flp,
|
||||
new AstEq{flp, new AstVarRef{flp, prevVscp, VAccess::READ},
|
||||
makeStateConst(flp, prevVscp, fromVertexp->value())},
|
||||
new AstEq{flp, new AstVarRef{flp, stateVscp, VAccess::READ},
|
||||
makeStateConst(flp, stateVscp, toStatep->value())});
|
||||
}
|
||||
covPostp->addStmtsp(new AstIf{flp, guardp, new AstCoverInc{flp, declp}});
|
||||
}
|
||||
}
|
||||
|
||||
AstSenTree* const sentreep = buildSenTree(flp, graph.senses());
|
||||
AstActive* const activep = new AstActive{flp, "fsm-coverage", sentreep};
|
||||
activep->senTreeStorep(sentreep);
|
||||
scopep->addBlocksp(activep);
|
||||
activep->addStmtsp(covPostp);
|
||||
}
|
||||
|
||||
public:
|
||||
// CONSTRUCTORS
|
||||
// Lower every detected FSM graph from the shared local state into
|
||||
// concrete coverage instrumentation while the saved scoped pointers are
|
||||
// still valid in the same pass.
|
||||
explicit FsmLowerVisitor(const FsmState& state)
|
||||
: m_state{state} {
|
||||
for (const std::pair<const string, DetectedFsm>& it : m_state.fsms()) {
|
||||
buildOne(*it.second.graphp);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void V3FsmDetect::detect(AstNetlist* rootp) {
|
||||
UINFO(2, __FUNCTION__ << ":");
|
||||
FsmState state;
|
||||
// Phase 1: recover each supported FSM into a complete graph while the
|
||||
// original clocked/case structure is still easy to recognize.
|
||||
FsmDetectVisitor detect{state, rootp};
|
||||
if (dumpGraphLevel() >= 6) {
|
||||
size_t index = 0;
|
||||
for (const std::pair<const string, DetectedFsm>& it : state.fsms()) {
|
||||
it.second.graphp->dumpDotFilePrefixed(it.second.graphp->dumpTag(index++));
|
||||
}
|
||||
}
|
||||
// Phase 2: lower the completed in-memory graph state immediately, without
|
||||
// crossing into another pass owner or serializing through AST placeholders.
|
||||
{ FsmLowerVisitor lower{state}; }
|
||||
V3Global::dumpCheckGlobalTree("fsm-detect", 0, dumpTreeEitherLevel() >= 3);
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||||
//*************************************************************************
|
||||
// DESCRIPTION: Verilator: FSM coverage detect pass
|
||||
//
|
||||
// Code available from: https://verilator.org
|
||||
//
|
||||
//*************************************************************************
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#ifndef VERILATOR_V3FSMDETECT_H_
|
||||
#define VERILATOR_V3FSMDETECT_H_
|
||||
|
||||
#include "config_build.h"
|
||||
#include "verilatedos.h"
|
||||
|
||||
class AstNetlist;
|
||||
|
||||
class V3FsmDetect final {
|
||||
public:
|
||||
// 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.
|
||||
static void detect(AstNetlist* rootp) VL_MT_DISABLED;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -611,6 +611,18 @@ class LinkParseVisitor final : public VNVisitor {
|
|||
m_varp->attrSplitVar(true);
|
||||
}
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
} else if (nodep->attrType() == VAttrType::VAR_FSM_ARC_INCLUDE_COND) {
|
||||
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
||||
m_varp->attrFsmArcInclCond(true);
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
} else if (nodep->attrType() == VAttrType::VAR_FSM_RESET_ARC) {
|
||||
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
||||
m_varp->attrFsmResetArc(true);
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
} else if (nodep->attrType() == VAttrType::VAR_FSM_STATE) {
|
||||
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
||||
m_varp->attrFsmState(true);
|
||||
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
||||
} else if (nodep->attrType() == VAttrType::VAR_SC_BIGUINT) {
|
||||
UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable");
|
||||
m_varp->attrScBigUint(true);
|
||||
|
|
|
|||
|
|
@ -1352,6 +1352,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
|
|||
DECL_OPTION("-coverage", CbOnOff, [this](bool flag) { coverage(flag); });
|
||||
DECL_OPTION("-coverage-expr", OnOff, &m_coverageExpr);
|
||||
DECL_OPTION("-coverage-expr-max", Set, &m_coverageExprMax);
|
||||
DECL_OPTION("-coverage-fsm", OnOff, &m_coverageFsm);
|
||||
DECL_OPTION("-coverage-line", OnOff, &m_coverageLine);
|
||||
DECL_OPTION("-coverage-max-width", Set, &m_coverageMaxWidth);
|
||||
DECL_OPTION("-coverage-toggle", OnOff, &m_coverageToggle);
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ private:
|
|||
bool m_build = false; // main switch: --build
|
||||
bool m_context = true; // main switch: --Wcontext
|
||||
bool m_coverageExpr = false; // main switch: --coverage-expr
|
||||
bool m_coverageFsm = false; // main switch: --coverage-fsm
|
||||
bool m_coverageLine = false; // main switch: --coverage-block
|
||||
bool m_coverageToggle = false; // main switch: --coverage-toggle
|
||||
bool m_coverageUnderscore = false; // main switch: --coverage-underscore
|
||||
|
|
@ -447,7 +448,8 @@ private:
|
|||
void optimize(int level);
|
||||
void showVersion(bool verbose);
|
||||
void coverage(bool flag) {
|
||||
m_coverageLine = m_coverageToggle = m_coverageExpr = m_coverageUser = flag;
|
||||
m_coverageLine = m_coverageToggle = m_coverageExpr = m_coverageFsm = m_coverageUser
|
||||
= flag;
|
||||
}
|
||||
static bool suffixed(const string& sw, const char* arg);
|
||||
static string parseFileArg(const string& optdir, const string& relfilename);
|
||||
|
|
@ -508,9 +510,19 @@ public:
|
|||
void buildDepBin(const string& flag) { m_buildDepBin = flag; }
|
||||
bool context() const VL_MT_SAFE { return m_context; }
|
||||
bool coverage() const VL_MT_SAFE {
|
||||
// Any enabled coverage kind, including FSM coverage. Code generation
|
||||
// and runtime support should generally query this accessor.
|
||||
return m_coverageLine || m_coverageToggle || m_coverageExpr || m_coverageUser
|
||||
|| m_coverageFsm;
|
||||
}
|
||||
bool coverageNonFsm() const VL_MT_SAFE {
|
||||
// The broad line/toggle/expr/user coverage transforms use this
|
||||
// accessor. FSM coverage shares the overall coverage umbrella, but its
|
||||
// extraction still happens through a separate early-recognition path.
|
||||
return m_coverageLine || m_coverageToggle || m_coverageExpr || m_coverageUser;
|
||||
}
|
||||
bool coverageExpr() const { return m_coverageExpr; }
|
||||
bool coverageFsm() const { return m_coverageFsm; }
|
||||
bool coverageLine() const { return m_coverageLine; }
|
||||
bool coverageToggle() const { return m_coverageToggle; }
|
||||
bool coverageUnderscore() const { return m_coverageUnderscore; }
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
#include "V3Expand.h"
|
||||
#include "V3File.h"
|
||||
#include "V3Force.h"
|
||||
#include "V3FsmDetect.h"
|
||||
#include "V3Fork.h"
|
||||
#include "V3FuncOpt.h"
|
||||
#include "V3Gate.h"
|
||||
|
|
@ -232,9 +233,11 @@ static void process() {
|
|||
v3Global.vlExit(0);
|
||||
}
|
||||
|
||||
// Coverage insertion
|
||||
// Before we do dead code elimination and inlining, or we'll lose it.
|
||||
if (v3Global.opt.coverage()) V3Coverage::coverage(v3Global.rootp());
|
||||
// Insert generic non-FSM coverage before dead code elimination and
|
||||
// inlining, or those opportunities may be optimized away. FSM
|
||||
// coverage is handled later in V3FsmDetect, after scoping has created
|
||||
// the AST context needed to recover and lower FSMs reliably.
|
||||
if (v3Global.opt.coverageNonFsm()) V3Coverage::coverage(v3Global.rootp());
|
||||
|
||||
// Resolve randsequence if they are used by the design
|
||||
if (v3Global.useRandSequence()) V3RandSequence::randSequenceNetlist(v3Global.rootp());
|
||||
|
|
@ -347,6 +350,12 @@ static void process() {
|
|||
// No more AstAlias after linkDotScope
|
||||
V3Scope::scopeAll(v3Global.rootp());
|
||||
V3LinkDot::linkDotScope(v3Global.rootp());
|
||||
// FSM coverage needs scopes, but should otherwise run as early as
|
||||
// possible before later lowering rewrites user-visible clocked
|
||||
// case structure. This entry point runs two adjacent phases:
|
||||
// detect into local graph state, then lower that completed state
|
||||
// into the concrete coverage machinery.
|
||||
if (v3Global.opt.coverageFsm()) V3FsmDetect::detect(v3Global.rootp());
|
||||
|
||||
// Relocate classes (after linkDot)
|
||||
V3Class::classAll(v3Global.rootp());
|
||||
|
|
@ -428,8 +437,9 @@ static void process() {
|
|||
"This may cause ordering problems.");
|
||||
}
|
||||
|
||||
// Combine COVERINCs with duplicate terms
|
||||
if (v3Global.opt.coverage()) V3CoverageJoin::coverageJoin(v3Global.rootp());
|
||||
// Combine generic COVERINCs with duplicate terms. FSM coverage is
|
||||
// already lowered separately inside V3FsmDetect.
|
||||
if (v3Global.opt.coverageNonFsm()) V3CoverageJoin::coverageJoin(v3Global.rootp());
|
||||
|
||||
// Remove unused vars
|
||||
V3Const::constifyAll(v3Global.rootp());
|
||||
|
|
@ -447,7 +457,7 @@ static void process() {
|
|||
}
|
||||
|
||||
// Create delayed assignments
|
||||
// This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step
|
||||
// This creates lots of duplicate ACTIVES so ActiveTop needs to be after this step.
|
||||
V3Delayed::delayedAll(v3Global.rootp());
|
||||
|
||||
// Make Active's on the top level.
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ void VlcOptions::parseOptsList(int argc, char** argv) {
|
|||
DECL_OPTION("-debug", CbCall, []() { V3Error::debugDefault(3); });
|
||||
DECL_OPTION("-debugi", CbVal, [](int v) { V3Error::debugDefault(v); });
|
||||
DECL_OPTION("-filter-type", Set, &m_filterType);
|
||||
DECL_OPTION("-include-reset-arcs", OnOff, &m_includeResetArcs);
|
||||
DECL_OPTION("-rank", OnOff, &m_rank);
|
||||
DECL_OPTION("-unlink", OnOff, &m_unlink);
|
||||
DECL_OPTION("-V", CbCall, []() {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class VlcOptions final {
|
|||
bool m_annotateAll = false; // main switch: --annotate-all
|
||||
int m_annotateMin = 10; // main switch: --annotate-min I<count>
|
||||
bool m_annotatePoints = false; // main switch: --annotate-points
|
||||
bool m_includeResetArcs = false; // main switch: --include-reset-arcs
|
||||
string m_filterType = "*"; // main switch: --filter-type
|
||||
VlStringSet m_readFiles; // main switch: --read
|
||||
bool m_rank = false; // main switch: --rank
|
||||
|
|
@ -67,6 +68,7 @@ public:
|
|||
int annotateMin() const { return m_annotateMin; }
|
||||
bool countOk(uint64_t count) const { return count >= static_cast<uint64_t>(m_annotateMin); }
|
||||
bool annotatePoints() const { return m_annotatePoints; }
|
||||
bool includeResetArcs() const { return m_includeResetArcs; }
|
||||
bool rank() const { return m_rank; }
|
||||
bool unlink() const { return m_unlink; }
|
||||
string writeFile() const { return m_writeFile; }
|
||||
|
|
|
|||
|
|
@ -70,6 +70,18 @@ public:
|
|||
return keyExtract(VL_CIK_THRESH, m_name.c_str());
|
||||
}
|
||||
string linescov() const { return keyExtract(VL_CIK_LINESCOV, m_name.c_str()); }
|
||||
bool isFsmState() const { return type() == "fsm_state"; }
|
||||
bool isFsmArc() const { return type() == "fsm_arc"; }
|
||||
// Arc-specific helpers are used after callers have already filtered to
|
||||
// FSM arc points, so they do not repeat the type check here.
|
||||
string fsmVarName() const { return keyExtract(VL_CIK_FSM_VAR, m_name.c_str()); }
|
||||
string fsmFromState() const { return keyExtract(VL_CIK_FSM_FROM, m_name.c_str()); }
|
||||
string fsmToState() const { return keyExtract(VL_CIK_FSM_TO, m_name.c_str()); }
|
||||
string fsmTag() const { return keyExtract(VL_CIK_FSM_TAG, m_name.c_str()); }
|
||||
bool isFsmResetInclude() const { return fsmTag() == "reset_include"; }
|
||||
bool isFsmResetArc() const { return fsmTag() == "reset"; }
|
||||
bool isFsmDefaultArc() const { return fsmTag() == "default"; }
|
||||
bool fsmIsReset() const { return isFsmResetArc() || isFsmResetInclude(); }
|
||||
int lineno() const {
|
||||
const string lineStr = keyExtract(VL_CIK_LINENO, m_name.c_str());
|
||||
return std::atoi(lineStr.c_str());
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -122,12 +124,18 @@ void VlcTop::writeInfo(const string& filename) {
|
|||
int branchesHit = 0;
|
||||
for (auto& li : lines) {
|
||||
VlcSourceCount& sc = li.second;
|
||||
os << "DA:" << sc.lineno() << "," << sc.maxCount() << "\n";
|
||||
const int num_branches = sc.points().size();
|
||||
if (num_branches == 1) continue;
|
||||
branchesFound += num_branches;
|
||||
int point_num = 0;
|
||||
uint64_t daCount = 0;
|
||||
std::vector<const VlcPoint*> infoPoints;
|
||||
for (const auto& point : sc.points()) {
|
||||
if (point->isFsmArc()) continue;
|
||||
daCount = std::max(daCount, point->count());
|
||||
if (!point->isFsmState()) infoPoints.push_back(point);
|
||||
}
|
||||
os << "DA:" << sc.lineno() << "," << daCount << "\n";
|
||||
if (infoPoints.size() <= 1) continue;
|
||||
branchesFound += static_cast<int>(infoPoints.size());
|
||||
int point_num = 0;
|
||||
for (const VlcPoint* point : infoPoints) {
|
||||
os << "BRDA:" << sc.lineno() << ",";
|
||||
os << "0,";
|
||||
os << point_num << ",";
|
||||
|
|
@ -328,6 +336,29 @@ void VlcTop::annotateOutputFiles(const string& dirname) {
|
|||
if (opt.annotatePoints()) {
|
||||
for (const auto& pit : sc.points()) pit->dumpAnnotate(os, opt.annotateMin());
|
||||
}
|
||||
bool printedFsmHeader = false;
|
||||
for (const auto& pit : sc.points()) {
|
||||
if (!pit->isFsmState() && !pit->isFsmArc()) continue;
|
||||
if (!printedFsmHeader) {
|
||||
os << " // [FSM coverage]\n";
|
||||
printedFsmHeader = true;
|
||||
}
|
||||
os << (opt.countOk(pit->count()) ? " " : "%");
|
||||
os << std::setfill('0') << std::setw(6) << pit->count() << " ";
|
||||
if (pit->isFsmState()) {
|
||||
os << "// [fsm_state " << pit->comment() << "]";
|
||||
if (pit->count() == 0) os << " *** UNCOVERED ***";
|
||||
os << "\n";
|
||||
} else if (pit->isFsmDefaultArc()) {
|
||||
os << "// [SYNTHETIC DEFAULT ARC: " << pit->comment() << "]\n";
|
||||
} else {
|
||||
os << "// [fsm_arc " << pit->comment() << "]";
|
||||
if (pit->fsmIsReset() && !opt.includeResetArcs()) {
|
||||
os << " [reset arc, excluded from %]";
|
||||
}
|
||||
os << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -848,6 +848,14 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
|
|||
"/*verilator sc_clock*/" { FL; yylval.fl->v3warn(DEPRECATED, "sc_clock is ignored"); FL_BRK; }
|
||||
"/*verilator sformat*/" { FL; return yVL_SFORMAT; }
|
||||
"/*verilator split_var*/" { FL; return yVL_SPLIT_VAR; }
|
||||
/* Experimental Verilator-specific FSM coverage controls. These names were
|
||||
* chosen to match the current extractor behavior, not a published synthesis
|
||||
* or simulator pragma standard, so they may evolve as we settle on longer-
|
||||
* term compatibility/aliasing.
|
||||
*/
|
||||
"/*verilator fsm_arc_include_cond*/" { FL; return yVL_FSM_ARC_INCL_COND; }
|
||||
"/*verilator fsm_reset_arc*/" { FL; return yVL_FSM_RESET_ARC; }
|
||||
"/*verilator fsm_state*/" { FL; return yVL_FSM_STATE; }
|
||||
"/*verilator tag"[^*]*"*/" { FL; yylval.strp = PARSEP->newString(V3ParseImp::lexParseTag(yytext));
|
||||
return yVL_TAG; }
|
||||
"/*verilator timing_off*/" { FL_FWD; PARSEP->lexFileline()->timingOn(false); FL_BRK; }
|
||||
|
|
|
|||
|
|
@ -804,6 +804,9 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
|
|||
%token<fl> yVL_SC_BV "/*verilator sc_bv*/"
|
||||
%token<fl> yVL_SFORMAT "/*verilator sformat*/"
|
||||
%token<fl> yVL_SPLIT_VAR "/*verilator split_var*/"
|
||||
%token<fl> yVL_FSM_ARC_INCL_COND "/*verilator fsm_arc_include_cond*/"
|
||||
%token<fl> yVL_FSM_RESET_ARC "/*verilator fsm_reset_arc*/"
|
||||
%token<fl> yVL_FSM_STATE "/*verilator fsm_state*/"
|
||||
%token<strp> yVL_TAG "/*verilator tag*/"
|
||||
%token<fl> yVL_UNROLL_DISABLE "/*verilator unroll_disable*/"
|
||||
%token<fl> yVL_UNROLL_FULL "/*verilator unroll_full*/"
|
||||
|
|
@ -3123,6 +3126,9 @@ sigAttr<nodep>:
|
|||
| yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; }
|
||||
| yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; }
|
||||
| yVL_SPLIT_VAR { $$ = new AstAttrOf{$1, VAttrType::VAR_SPLIT_VAR}; }
|
||||
| yVL_FSM_ARC_INCL_COND { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_ARC_INCLUDE_COND}; }
|
||||
| yVL_FSM_RESET_ARC { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_RESET_ARC}; }
|
||||
| yVL_FSM_STATE { $$ = new AstAttrOf{$1, VAttrType::VAR_FSM_STATE}; }
|
||||
;
|
||||
|
||||
rangeListE<nodeRangep>: // IEEE: [{packed_dimension}]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage basic test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S_IDLE = 2'd0,
|
||||
S_RUN = 2'd1,
|
||||
S_DONE = 2'd2,
|
||||
S_ERR = 2'd3
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
logic start;
|
||||
integer cyc;
|
||||
state_t state /*verilator fsm_reset_arc*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S_IDLE;
|
||||
end else begin
|
||||
%000004 case (state)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %]
|
||||
%000004 // [fsm_arc t.state::S_DONE->S_DONE]
|
||||
%000003 // [fsm_arc t.state::S_IDLE->S_IDLE]
|
||||
%000001 // [fsm_arc t.state::S_IDLE->S_RUN]
|
||||
%000001 // [fsm_arc t.state::S_RUN->S_DONE]
|
||||
%000001 // [fsm_state t.state::S_DONE]
|
||||
%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED ***
|
||||
%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.state::S_RUN]
|
||||
S_IDLE: if (start) state <= S_RUN; else state <= S_IDLE;
|
||||
S_RUN: state <= S_DONE;
|
||||
S_DONE: state <= S_DONE;
|
||||
default: state <= S_ERR;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage basic test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate",
|
||||
test.obj_dir + "/annotated",
|
||||
test.obj_dir + "/coverage.dat",
|
||||
],
|
||||
verilator_run=True)
|
||||
|
||||
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage basic test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S_IDLE = 2'd0,
|
||||
S_RUN = 2'd1,
|
||||
S_DONE = 2'd2,
|
||||
S_ERR = 2'd3
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
logic start;
|
||||
integer cyc;
|
||||
state_t state /*verilator fsm_reset_arc*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S_IDLE;
|
||||
end else begin
|
||||
case (state)
|
||||
S_IDLE: if (start) state <= S_RUN; else state <= S_IDLE;
|
||||
S_RUN: state <= S_DONE;
|
||||
S_DONE: state <= S_DONE;
|
||||
default: state <= S_ERR;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else test
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify it
|
||||
// under the terms of either the GNU Lesser General Public License Version 3
|
||||
// or the Perl Artistic License Version 2.0.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
module t(
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
logic sel;
|
||||
int cyc;
|
||||
state_t state /*verilator fsm_reset_arc*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end else begin
|
||||
%000003 case (state)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state::ANY->S0[reset_include]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.state::S0->S1]
|
||||
%000003 // [fsm_arc t.state::S0->S2]
|
||||
%000000 // [fsm_arc t.state::S1->S0]
|
||||
%000002 // [fsm_state t.state::S0]
|
||||
%000000 // [fsm_state t.state::S1] *** UNCOVERED ***
|
||||
%000003 // [fsm_state t.state::S2]
|
||||
S0: if (sel) state <= S1; else state <= S2;
|
||||
S1: state <= S0;
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else extraction test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
||||
test.execute()
|
||||
|
||||
# Use annotated-source output so the expected file captures both the extracted
|
||||
# FSM shape and the per-point hit counts.
|
||||
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()
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage begin-wrapped/if-else test
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify it
|
||||
// under the terms of either the GNU Lesser General Public License Version 3
|
||||
// or the Perl Artistic License Version 2.0.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
module t(
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
logic sel;
|
||||
int cyc;
|
||||
state_t state /*verilator fsm_reset_arc*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
sel = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) sel <= 1'b1;
|
||||
if (cyc == 3) sel <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end else begin
|
||||
case (state)
|
||||
S0: if (sel) state <= S1; else state <= S2;
|
||||
S1: state <= S0;
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM lowered coverage declaration dump test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
test.top_filename = "t/t_cover_fsm_styles.v"
|
||||
|
||||
# Dump the lowered AST so AstCoverOtherDecl::dump() sees FSM metadata-bearing
|
||||
# coverage declarations directly. This avoids JSON/schema coupling while still
|
||||
# covering the dump-side formatting for fv/ff/ft/fg.
|
||||
test.lint(v_flags=["--coverage-fsm", "--dump-tree"])
|
||||
|
||||
tree_files = [Path(filename) for filename in test.glob_some(test.obj_dir + "/*.tree")]
|
||||
tree_texts = [filename.read_text(encoding="utf8") for filename in tree_files]
|
||||
|
||||
assert any("COVEROTHERDECL" in text and " fv=t.state" in text for text in tree_texts)
|
||||
assert any(
|
||||
"COVEROTHERDECL" in text and " ff=ANY" in text and " ft=S0" in text and " fg=reset" in text
|
||||
for text in tree_texts
|
||||
)
|
||||
assert any(
|
||||
"COVEROTHERDECL" in text and " ff=default" in text and " ft=S0" in text and " fg=default"
|
||||
in text
|
||||
for text in tree_texts
|
||||
)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
%Warning-COVERIGN: t/t_cover_fsm_enum_bad.v:27:19: Ignoring unsupported: FSM coverage on enum state transitions that assign a constant not present in the declared enum
|
||||
27 | S0: state <= 2'd3;
|
||||
| ^~
|
||||
... For warning description see https://verilator.org/warn/COVERIGN?v=latest
|
||||
... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message.
|
||||
%Error: Exiting due to
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM enum transition bad-value test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
# When an enum-backed FSM assigns a constant that is not one of the declared
|
||||
# enum items, FSM coverage should warn and skip the unsupported edge rather
|
||||
# than turning optional coverage into a hard compile failure.
|
||||
test.lint(
|
||||
verilator_flags2=["--coverage-fsm"],
|
||||
fails=True)
|
||||
|
||||
test.file_grep(
|
||||
test.compile_log_filename,
|
||||
r'%Warning-COVERIGN: t/t_cover_fsm_enum_bad.v:27:19: Ignoring unsupported: FSM coverage '
|
||||
r'on enum state transitions that assign a constant not present in the declared enum')
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// DESCRIPTION: Verilator: FSM enum transition rejects unknown constant values
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0, S1
|
||||
} state_t;
|
||||
|
||||
state_t state;
|
||||
|
||||
// FSM coverage should reject a constant next-state value that is not one of
|
||||
// the declared enum items. This keeps graph construction aligned with the
|
||||
// enum-backed state set instead of silently dropping the transition.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end else begin
|
||||
case (state)
|
||||
/* verilator lint_off ENUMVALUE */
|
||||
S0: state <= 2'd3;
|
||||
/* verilator lint_on ENUMVALUE */
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
%Warning-COVERIGN: t/t_cover_fsm_enumwide_bad.v:25:7: Ignoring unsupported: FSM coverage on enum-typed state variables wider than 32 bits
|
||||
25 | case (state)
|
||||
| ^~~~
|
||||
... For warning description see https://verilator.org/warn/COVERIGN?v=latest
|
||||
... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message.
|
||||
%Error: Exiting due to
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM enum width limit test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
# FSM coverage currently stores recovered enum state values in the detector's
|
||||
# 32-bit internal representation, so wider enum-backed FSMs are rejected.
|
||||
test.lint(
|
||||
verilator_flags2=["--coverage-fsm"],
|
||||
fails=True)
|
||||
|
||||
test.file_grep(
|
||||
test.compile_log_filename,
|
||||
r'%Warning-COVERIGN: t/t_cover_fsm_enumwide_bad.v:25:7: Ignoring unsupported: '
|
||||
r'FSM coverage on enum-typed state variables wider than 32 bits')
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// DESCRIPTION: Verilator: FSM enum width limit rejects >32-bit enums
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
|
||||
typedef enum logic [32:0] {
|
||||
S0 = 33'd0,
|
||||
S1 = 33'd1
|
||||
} state_t;
|
||||
|
||||
state_t state;
|
||||
|
||||
// FSM coverage currently supports enum-backed state variables only up to
|
||||
// 32 bits wide, so this wider enum should be rejected at FSM detection time.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end else begin
|
||||
case (state)
|
||||
S0: state <= S1;
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage stays off without --coverage-fsm
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage-line'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.file_grep_not(test.obj_dir + "/coverage.dat", r"fsm_state")
|
||||
test.file_grep_not(test.obj_dir + "/coverage.dat", r"fsm_arc")
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage stays off without --coverage-fsm
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S_IDLE = 2'd0,
|
||||
S_RUN = 2'd1,
|
||||
S_DONE = 2'd2,
|
||||
S_ERR = 2'd3
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
logic start;
|
||||
integer cyc;
|
||||
state_t state /*verilator fsm_reset_arc*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 8) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S_IDLE;
|
||||
end else begin
|
||||
case (state)
|
||||
S_IDLE: if (start) state <= S_RUN; else state <= S_IDLE;
|
||||
S_RUN: state <= S_DONE;
|
||||
S_DONE: state <= S_DONE;
|
||||
default: state <= S_ERR;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage forced non-enum test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic [1:0] state /*verilator fsm_state*/;
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
rst = 1'b1;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= 2'd0;
|
||||
end else begin
|
||||
%000002 case (state)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000002 // [fsm_arc t.state::S0->S1]
|
||||
%000002 // [fsm_arc t.state::S1->S2]
|
||||
%000001 // [fsm_state t.state::S0]
|
||||
%000002 // [fsm_state t.state::S1]
|
||||
%000002 // [fsm_state t.state::S2]
|
||||
%000000 // [fsm_state t.state::S3] *** UNCOVERED ***
|
||||
2'd0: state <= 2'd1;
|
||||
2'd1: state <= 2'd2;
|
||||
default: state <= 2'd0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage forced non-enum test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
||||
|
||||
test.execute()
|
||||
|
||||
# Use annotated-source golden output so hit-count regressions are visible in the
|
||||
# expected file instead of being hidden behind coarse coverage.dat greps.
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate",
|
||||
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()
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage forced non-enum test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic [1:0] state /*verilator fsm_state*/;
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
rst = 1'b1;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= 2'd0;
|
||||
end else begin
|
||||
case (state)
|
||||
2'd0: state <= 2'd1;
|
||||
2'd1: state <= 2'd2;
|
||||
default: state <= 2'd0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage graph dump test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vltmt')
|
||||
test.top_filename = "t/t_cover_fsm_styles.v"
|
||||
|
||||
test.compile(v_flags2=["--coverage-fsm", "--dumpi-graph", "6"], threads=2)
|
||||
|
||||
dot_files = test.glob_some(test.obj_dir + "/*fsm_*.dot")
|
||||
for dot_filename in dot_files:
|
||||
test.file_grep(dot_filename, r'digraph v3graph')
|
||||
|
||||
test.file_grep_any(dot_files, r'ANY')
|
||||
test.file_grep_any(dot_files, r'default')
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage negative extraction test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
int cyc;
|
||||
logic side;
|
||||
state_t state /*verilator fsm_reset_arc*/;
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
side = 1'b0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) side <= 1'b1;
|
||||
if (cyc == 5) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
// The S0 arm is the supported baseline. The S1 and default arms are
|
||||
// deliberately unsupported extractor shapes: one has two meaningful
|
||||
// statements, the other writes a different lhs first. Coverage should ignore
|
||||
// those arcs rather than guessing.
|
||||
always_ff @(posedge clk) begin
|
||||
if (cyc == 0) begin
|
||||
state <= S0;
|
||||
end else begin
|
||||
%000002 case (state)
|
||||
// [FSM coverage]
|
||||
%000002 // [fsm_arc t.state::S0->S1]
|
||||
%000001 // [fsm_state t.state::S0]
|
||||
%000002 // [fsm_state t.state::S1]
|
||||
%000002 // [fsm_state t.state::S2]
|
||||
S0: state <= S1;
|
||||
S1: begin
|
||||
side <= ~side;
|
||||
state <= S2;
|
||||
end
|
||||
default: begin
|
||||
side <= 1'b0;
|
||||
state <= S0;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage negative extraction test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
# This test is intentionally "half supported": one case item is a simple
|
||||
# direct state assignment, while the others use shapes the extractor should
|
||||
# ignore (multiple meaningful statements or assignment to a non-state lhs).
|
||||
# That lets us hit the conservative negative branches in directStateAssign()
|
||||
# and singleMeaningfulStmt() without changing user-visible behavior.
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
||||
|
||||
test.execute()
|
||||
|
||||
# Use annotated-source output so the golden locks down which candidate arcs
|
||||
# survive extraction and which unsupported shapes are intentionally skipped.
|
||||
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()
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage negative extraction test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
int cyc;
|
||||
logic side;
|
||||
state_t state /*verilator fsm_reset_arc*/;
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
side = 1'b0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) side <= 1'b1;
|
||||
if (cyc == 5) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
// The S0 arm is the supported baseline. The S1 and default arms are
|
||||
// deliberately unsupported extractor shapes: one has two meaningful
|
||||
// statements, the other writes a different lhs first. Coverage should ignore
|
||||
// those arcs rather than guessing.
|
||||
always_ff @(posedge clk) begin
|
||||
if (cyc == 0) begin
|
||||
state <= S0;
|
||||
end else begin
|
||||
case (state)
|
||||
S0: state <= S1;
|
||||
S1: begin
|
||||
side <= ~side;
|
||||
state <= S2;
|
||||
end
|
||||
default: begin
|
||||
side <= 1'b0;
|
||||
state <= S0;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage no-reset lowering test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [0:0] {
|
||||
S0 = 1'b0,
|
||||
S1 = 1'b1
|
||||
} state_t;
|
||||
|
||||
int cyc;
|
||||
state_t state;
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
state = S0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 4) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
// No reset branch on purpose: this keeps the test focused on the branch in
|
||||
// lowering that skips reset reconstruction entirely.
|
||||
always_ff @(posedge clk) begin
|
||||
%000003 case (state)
|
||||
// [FSM coverage]
|
||||
%000003 // [fsm_arc t.state::S0->S1]
|
||||
%000002 // [fsm_state t.state::S0]
|
||||
%000003 // [fsm_state t.state::S1]
|
||||
S0: state <= S1;
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage no-reset lowering test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
# This test deliberately uses a clocked FSM with no outer reset branch. It
|
||||
# keeps coverage extraction in the supported subset, but forces lowering down
|
||||
# the "hasResetCond() == false" path so we validate the no-reset machinery
|
||||
# rather than only reset-wrapped FSMs.
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
||||
|
||||
test.execute()
|
||||
|
||||
# Use annotated-source output so the expected file captures the no-reset shape
|
||||
# directly, including the absence of reset pseudo-arcs.
|
||||
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()
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage no-reset lowering test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [0:0] {
|
||||
S0 = 1'b0,
|
||||
S1 = 1'b1
|
||||
} state_t;
|
||||
|
||||
int cyc;
|
||||
state_t state;
|
||||
|
||||
initial begin
|
||||
cyc = 0;
|
||||
state = S0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 4) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
// No reset branch on purpose: this keeps the test focused on the branch in
|
||||
// lowering that skips reset reconstruction entirely.
|
||||
always_ff @(posedge clk) begin
|
||||
case (state)
|
||||
S0: state <= S1;
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage reset policy test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
%000006 input clk
|
||||
);
|
||||
|
||||
typedef enum logic [0:0] {
|
||||
S0 = 1'b0,
|
||||
S1 = 1'b1
|
||||
} state_t;
|
||||
|
||||
%000001 logic rst;
|
||||
integer cyc;
|
||||
%000001 state_t state_incl /*verilator fsm_reset_arc*/;
|
||||
%000001 state_t state_excl;
|
||||
|
||||
%000001 initial begin
|
||||
%000001 rst = 1'b1;
|
||||
%000001 cyc = 0;
|
||||
end
|
||||
|
||||
%000006 always @(posedge clk) begin
|
||||
%000006 cyc <= cyc + 1;
|
||||
%000005 if (cyc == 1) rst <= 1'b0;
|
||||
%000005 if (cyc == 5) begin
|
||||
%000001 $write("*-* All Finished *-*\n");
|
||||
%000001 $finish;
|
||||
end
|
||||
end
|
||||
|
||||
%000006 always_ff @(posedge clk) begin
|
||||
%000004 if (rst) state_incl <= S0;
|
||||
%000004 else case (state_incl)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_incl::ANY->S0[reset_include]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.state_incl::S0->S1]
|
||||
%000000 // [fsm_state t.state_incl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.state_incl::S1]
|
||||
%000001 S0: state_incl <= S1;
|
||||
%000003 default: state_incl <= S1;
|
||||
endcase
|
||||
end
|
||||
|
||||
%000006 always_ff @(posedge clk) begin
|
||||
%000004 if (rst) state_excl <= S0;
|
||||
%000004 else case (state_excl)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_excl::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.state_excl::S0->S1]
|
||||
%000000 // [fsm_state t.state_excl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.state_excl::S1]
|
||||
%000001 S0: state_excl <= S1;
|
||||
%000003 default: state_excl <= S1;
|
||||
endcase
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage reset policy test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--include-reset-arcs",
|
||||
test.obj_dir + "/coverage.dat",
|
||||
],
|
||||
verilator_run=True)
|
||||
|
||||
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()
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage reset policy test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [0:0] {
|
||||
S0 = 1'b0,
|
||||
S1 = 1'b1
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
integer cyc;
|
||||
state_t state_incl /*verilator fsm_reset_arc*/;
|
||||
state_t state_excl;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 5) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_incl <= S0;
|
||||
else case (state_incl)
|
||||
S0: state_incl <= S1;
|
||||
default: state_incl <= S1;
|
||||
endcase
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) state_excl <= S0;
|
||||
else case (state_excl)
|
||||
S0: state_excl <= S1;
|
||||
default: state_excl <= S1;
|
||||
endcase
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
integer cyc;
|
||||
state_t state /*verilator fsm_reset_arc*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 5) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
// This reset block is intentionally non-idiomatic. The detector only collects
|
||||
// reset arcs from top-level direct assignments in the reset branch, so two
|
||||
// sequential assignments are the narrowest way to force multiple reset arcs
|
||||
// into one FSM graph and exercise reuse of the synthetic ANY reset source.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
state <= S1;
|
||||
end else begin
|
||||
%000001 case (state)
|
||||
// [FSM coverage]
|
||||
%000000 // [fsm_arc t.state::ANY->S0[reset_include]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.state::ANY->S1[reset_include]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.state::S0->S2]
|
||||
%000001 // [fsm_arc t.state::S1->S2]
|
||||
%000000 // [fsm_state t.state::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.state::S1]
|
||||
%000001 // [fsm_state t.state::S2]
|
||||
S0: state <= S2;
|
||||
S1: state <= S2;
|
||||
default: state <= S2;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
# This regression is aimed at the graph helper, not at recommending RTL style.
|
||||
# We deliberately create two reset arcs in a single FSM so graph construction
|
||||
# has to reuse the synthetic ANY reset pseudo-vertex rather than allocating it
|
||||
# only once for a one-arc machine.
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
||||
|
||||
test.execute()
|
||||
|
||||
# Use annotated-source output so the golden proves both reset arcs remain
|
||||
# visible and share the same synthetic ANY reset source.
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate",
|
||||
test.obj_dir + "/annotated",
|
||||
test.obj_dir + "/coverage.dat",
|
||||
],
|
||||
verilator_run=True)
|
||||
|
||||
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage reset pseudo-vertex reuse test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2
|
||||
} state_t;
|
||||
|
||||
logic rst;
|
||||
integer cyc;
|
||||
state_t state /*verilator fsm_reset_arc*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 5) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
// This reset block is intentionally non-idiomatic. The detector only collects
|
||||
// reset arcs from top-level direct assignments in the reset branch, so two
|
||||
// sequential assignments are the narrowest way to force multiple reset arcs
|
||||
// into one FSM graph and exercise reuse of the synthetic ANY reset source.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
state <= S1;
|
||||
end else begin
|
||||
case (state)
|
||||
S0: state <= S2;
|
||||
S1: state <= S2;
|
||||
default: state <= S2;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM coverage style coverage test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2,
|
||||
S3 = 2'd3
|
||||
} state_t;
|
||||
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic start;
|
||||
state_t state /*verilator fsm_arc_include_cond*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end else begin
|
||||
%000003 case (state)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000000 // [fsm_arc t.state::S0->S1]
|
||||
%000003 // [fsm_arc t.state::S0->S2]
|
||||
%000000 // [fsm_arc t.state::S1->S3]
|
||||
%000000 // [SYNTHETIC DEFAULT ARC: t.state::default->S0]
|
||||
%000002 // [fsm_state t.state::S0]
|
||||
%000000 // [fsm_state t.state::S1] *** UNCOVERED ***
|
||||
%000003 // [fsm_state t.state::S2]
|
||||
%000000 // [fsm_state t.state::S3] *** UNCOVERED ***
|
||||
S0: if (start) state <= S1; else state <= S2;
|
||||
S1: state <= S3;
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM coverage style coverage test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--cc --coverage-fsm'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.run(cmd=[os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate", test.obj_dir + "/annotated",
|
||||
test.obj_dir + "/coverage.dat"],
|
||||
verilator_run=True) # yapf:disable
|
||||
|
||||
test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// DESCRIPTION: Verilator: FSM coverage style coverage test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2,
|
||||
S3 = 2'd3
|
||||
} state_t;
|
||||
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic start;
|
||||
state_t state /*verilator fsm_arc_include_cond*/;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end else begin
|
||||
case (state)
|
||||
S0: if (start) state <= S1; else state <= S2;
|
||||
S1: state <= S3;
|
||||
default: state <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: generic coverage declaration dump test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
test.top_filename = "t/t_cover_fsm_styles.v"
|
||||
|
||||
# Dump generic COVEROTHERDECL nodes so AstCoverOtherDecl::dump() also sees
|
||||
# coverage declarations with no FSM metadata, exercising the empty-field side
|
||||
# of the fv/ff/ft/fg formatting.
|
||||
test.lint(v_flags=["--coverage-line", "--dump-tree"])
|
||||
|
||||
tree_files = [Path(filename) for filename in test.glob_some(test.obj_dir + "/*.tree")]
|
||||
tree_texts = [filename.read_text(encoding="utf8") for filename in tree_files]
|
||||
|
||||
generic_lines = []
|
||||
for text in tree_texts:
|
||||
generic_lines.extend(
|
||||
line for line in text.splitlines() if "COVEROTHERDECL" in line and " page=v_line/" in line
|
||||
)
|
||||
|
||||
assert generic_lines
|
||||
assert any(" fv=" not in line and " ff=" not in line and " ft=" not in line and " fg=" not in line
|
||||
for line in generic_lines)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM metacomment dump test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import json
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
test.top_filename = "t/t_fsm_metacmt_dump.v"
|
||||
|
||||
test.lint(v_flags=["--dump-tree --dump-tree-json --no-json-edit-nums"])
|
||||
|
||||
tree_files = test.glob_some(test.obj_dir + "/*.tree")
|
||||
json_files = test.glob_some(test.obj_dir + "/*.tree.json")
|
||||
|
||||
test.file_grep_any(tree_files, r'\[aFSMSTATE\]')
|
||||
test.file_grep_any(tree_files, r'\[aFSMRESETARC\]')
|
||||
test.file_grep_any(tree_files, r'\[aFSMARCCOND\]')
|
||||
|
||||
test.file_grep_any(json_files, r'"attrFsmState":true')
|
||||
test.file_grep_any(json_files, r'"attrFsmResetArc":true')
|
||||
test.file_grep_any(json_files, r'"attrFsmArcInclCond":true')
|
||||
|
||||
for filename in json_files:
|
||||
with open(filename, 'r', encoding="utf8") as fh:
|
||||
json.load(fh)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// DESCRIPTION: Verilator: FSM metacomment dump test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
|
||||
typedef enum logic [0:0] {
|
||||
S0 = 1'b0,
|
||||
S1 = 1'b1
|
||||
} state_t;
|
||||
|
||||
state_t state_reset /*verilator fsm_reset_arc*/;
|
||||
state_t state_cond /*verilator fsm_arc_include_cond*/;
|
||||
logic forced_state /*verilator fsm_state*/;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_reset <= S0;
|
||||
state_cond <= S0;
|
||||
forced_state <= 1'b0;
|
||||
end else begin
|
||||
state_reset <= S1;
|
||||
if (state_cond) state_cond <= S0;
|
||||
else state_cond <= S1;
|
||||
forced_state <= ~forced_state;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
%Warning-COVERIGN: t/t_fsmmulti_same_bad.v:30:5: Ignoring unsupported: FSM coverage on multiple supported case statements found in the same always block. Only the first candidate will be instrumented.
|
||||
30 | case (state)
|
||||
| ^~~~
|
||||
... For warning description see https://verilator.org/warn/COVERIGN?v=latest
|
||||
... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message.
|
||||
%Error: Exiting due to
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: same-state multi-candidate FSM error test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
# Multiple supported case candidates on the same state variable in one
|
||||
# always_ff now warn and keep only the first candidate instrumented. Different-
|
||||
# state multi-candidate cases still use the existing FSMMULTI warning path; this
|
||||
# test locks down only the same-state unsupported form.
|
||||
test.lint(
|
||||
verilator_flags2=["--coverage-fsm"],
|
||||
fails=True)
|
||||
|
||||
test.file_grep(
|
||||
test.compile_log_filename,
|
||||
r'%Warning-COVERIGN: t/t_fsmmulti_same_bad.v:30:5: Ignoring unsupported: FSM coverage on '
|
||||
r'multiple supported case statements found in the same always block. Only the first '
|
||||
r'candidate will be instrumented.')
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// DESCRIPTION: Verilator: same-state multi-candidate FSM error test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0, S1, S2
|
||||
} state_t;
|
||||
|
||||
state_t state;
|
||||
|
||||
// This is intentionally non-idiomatic RTL. The detector sees one supported
|
||||
// candidate in the reset-if else branch and a second supported top-level
|
||||
// case on the same state variable. That same-state duplicate is rejected.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S0;
|
||||
end else begin
|
||||
case (state)
|
||||
S0: state <= S1;
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
case (state)
|
||||
S1: state <= S2;
|
||||
default: ;
|
||||
endcase
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
%Warning-FSMMULTI: t/t_fsmmulti_warn_bad.v:27:5: FSM coverage: multiple enum-typed case statements found in the same always block. Only the first candidate will be instrumented.
|
||||
27 | case (state_b)
|
||||
| ^~~~
|
||||
... For warning description see https://verilator.org/warn/FSMMULTI?v=latest
|
||||
... Use "/* verilator lint_off FSMMULTI */" and lint_on around source to disable this message.
|
||||
%Error: Exiting due to
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSMMULTI warning test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('vlt')
|
||||
|
||||
test.lint(
|
||||
verilator_flags2=["--coverage-fsm"],
|
||||
fails=True,
|
||||
expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// DESCRIPTION: Verilator: FSMMULTI warning test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input logic clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
A0, A1
|
||||
} a_state_t;
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
B0, B1
|
||||
} b_state_t;
|
||||
|
||||
a_state_t state_a;
|
||||
b_state_t state_b;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
case (state_a)
|
||||
A0: state_a <= A1;
|
||||
default: state_a <= A0;
|
||||
endcase
|
||||
case (state_b)
|
||||
B0: state_b <= B1;
|
||||
default: state_b <= B0;
|
||||
endcase
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSMMULTI warning disabled without --coverage-fsm
|
||||
#
|
||||
# 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-line"])
|
||||
test.file_grep_not(test.compile_log_filename, r"FSMMULTI")
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// DESCRIPTION: Verilator: FSMMULTI warning disabled without --coverage-fsm
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t;
|
||||
typedef enum logic [1:0] {
|
||||
A0 = 2'd0,
|
||||
A1 = 2'd1,
|
||||
A2 = 2'd2
|
||||
} state_a_t;
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
B0 = 2'd0,
|
||||
B1 = 2'd1,
|
||||
B2 = 2'd2
|
||||
} state_b_t;
|
||||
|
||||
logic clk;
|
||||
logic rst;
|
||||
state_a_t a_state;
|
||||
state_b_t b_state;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
a_state <= A0;
|
||||
b_state <= B0;
|
||||
end else begin
|
||||
case (a_state)
|
||||
A0: a_state <= A1;
|
||||
A1: a_state <= A2;
|
||||
default: a_state <= A0;
|
||||
endcase
|
||||
case (b_state)
|
||||
B0: b_state <= B1;
|
||||
B1: b_state <= B2;
|
||||
default: b_state <= B0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM reporting coverage test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
%000007 input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2,
|
||||
S3 = 2'd3
|
||||
} state_t;
|
||||
|
||||
integer cyc;
|
||||
%000001 logic rst;
|
||||
%000001 logic start;
|
||||
%000003 state_t state_default /*verilator fsm_arc_include_cond*/;
|
||||
%000001 state_t state_reset_incl /*verilator fsm_reset_arc*/;
|
||||
%000001 state_t state_reset_excl;
|
||||
|
||||
%000001 initial begin
|
||||
%000001 rst = 1'b1;
|
||||
%000001 start = 1'b0;
|
||||
%000001 cyc = 0;
|
||||
end
|
||||
|
||||
%000007 always @(posedge clk) begin
|
||||
%000007 cyc <= cyc + 1;
|
||||
%000006 if (cyc == 1) rst <= 1'b0;
|
||||
%000006 if (cyc == 2) start <= 1'b1;
|
||||
%000006 if (cyc == 3) start <= 1'b0;
|
||||
%000006 if (cyc == 6) begin
|
||||
%000001 $write("*-* All Finished *-*\n");
|
||||
%000001 $finish;
|
||||
end
|
||||
end
|
||||
|
||||
// This FSM gives the reporting path both ordinary arcs and a synthetic
|
||||
// default arc so annotate/write-info exercise FSM-arc filtering.
|
||||
%000007 always_ff @(posedge clk) begin
|
||||
%000005 if (rst) begin
|
||||
%000002 state_default <= S0;
|
||||
%000005 end else begin
|
||||
%000005 case (state_default)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_default::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000000 // [SYNTHETIC DEFAULT ARC: t.state_default::default->S0]
|
||||
%000002 // [fsm_state t.state_default::S0]
|
||||
%000000 // [fsm_state t.state_default::S1] *** UNCOVERED ***
|
||||
%000003 // [fsm_state t.state_default::S2]
|
||||
%000000 // [fsm_state t.state_default::S3] *** UNCOVERED ***
|
||||
%000003 S0: if (start) state_default <= S1; else state_default <= S2;
|
||||
%000002 default: state_default <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// These two FSMs give reporting both reset-include and reset-exclude arcs so
|
||||
// annotate can exercise the reset-arc filtering wording in both modes.
|
||||
%000007 always_ff @(posedge clk) begin
|
||||
%000005 if (rst) begin
|
||||
%000002 state_reset_incl <= S0;
|
||||
%000005 end else begin
|
||||
%000005 case (state_reset_incl)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_reset_incl::ANY->S0[reset_include]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.state_reset_incl::S0->S1]
|
||||
%000000 // [fsm_state t.state_reset_incl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.state_reset_incl::S1]
|
||||
%000000 // [fsm_state t.state_reset_incl::S2] *** UNCOVERED ***
|
||||
%000000 // [fsm_state t.state_reset_incl::S3] *** UNCOVERED ***
|
||||
%000001 S0: state_reset_incl <= S1;
|
||||
%000004 default: state_reset_incl <= S1;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
%000007 always_ff @(posedge clk) begin
|
||||
%000005 if (rst) begin
|
||||
%000002 state_reset_excl <= S0;
|
||||
%000005 end else begin
|
||||
%000005 case (state_reset_excl)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_reset_excl::ANY->S0[reset]] [reset arc, excluded from %]
|
||||
%000001 // [fsm_arc t.state_reset_excl::S0->S1]
|
||||
%000000 // [fsm_state t.state_reset_excl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.state_reset_excl::S1]
|
||||
%000000 // [fsm_state t.state_reset_excl::S2] *** UNCOVERED ***
|
||||
%000000 // [fsm_state t.state_reset_excl::S3] *** UNCOVERED ***
|
||||
%000001 S0: state_reset_excl <= S1;
|
||||
%000004 default: state_reset_excl <= S1;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: FSM reporting coverage test
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import os
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
# This regression targets the reporting side of FSM coverage rather than the
|
||||
# detector itself. The generated coverage.dat contains state points, ordinary
|
||||
# arcs, default arcs, reset arcs, and reset-include arcs so verilator_coverage
|
||||
# exercises the FSM-specific filtering and annotation code paths.
|
||||
test.compile(verilator_flags2=['--cc --coverage'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.run(cmd=[
|
||||
os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--write-info",
|
||||
test.obj_dir + "/coverage.info",
|
||||
test.obj_dir + "/coverage.dat",
|
||||
],
|
||||
verilator_run=True)
|
||||
|
||||
test.file_grep(test.obj_dir + "/coverage.info", r"TN:verilator_coverage")
|
||||
test.file_grep(test.obj_dir + "/coverage.info", r"BRF:")
|
||||
test.file_grep(test.obj_dir + "/coverage.info", r"BRH:")
|
||||
|
||||
test.run(cmd=[os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--annotate", test.obj_dir + "/annotated",
|
||||
test.obj_dir + "/coverage.dat"],
|
||||
verilator_run=True) # yapf:disable
|
||||
|
||||
test.run(cmd=[os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage",
|
||||
"--include-reset-arcs",
|
||||
"--annotate", test.obj_dir + "/annotated-incl",
|
||||
test.obj_dir + "/coverage.dat"],
|
||||
verilator_run=True) # yapf:disable
|
||||
|
||||
annotated = test.obj_dir + "/annotated/t_vlcov_fsm_report.v"
|
||||
annotated_incl = test.obj_dir + "/annotated-incl/t_vlcov_fsm_report.v"
|
||||
|
||||
test.files_identical(annotated, "t/t_vlcov_fsm_report.out")
|
||||
test.files_identical(annotated_incl, "t/t_vlcov_fsm_report_incl.out")
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// DESCRIPTION: Verilator: FSM reporting coverage test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2,
|
||||
S3 = 2'd3
|
||||
} state_t;
|
||||
|
||||
integer cyc;
|
||||
logic rst;
|
||||
logic start;
|
||||
state_t state_default /*verilator fsm_arc_include_cond*/;
|
||||
state_t state_reset_incl /*verilator fsm_reset_arc*/;
|
||||
state_t state_reset_excl;
|
||||
|
||||
initial begin
|
||||
rst = 1'b1;
|
||||
start = 1'b0;
|
||||
cyc = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) begin
|
||||
cyc <= cyc + 1;
|
||||
if (cyc == 1) rst <= 1'b0;
|
||||
if (cyc == 2) start <= 1'b1;
|
||||
if (cyc == 3) start <= 1'b0;
|
||||
if (cyc == 6) begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
// This FSM gives the reporting path both ordinary arcs and a synthetic
|
||||
// default arc so annotate/write-info exercise FSM-arc filtering.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_default <= S0;
|
||||
end else begin
|
||||
case (state_default)
|
||||
S0: if (start) state_default <= S1; else state_default <= S2;
|
||||
default: state_default <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// These two FSMs give reporting both reset-include and reset-exclude arcs so
|
||||
// annotate can exercise the reset-arc filtering wording in both modes.
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_reset_incl <= S0;
|
||||
end else begin
|
||||
case (state_reset_incl)
|
||||
S0: state_reset_incl <= S1;
|
||||
default: state_reset_incl <= S1;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state_reset_excl <= S0;
|
||||
end else begin
|
||||
case (state_reset_excl)
|
||||
S0: state_reset_excl <= S1;
|
||||
default: state_reset_excl <= S1;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// // verilator_coverage annotation
|
||||
// DESCRIPTION: Verilator: FSM reporting coverage test
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module t (
|
||||
%000007 input clk
|
||||
);
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
S0 = 2'd0,
|
||||
S1 = 2'd1,
|
||||
S2 = 2'd2,
|
||||
S3 = 2'd3
|
||||
} state_t;
|
||||
|
||||
integer cyc;
|
||||
%000001 logic rst;
|
||||
%000001 logic start;
|
||||
%000003 state_t state_default /*verilator fsm_arc_include_cond*/;
|
||||
%000001 state_t state_reset_incl /*verilator fsm_reset_arc*/;
|
||||
%000001 state_t state_reset_excl;
|
||||
|
||||
%000001 initial begin
|
||||
%000001 rst = 1'b1;
|
||||
%000001 start = 1'b0;
|
||||
%000001 cyc = 0;
|
||||
end
|
||||
|
||||
%000007 always @(posedge clk) begin
|
||||
%000007 cyc <= cyc + 1;
|
||||
%000006 if (cyc == 1) rst <= 1'b0;
|
||||
%000006 if (cyc == 2) start <= 1'b1;
|
||||
%000006 if (cyc == 3) start <= 1'b0;
|
||||
%000006 if (cyc == 6) begin
|
||||
%000001 $write("*-* All Finished *-*\n");
|
||||
%000001 $finish;
|
||||
end
|
||||
end
|
||||
|
||||
// This FSM gives the reporting path both ordinary arcs and a synthetic
|
||||
// default arc so annotate/write-info exercise FSM-arc filtering.
|
||||
%000007 always_ff @(posedge clk) begin
|
||||
%000005 if (rst) begin
|
||||
%000002 state_default <= S0;
|
||||
%000005 end else begin
|
||||
%000005 case (state_default)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_default::ANY->S0[reset]]
|
||||
%000000 // [SYNTHETIC DEFAULT ARC: t.state_default::default->S0]
|
||||
%000002 // [fsm_state t.state_default::S0]
|
||||
%000000 // [fsm_state t.state_default::S1] *** UNCOVERED ***
|
||||
%000003 // [fsm_state t.state_default::S2]
|
||||
%000000 // [fsm_state t.state_default::S3] *** UNCOVERED ***
|
||||
%000003 S0: if (start) state_default <= S1; else state_default <= S2;
|
||||
%000002 default: state_default <= S0;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// These two FSMs give reporting both reset-include and reset-exclude arcs so
|
||||
// annotate can exercise the reset-arc filtering wording in both modes.
|
||||
%000007 always_ff @(posedge clk) begin
|
||||
%000005 if (rst) begin
|
||||
%000002 state_reset_incl <= S0;
|
||||
%000005 end else begin
|
||||
%000005 case (state_reset_incl)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_reset_incl::ANY->S0[reset_include]]
|
||||
%000001 // [fsm_arc t.state_reset_incl::S0->S1]
|
||||
%000000 // [fsm_state t.state_reset_incl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.state_reset_incl::S1]
|
||||
%000000 // [fsm_state t.state_reset_incl::S2] *** UNCOVERED ***
|
||||
%000000 // [fsm_state t.state_reset_incl::S3] *** UNCOVERED ***
|
||||
%000001 S0: state_reset_incl <= S1;
|
||||
%000004 default: state_reset_incl <= S1;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
%000007 always_ff @(posedge clk) begin
|
||||
%000005 if (rst) begin
|
||||
%000002 state_reset_excl <= S0;
|
||||
%000005 end else begin
|
||||
%000005 case (state_reset_excl)
|
||||
// [FSM coverage]
|
||||
%000001 // [fsm_arc t.state_reset_excl::ANY->S0[reset]]
|
||||
%000001 // [fsm_arc t.state_reset_excl::S0->S1]
|
||||
%000000 // [fsm_state t.state_reset_excl::S0] *** UNCOVERED ***
|
||||
%000001 // [fsm_state t.state_reset_excl::S1]
|
||||
%000000 // [fsm_state t.state_reset_excl::S2] *** UNCOVERED ***
|
||||
%000000 // [fsm_state t.state_reset_excl::S3] *** UNCOVERED ***
|
||||
%000001 S0: state_reset_excl <= S1;
|
||||
%000004 default: state_reset_excl <= S1;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
Loading…
Reference in New Issue