Extend FSM Detect to support 'Wide State Encodings' (#7573)

This commit is contained in:
Yogish Sekhar
2026-05-13 06:59:22 -04:00
committed by GitHub
parent 9588e67ca9
commit 8312e9d901
17 changed files with 673 additions and 204 deletions
+208 -115
View File
@@ -40,9 +40,57 @@ VL_DEFINE_DEBUG_FUNCTIONS;
namespace {
// FSM graph state ids intentionally stay scalar here; arbitrary-width Verilog
// encodings need a wider graph representation and are rejected before lowering.
using FsmStateValue = uint32_t;
// Width-preserving FSM state identity. FSM detection needs a stable key for
// graph vertices and lookup tables, but lowering still needs the original
// folded Verilog value so emitted comparisons keep the correct width and bits.
class FsmStateValue final {
// Hash/equality key only. It deliberately ignores signedness because
// signed and unsigned constants with the same width and bits denote the
// same encoded FSM state.
string m_key; // Canonical "width:value" identity, independent of signedness
// Semantic value. This is what diagnostics and lowering use when printing
// values or rebuilding AstConst nodes for instrumentation.
V3Number m_num; // Original folded value, preserving width for lowered comparisons
static string makeKey(const V3Number& num) {
V3Number keyNum = num;
// Signedness does not change FSM state identity: same width and bits
// should address the same graph vertex.
keyNum.isSigned(false);
return cvtToStr(keyNum.width()) + ":" + keyNum.ascii(true, true);
}
public:
// Default value is used only for synthetic pseudo-states such as ANY and
// default, which never use m_num as a real Verilog state encoding.
FsmStateValue()
: m_key{"1:1'h0"}
, m_num{static_cast<AstNode*>(nullptr), 1, 0} {}
explicit FsmStateValue(const V3Number& num)
: m_key{makeKey(num)}
, m_num{num} {}
const string& key() const { return m_key; }
const V3Number& num() const { return m_num; }
string ascii() const { return m_num.ascii(true, true); }
string warnText() const {
// Preserve legacy diagnostics for old <=32-bit FSMs, but print wide
// values without truncation.
if (m_num.width() <= 32) return cvtToStr(m_num.toUInt());
return ascii();
}
bool operator==(const FsmStateValue& rhs) const { return m_key == rhs.m_key; }
};
// unordered_map needs an explicit hash for this custom key type. Keep the
// hash definition paired with operator== by hashing the same canonical key.
struct FsmStateValueHash final {
size_t operator()(const FsmStateValue& value) const {
return std::hash<string>{}(value.key());
}
};
// Captures one sensitivity-list entry so the lowering phase can later rebuild
// an active block with the same triggering event control.
@@ -62,17 +110,17 @@ struct FsmResetCondDesc final {
};
class FsmResetArcDesc final {
FsmStateValue m_toValue = 0; // Encoded reset target state.
AstNode* m_nodep = nullptr; // Source node for warnings and emitted metadata.
FsmStateValue m_toValue; // Encoded reset target state.
AstNodeAssign* m_nodep = nullptr; // Source assignment for warnings and emitted metadata
public:
FsmResetArcDesc() = default;
FsmResetArcDesc(FsmStateValue toValue, AstNode* nodep)
FsmResetArcDesc(FsmStateValue toValue, AstNodeAssign* nodep)
: m_toValue{toValue}
, m_nodep{nodep} {}
FsmStateValue toValue() const { return m_toValue; }
AstNode* nodep() const { return m_nodep; }
AstNodeAssign* nodep() const { return m_nodep; }
};
class FsmRegisterCandidate final {
@@ -135,7 +183,7 @@ public:
private:
Kind m_kind; // State vs synthetic ANY/default vertex role.
string m_label; // User-facing state or pseudo-state label.
FsmStateValue m_value = 0; // Encoded state value for real state vertices.
FsmStateValue m_value; // Encoded state value for real state vertices.
protected:
FsmVertex(V3Graph* graphp, Kind kind, string label, FsmStateValue value) VL_MT_DISABLED
@@ -153,7 +201,7 @@ public:
const string& label() const { return m_label; }
FsmStateValue value() const { return m_value; }
string name() const override VL_MT_SAFE { return m_label + "=" + cvtToStr(m_value); }
string name() const override VL_MT_SAFE { return m_label + "=" + m_value.ascii(); }
};
class FsmStateVertex final : public FsmVertex {
@@ -173,7 +221,7 @@ class FsmPseudoVertex final : public FsmVertex {
public:
FsmPseudoVertex(V3Graph* graphp, Kind kind, string label) VL_MT_DISABLED
: FsmVertex{graphp, kind, label, 0} {}
: FsmVertex{graphp, kind, label, FsmStateValue{}} {}
~FsmPseudoVertex() override = default;
string name() const override VL_MT_SAFE { return label(); }
@@ -230,7 +278,8 @@ class FsmGraph final : public V3Graph {
bool m_resetInclude = false; // Whether reset arcs count toward coverage totals.
bool m_inclCond = false; // Whether conditional arcs should be kept explicitly.
FileLine* m_flp = nullptr; // Representative source location for declarations/arcs.
std::unordered_map<FsmStateValue, FsmStateVertex*> m_stateVertices; // Value to state map.
std::unordered_map<FsmStateValue, FsmStateVertex*, FsmStateValueHash>
m_stateVertices; // Value to state map.
FsmPseudoVertex* m_resetVertexp = nullptr; // Synthetic ANY source for reset arcs.
FsmPseudoVertex* m_defaultVertexp = nullptr; // Synthetic default source for case defaults.
@@ -312,7 +361,7 @@ struct FsmCaseCandidate final {
struct FsmStateComparison final {
AstVarScope* stateVscp = nullptr; // Compared state variable
AstNodeExpr* valuep = nullptr; // Compared constant value expression
FsmStateValue value = 0; // Encoded compared state value
FsmStateValue value; // Encoded compared state value
};
// A branch is usable only after its predicate has exactly one state comparison;
@@ -321,7 +370,7 @@ struct FsmIfBranch final {
AstIf* ifp = nullptr; // Source if/else-if node
AstNode* stmtsp = nullptr; // Branch body
AstNodeExpr* valuep = nullptr; // Source state value expression
FsmStateValue fromValue = 0; // Encoded source state value
FsmStateValue fromValue; // Encoded source state value
bool hasTopGuard = false; // Branch condition had extra guard terms
};
@@ -346,7 +395,8 @@ struct StateConstLabel final {
struct FsmStateSpace final {
std::vector<std::pair<string, FsmStateValue>> states; // User label and encoded value
std::unordered_map<FsmStateValue, StateConstLabel> labels; // Encoded value to label
std::unordered_map<FsmStateValue, StateConstLabel, FsmStateValueHash>
labels; // Encoded value to label
AstVar* stateVarp = nullptr; // Tracked FSM state variable
bool enumBacked = false; // Whether states came from an enum declaration
};
@@ -471,15 +521,15 @@ class FsmDetectVisitor final : public VNVisitor {
firstIfp->thensp(), resetStateVscp, reg.resetArcs());
if (resetStatus == ResetAssignStatus::NONE || resetStateVscp != vscp) {
reg.resetArcs().clear();
FsmStateValue resetValue = 0;
FsmStateValue resetValue;
AstNode* const thenNodep
= FsmDetectVisitor::singleMeaningfulBranch(firstIfp->thensp());
UASSERT_OBJ(thenNodep, firstIfp,
"one-block reset fallback requires a non-empty reset branch");
if (FsmDetectVisitor::directConstStateAssignNode(thenNodep, resetStateVscp,
resetValue)
&& resetStateVscp == vscp) {
reg.resetArcs().emplace_back(resetValue, firstIfp->thensp());
AstNodeAssign* const resetAssp = FsmDetectVisitor::directConstStateAssignNode(
thenNodep, resetStateVscp, resetValue);
if (resetAssp && resetStateVscp == vscp) {
reg.resetArcs().emplace_back(resetValue, resetAssp);
}
} else if (resetStatus == ResetAssignStatus::MULTI_SAME_STATE) {
reg.resetArcs().clear();
@@ -743,7 +793,7 @@ class FsmDetectVisitor final : public VNVisitor {
UASSERT_OBJ(nodep, stmtp, "Empty reset branch unexpectedly survived to FSM detection");
for (;; nodep = nodep->nextp()) {
AstVarScope* assignStateVscp = nullptr;
FsmStateValue value = 0;
FsmStateValue value;
AstNodeAssign* const assp = directConstStateAssignNode(nodep, assignStateVscp, value);
if (!assp) return ResetAssignStatus::NONE;
if (!stateVscp) stateVscp = assignStateVscp;
@@ -829,11 +879,11 @@ class FsmDetectVisitor final : public VNVisitor {
}
AstNodeAssign* const assp = directStateAssign(itemp->stmtsp(), stateVscp);
if (assp) {
FsmStateValue toValue = 0;
FsmStateValue toValue;
if (constValueStatus(assp->rhsp(), toValue) == ConstValueStatus::OK) return assp;
}
FsmStateValue thenValue = 0;
FsmStateValue elseValue = 0;
FsmStateValue thenValue;
FsmStateValue elseValue;
if (directStateCondConstAssign(itemp->stmtsp(), stateVscp, thenValue, elseValue)) {
return assp;
}
@@ -857,10 +907,10 @@ class FsmDetectVisitor final : public VNVisitor {
static AstNode* caseItemSupportedArcNodeLike(AstNode* stmtsp, AstVarScope* stateVscp) {
if (AstNodeAssign* const assp = directStateAssign(stmtsp, stateVscp)) {
FsmStateValue toValue = 0;
FsmStateValue toValue;
if (constValueStatus(assp->rhsp(), toValue) == ConstValueStatus::OK) return assp;
FsmStateValue thenValue = 0;
FsmStateValue elseValue = 0;
FsmStateValue thenValue;
FsmStateValue elseValue;
if (directStateCondConstAssign(stmtsp, stateVscp, thenValue, elseValue)) return assp;
}
return nullptr;
@@ -878,24 +928,22 @@ class FsmDetectVisitor final : public VNVisitor {
// Prefer user labels in reports. Forced non-enum FSMs prepopulate synthetic
// labels, so all emitted arcs should already have a known label here.
static string labelForValue(const std::unordered_map<FsmStateValue, StateConstLabel>& labels,
FsmStateValue value) {
static string labelForValue(
const std::unordered_map<FsmStateValue, StateConstLabel, FsmStateValueHash>& labels,
const FsmStateValue& value) {
return labels.at(value).text;
}
// The extractor only models constant-valued state transitions, and by the
// time detect runs those values have already been constant-folded.
enum class ConstValueStatus : uint8_t { OK, NOT_CONST, XZ, WIDE };
enum class ConstValueStatus : uint8_t { OK, NOT_CONST, XZ };
static ConstValueStatus constValueStatus(AstNodeExpr* exprp, FsmStateValue& value) {
const AstConst* const constp = VN_CAST(exprp, Const);
if (!constp) return ConstValueStatus::NOT_CONST;
const V3Number& num = constp->num();
if (num.isAnyXZ()) return ConstValueStatus::XZ;
// Some callers are still only probing candidate shapes, so wide constants
// should reject the candidate instead of reporting a V3Number error.
if (constp->width() > 32) return ConstValueStatus::WIDE;
value = constp->toUInt();
value = FsmStateValue{num};
return ConstValueStatus::OK;
}
@@ -912,7 +960,7 @@ class FsmDetectVisitor final : public VNVisitor {
valuep = eqp->lhsp();
}
FsmStateValue value = 0;
FsmStateValue value;
if (constValueStatus(valuep, value) != ConstValueStatus::OK) return false;
cmp.stateVscp = vrefp->varScopep();
cmp.valuep = valuep;
@@ -1023,7 +1071,7 @@ class FsmDetectVisitor final : public VNVisitor {
static bool collectIfChain(AstIf* ifp, const FsmAliasMap& aliases,
FsmIfChainCandidate& chain) {
chain.ifp = ifp;
std::unordered_set<FsmStateValue> seenValues;
std::unordered_set<string> seenValues;
AstIf* curp = ifp;
// Only the top-level spine represents dispatch; treating nested branch
// logic as additional source states would invent transitions.
@@ -1032,7 +1080,7 @@ class FsmDetectVisitor final : public VNVisitor {
bool hasGuard = false;
if (!resolveIfPredicate(curp->condp(), aliases, cmp, hasGuard)) return false;
if (chain.compareVscp && chain.compareVscp != cmp.stateVscp) return false;
if (!seenValues.insert(cmp.value).second) return false;
if (!seenValues.insert(cmp.value.key()).second) return false;
chain.compareVscp = cmp.stateVscp;
chain.branches.push_back(
FsmIfBranch{curp, curp->thensp(), cmp.valuep, cmp.value, hasGuard});
@@ -1055,19 +1103,19 @@ class FsmDetectVisitor final : public VNVisitor {
// instrumentation for that edge rather than silently dropping it or turning
// optional coverage into a hard compile failure.
static bool validateKnownStateValue(AstNode* nodep, const FsmStateSpace& stateSpace,
FsmStateValue value, const string& role) {
const FsmStateValue& value, const string& role) {
if (stateSpace.labels.find(value) != stateSpace.labels.end()) return true;
if (stateSpace.enumBacked) {
const string enumRole = role == "source" ? "case item value" : "assigned value";
nodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on enum state variable "
+ stateSpace.stateVarp->prettyNameQ() + ": " + enumRole
+ " " + cvtToStr(value)
+ " " + value.warnText()
+ " is not present in the declared enum");
return false;
}
nodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum state variable "
+ stateSpace.stateVarp->prettyNameQ() + ": " + role + " value "
+ cvtToStr(value)
+ value.warnText()
+ " is not present in the inferred state space");
return false;
}
@@ -1078,7 +1126,7 @@ class FsmDetectVisitor final : public VNVisitor {
return StateConstLabel{constp->name(), false, 0};
}
static void updateStateLabel(FsmStateSpace& stateSpace, FsmStateValue value,
static void updateStateLabel(FsmStateSpace& stateSpace, const FsmStateValue& value,
const StateConstLabel& label) {
stateSpace.states.at(stateSpace.labels.at(value).stateIndex).first = label.text;
}
@@ -1102,12 +1150,13 @@ class FsmDetectVisitor final : public VNVisitor {
= collectConstStateAssigns(ifp->thensp(), resetStateVscp, cand.resetArcs());
if (resetStatus == ResetAssignStatus::NONE) {
cand.resetArcs().clear();
FsmStateValue resetValue = 0;
FsmStateValue resetValue;
AstNode* const thenNodep = singleMeaningfulBranch(ifp->thensp());
UASSERT_OBJ(thenNodep, ifp, "reset fallback requires a non-empty reset branch");
if (!directConstStateAssignNode(thenNodep, resetStateVscp, resetValue))
return false;
cand.resetArcs().emplace_back(resetValue, ifp->thensp());
AstNodeAssign* const resetAssp
= directConstStateAssignNode(thenNodep, resetStateVscp, resetValue);
if (!resetAssp) return false;
cand.resetArcs().emplace_back(resetValue, resetAssp);
} else if (resetStatus == ResetAssignStatus::MULTI_SAME_STATE) {
cand.resetArcs().clear();
}
@@ -1119,7 +1168,7 @@ class FsmDetectVisitor final : public VNVisitor {
cand.hasResetCond(cand.resetCond().varScopep != nullptr);
} else {
AstNodeExpr* resetCondp = nullptr;
FsmStateValue resetValue = 0;
FsmStateValue resetValue;
if (AstNodeAssign* const assp
= directCondStateVarAssign(nodep, stateVscp, nextVscp, resetCondp, resetValue)) {
cand.resetArcs().emplace_back(resetValue, assp);
@@ -1139,32 +1188,18 @@ class FsmDetectVisitor final : public VNVisitor {
return true;
}
// Helper: process a single condition expression and add it to the state space.
// Returns true on success, false if the state space is invalid.
static bool addCondToStateSpace(AstNodeExpr* condp, FsmStateSpace& stateSpace) {
FsmStateValue value = 0;
const ConstValueStatus status = constValueStatus(condp, value);
if (status != ConstValueStatus::OK) {
if (status == ConstValueStatus::XZ) {
condp->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum "
"state variable "
+ stateSpace.stateVarp->prettyNameQ()
+ " with X/Z state encoding values");
}
return false;
}
AstConst* const constp = VN_AS(condp, Const);
const StateConstLabel label = stateLabelForConst(constp);
static bool addValueToStateSpace(AstNode* nodep, FsmStateSpace& stateSpace,
const FsmStateValue& value, StateConstLabel label) {
const auto labelIt = stateSpace.labels.find(value);
if (labelIt != stateSpace.labels.end()) {
StateConstLabel& existingLabel = labelIt->second;
if (existingLabel.text != label.text && existingLabel.fromParam && label.fromParam) {
condp->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum "
nodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum "
"state variable "
+ stateSpace.stateVarp->prettyNameQ()
+ " with multiple labels for the same value "
+ cvtToStr(value) + ": " + existingLabel.text + " and "
+ label.text);
+ value.warnText() + ": " + existingLabel.text
+ " and " + label.text);
return false;
}
if (!existingLabel.fromParam && label.fromParam) {
@@ -1181,6 +1216,51 @@ class FsmDetectVisitor final : public VNVisitor {
return true;
}
// Helper: process a single observed state expression and add it to the state space
// Returns true on success, false if the state space is invalid
static bool addExprToStateSpace(AstNodeExpr* valuep, FsmStateSpace& stateSpace) {
FsmStateValue value;
const ConstValueStatus status = constValueStatus(valuep, value);
if (status != ConstValueStatus::OK) {
if (status == ConstValueStatus::XZ) {
valuep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum "
"state variable "
+ stateSpace.stateVarp->prettyNameQ()
+ " with X/Z state encoding values");
}
return false;
}
AstConst* const constp = VN_AS(valuep, Const);
return addValueToStateSpace(valuep, stateSpace, value, stateLabelForConst(constp));
}
static bool addOptionalTargetExprToStateSpace(AstNodeExpr* valuep, FsmStateSpace& stateSpace) {
FsmStateValue value;
const ConstValueStatus status = constValueStatus(valuep, value);
if (status != ConstValueStatus::OK) {
valuep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum "
"state variable "
+ stateSpace.stateVarp->prettyNameQ()
+ " with non-constant target state values");
return false;
}
AstConst* const constp = VN_AS(valuep, Const);
return addValueToStateSpace(valuep, stateSpace, value, stateLabelForConst(constp));
}
static void addResetTargetsToStateSpace(const std::vector<FsmResetArcDesc>& resetArcs,
FsmStateSpace& stateSpace) {
for (const FsmResetArcDesc& resetArc : resetArcs) {
StateConstLabel label{resetArc.toValue().ascii(), false, 0};
if (AstConst* const constp = VN_CAST(resetArc.nodep()->rhsp(), Const)) {
label = stateLabelForConst(constp);
}
UASSERT_OBJ(
addValueToStateSpace(resetArc.nodep(), stateSpace, resetArc.toValue(), label),
resetArc.nodep(), "reset target labels should be unambiguous");
}
}
// Build the Phase 1 state space from the tracked registered state
// variable, not from whichever signal the transition statement happened to use.
static bool collectDeclaredStateSpace(AstNode* warnNodep, AstVarScope* stateVscp,
@@ -1192,19 +1272,11 @@ class FsmDetectVisitor final : public VNVisitor {
stateSpace.stateVarp = stateVarp;
if (enump) {
if (stateVscp->width() > 32) {
warnNodep->v3warn(COVERIGN,
"Ignoring unsupported: FSM coverage on enum-typed state "
"variable "
+ stateSpace.stateVarp->prettyNameQ() + " with width "
+ cvtToStr(stateVscp->width()) + " wider than 32 bits");
return false;
}
stateSpace.enumBacked = true;
for (AstEnumItem* itemp = enump->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), EnumItem)) {
const AstConst* const constp = VN_AS(itemp->valuep(), Const);
const FsmStateValue value = constp->toUInt();
const FsmStateValue value{constp->num()};
const size_t stateIndex = stateSpace.states.size();
stateSpace.states.emplace_back(itemp->name(), value);
stateSpace.labels.emplace(value,
@@ -1214,34 +1286,17 @@ class FsmDetectVisitor final : public VNVisitor {
}
if (forced) {
const int width = stateVarp->width();
// Forced non-enum FSMs have no declared state list, so enumeration
// must stay small enough for this scalar graph representation.
if (width >= 31) return false;
const unsigned stateCount = 1U << width;
for (FsmStateValue value = 0; value < stateCount; ++value) {
const string label = "S" + cvtToStr(value);
const size_t stateIndex = stateSpace.states.size();
stateSpace.states.emplace_back(label, value);
stateSpace.labels.emplace(value, StateConstLabel{label, false, stateIndex});
}
needsSourceValues = true;
return true;
}
if (stateVscp->width() > 32) {
warnNodep->v3warn(COVERIGN, "Ignoring unsupported: FSM coverage on non-enum state "
"variable "
+ stateSpace.stateVarp->prettyNameQ() + " with width "
+ cvtToStr(stateVscp->width())
+ " wider than 32 bits");
return false;
}
needsSourceValues = true;
return true;
}
template <typename T_ValuepVisitor>
static bool collectStateSpaceFromValues(AstNode* warnNodep, AstVarScope* stateVscp,
const std::vector<FsmResetArcDesc>& resetArcs,
FsmStateSpace& stateSpace,
const T_ValuepVisitor& visitValueps) {
bool needsSourceValues = false;
@@ -1251,23 +1306,41 @@ class FsmDetectVisitor final : public VNVisitor {
return false;
}
if (!needsSourceValues) return true;
addResetTargetsToStateSpace(resetArcs, stateSpace);
if (!visitValueps(
[&](AstNodeExpr* valuep) { return addCondToStateSpace(valuep, stateSpace); })) {
[&](AstNodeExpr* valuep) { return addExprToStateSpace(valuep, stateSpace); })) {
return false;
}
return stateSpace.states.size() >= 2;
}
static bool collectStateSpace(AstCase* casep, AstVarScope* stateVscp,
static bool collectStateSpace(AstCase* casep, AstVarScope* stateVscp, AstVarScope* assignVscp,
const std::vector<FsmResetArcDesc>& resetArcs,
FsmStateSpace& stateSpace) {
return collectStateSpaceFromValues(
casep, stateVscp, stateSpace, [casep](const auto& visitValuep) {
casep, stateVscp, resetArcs, stateSpace,
[casep, assignVscp, &stateSpace](const auto& visitValuep) {
for (AstCaseItem* itemp = casep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), CaseItem)) {
if (itemp->isDefault()) continue;
for (AstNodeExpr* condp = itemp->condsp(); condp;
condp = VN_AS(condp->nextp(), NodeExpr)) {
if (!visitValuep(condp)) return false;
if (!itemp->isDefault()) {
for (AstNodeExpr* condp = itemp->condsp(); condp;
condp = VN_AS(condp->nextp(), NodeExpr)) {
if (!visitValuep(condp)) return false;
}
}
if (AstNodeAssign* const assp
= directStateAssign(itemp->stmtsp(), assignVscp)) {
FsmStateValue thenValue;
FsmStateValue elseValue;
AstCond* const condp = VN_CAST(assp->rhsp(), Cond);
if (condp
&& directStateCondConstAssign(itemp->stmtsp(), assignVscp, thenValue,
elseValue)) {
if (!visitValuep(condp->thenp())) return false;
if (!visitValuep(condp->elsep())) return false;
} else if (!addOptionalTargetExprToStateSpace(assp->rhsp(), stateSpace)) {
return false;
}
}
}
return true;
@@ -1275,14 +1348,32 @@ class FsmDetectVisitor final : public VNVisitor {
}
static bool collectStateSpace(const FsmIfChainCandidate& chain, AstVarScope* stateVscp,
AstVarScope* assignVscp,
const std::vector<FsmResetArcDesc>& resetArcs,
FsmStateSpace& stateSpace) {
return collectStateSpaceFromValues(
chain.ifp, stateVscp, stateSpace, [&chain](const auto& visitValuep) {
chain.ifp, stateVscp, resetArcs, stateSpace,
[&chain, assignVscp, &stateSpace](const auto& visitValuep) {
for (const FsmIfBranch& branch : chain.branches) {
// Reaching this point with an unresolvable source value
// would mean the if-chain classifier and emitter disagree.
UASSERT_OBJ(visitValuep(branch.valuep), branch.valuep,
"FSM if-chain source values should be prevalidated");
AstNodeAssign* const assp = directStateAssign(branch.stmtsp, assignVscp);
UASSERT_OBJ(assp, branch.stmtsp,
"FSM if-chain target values should be prevalidated");
FsmStateValue thenValue;
FsmStateValue elseValue;
AstCond* const condp = VN_CAST(assp->rhsp(), Cond);
if (condp) {
UASSERT_OBJ(directStateCondConstAssign(branch.stmtsp, assignVscp,
thenValue, elseValue),
condp, "FSM if-chain ternary targets should be prevalidated");
if (!visitValuep(condp->thenp())) return false;
if (!visitValuep(condp->elsep())) return false;
} else if (!addOptionalTargetExprToStateSpace(assp->rhsp(), stateSpace)) {
return false;
}
}
return true;
});
@@ -1297,11 +1388,11 @@ class FsmDetectVisitor final : public VNVisitor {
std::vector<std::pair<string, FsmStateValue>> froms;
if (itemp->isDefault()) {
if (!inclCond) return false;
froms.emplace_back("default", 0);
froms.emplace_back("default", FsmStateValue{});
} else {
for (AstNodeExpr* condp = itemp->condsp(); condp;
condp = VN_AS(condp->nextp(), NodeExpr)) {
FsmStateValue value = 0;
FsmStateValue value;
if (constValueStatus(condp, value) != ConstValueStatus::OK) continue;
if (!validateKnownStateValue(condp, stateSpace, value, "source")) return true;
froms.emplace_back(labelForValue(stateSpace.labels, value), value);
@@ -1310,7 +1401,7 @@ class FsmDetectVisitor final : public VNVisitor {
}
if (AstNodeAssign* const assp = directStateAssign(itemp->stmtsp(), stateVscp)) {
FsmStateValue toValue = 0;
FsmStateValue toValue;
const ConstValueStatus status = constValueStatus(assp->rhsp(), toValue);
if (status == ConstValueStatus::OK) {
if (!validateKnownStateValue(assp, stateSpace, toValue, "target")) return true;
@@ -1322,15 +1413,15 @@ class FsmDetectVisitor final : public VNVisitor {
}
}
FsmStateValue thenValue = 0;
FsmStateValue elseValue = 0;
FsmStateValue thenValue;
FsmStateValue elseValue;
if (directStateCondConstAssign(itemp->stmtsp(), stateVscp, thenValue, elseValue)
|| ifStateConstAssign(itemp->stmtsp(), stateVscp, thenValue, elseValue)) {
if (!validateKnownStateValue(itemp->stmtsp(), stateSpace, thenValue, "target"))
return true;
if (!validateKnownStateValue(itemp->stmtsp(), stateSpace, elseValue, "target"))
return true;
for (const FsmStateValue branchValue : {thenValue, elseValue}) {
for (const FsmStateValue& branchValue : {thenValue, elseValue}) {
for (const std::pair<string, FsmStateValue>& from : froms) {
graph.addArc(from.second, branchValue, false, true, itemp->isDefault(),
itemp->stmtsp()->fileline());
@@ -1347,7 +1438,7 @@ class FsmDetectVisitor final : public VNVisitor {
bool isDefault, bool forceCond) {
AstNodeAssign* const assp = directStateAssign(stmtsp, stateVscp);
UASSERT_OBJ(assp, stmtsp, "FSM if-chain branch should have been prevalidated");
FsmStateValue toValue = 0;
FsmStateValue toValue;
const ConstValueStatus status = constValueStatus(assp->rhsp(), toValue);
if (status == ConstValueStatus::OK) {
if (!validateKnownStateValue(assp, stateSpace, toValue, "target")) return;
@@ -1357,15 +1448,15 @@ class FsmDetectVisitor final : public VNVisitor {
return;
}
FsmStateValue thenValue = 0;
FsmStateValue elseValue = 0;
FsmStateValue thenValue;
FsmStateValue elseValue;
const bool condAssign
= directStateCondConstAssign(stmtsp, stateVscp, thenValue, elseValue);
UASSERT_OBJ(condAssign, stmtsp,
"FSM if-chain branch should be a direct constant transition");
if (!validateKnownStateValue(stmtsp, stateSpace, thenValue, "target")) return;
if (!validateKnownStateValue(stmtsp, stateSpace, elseValue, "target")) return;
for (const FsmStateValue branchValue : {thenValue, elseValue}) {
for (const FsmStateValue& branchValue : {thenValue, elseValue}) {
graph.addArc(fromValue, branchValue, false, true, isDefault, stmtsp->fileline());
}
}
@@ -1391,7 +1482,8 @@ class FsmDetectVisitor final : public VNVisitor {
if (!validateKnownStateValue(resetArc.nodep(), stateSpace, resetArc.toValue(),
"target"))
continue;
graph.addArc(0, resetArc.toValue(), true, false, false, resetArc.nodep()->fileline());
graph.addArc(FsmStateValue{}, resetArc.toValue(), true, false, false,
resetArc.nodep()->fileline());
}
}
@@ -1402,7 +1494,7 @@ class FsmDetectVisitor final : public VNVisitor {
UASSERT_OBJ(assignVscp, casep, "FSM case processing requires a non-null assignment var");
AstVarScope* const stateVscp = reg.stateVscp();
FsmStateSpace stateSpace;
if (!collectStateSpace(casep, stateVscp, stateSpace)) return;
if (!collectStateSpace(casep, stateVscp, assignVscp, reg.resetArcs(), stateSpace)) return;
DetectedFsm& entry = m_state.fsms()[stateVscp];
if (!entry.graphp) {
entry.graphp.reset(new FsmGraph{});
@@ -1435,7 +1527,7 @@ class FsmDetectVisitor final : public VNVisitor {
"FSM if-chain processing requires a non-null assignment var");
AstVarScope* const stateVscp = reg.stateVscp();
FsmStateSpace stateSpace;
if (!collectStateSpace(chain, stateVscp, stateSpace)) return;
if (!collectStateSpace(chain, stateVscp, assignVscp, reg.resetArcs(), stateSpace)) return;
DetectedFsm& entry = m_state.fsms()[stateVscp];
// Case candidates keep ownership of existing graphs; reaching this path
// means the if-chain is the only supported dispatch for this FSM.
@@ -1698,8 +1790,9 @@ class FsmLowerVisitor final {
// METHODS
// Rebuild a state-typed constant using the tracked state variable
// width/sign so emitted comparisons match the original representation.
static AstConst* makeStateConst(FileLine* flp, AstVarScope* vscp, FsmStateValue value) {
V3Number num{flp, vscp->width(), value};
static AstConst* makeStateConst(FileLine* flp, AstVarScope* vscp, const FsmStateValue& value) {
V3Number num{static_cast<AstNode*>(nullptr), vscp->width()};
num.opAssign(value.num());
num.isSigned(vscp->dtypep()->isSigned());
return new AstConst{flp, num};
}