mirror of
https://github.com/verilator/verilator.git
synced 2026-09-07 18:06:07 +02:00
Extend FSM Detect to support 'Wide State Encodings' (#7573)
This commit is contained in:
+208
-115
@@ -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};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user