mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 01:54:01 +02:00
Fix NFA assertion crash and mis-counted property if/case (#8074)
This commit is contained in:
+232
-82
@@ -21,6 +21,9 @@
|
||||
// - Replace converted assertions with combinational match/reject checks
|
||||
// so V3AssertPre sees no multi-cycle SExpr (unsupported ones fall through).
|
||||
//
|
||||
// Members marked OWNED hold an AST tree this pass allocated and must delete;
|
||||
// they are not linked into the netlist.
|
||||
//
|
||||
//*************************************************************************
|
||||
|
||||
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
||||
@@ -55,7 +58,7 @@ struct SvaVertexData final {
|
||||
AstVar* delayRingWrappedVarp = nullptr; // All slots written since the last clear
|
||||
AstVar* doneLVarp = nullptr; // SAnd LHS done-latch
|
||||
AstVar* doneRVarp = nullptr; // SAnd RHS done-latch
|
||||
AstNodeExpr* stateSigp = nullptr; // Combinational state signal (owned during lowering)
|
||||
AstNodeExpr* stateSigp = nullptr; // Combinational state signal; OWNED during lowering
|
||||
bool needsReg = false; // True if vertex has incoming clocked edge
|
||||
};
|
||||
|
||||
@@ -65,12 +68,14 @@ class SvaStateVertex final : public V3GraphVertex {
|
||||
public:
|
||||
// True if this is the sequence-match terminal vertex
|
||||
bool m_isMatch = false;
|
||||
// Owned throughout-guard condition clones; IEEE 1800-2023 16.9.9
|
||||
// OWNED throughout-guard condition clones; IEEE 1800-2023 16.9.9
|
||||
std::vector<AstNodeExpr*> m_throughoutConds;
|
||||
// Nonzero for a bitset ring-buffer vertex for ## delays.
|
||||
bool m_isFixedDelayRing = false;
|
||||
int m_delayRingSize = 0; // Fixed delay cycles. Range: max-min+1.
|
||||
AstNodeExpr* m_delayRingClearCondp = nullptr; // local RHS for pure-boolean range
|
||||
// OWNED; enclosing-abort fire condition clearing in-flight ring bits
|
||||
AstNodeExpr* m_abortClearp = nullptr;
|
||||
// Liveness terminal (IEEE weak semantics): reject must not fire from this source
|
||||
bool m_isUnbounded = false;
|
||||
// Temporal sequence AND combiner; IEEE 1800-2023 16.9.5
|
||||
@@ -95,6 +100,7 @@ public:
|
||||
for (AstNodeExpr* cp : m_throughoutConds) VL_DO_DANGLING(cp->deleteTree(), cp);
|
||||
if (m_delayRingClearCondp)
|
||||
VL_DO_DANGLING(m_delayRingClearCondp->deleteTree(), m_delayRingClearCondp);
|
||||
if (m_abortClearp) VL_DO_DANGLING(m_abortClearp->deleteTree(), m_abortClearp);
|
||||
if (m_andLhsCondp) VL_DO_DANGLING(m_andLhsCondp->deleteTree(), m_andLhsCondp);
|
||||
if (m_andRhsCondp) VL_DO_DANGLING(m_andRhsCondp->deleteTree(), m_andRhsCondp);
|
||||
}
|
||||
@@ -183,8 +189,8 @@ public:
|
||||
std::vector<const SvaTransEdge*> allEdges() const {
|
||||
std::vector<const SvaTransEdge*> result;
|
||||
for (const V3GraphVertex& vtxr : m_graph.vertices()) {
|
||||
for (const V3GraphEdge& er : vtxr.outEdges()) {
|
||||
result.push_back(static_cast<const SvaTransEdge*>(&er));
|
||||
for (const V3GraphEdge& edger : vtxr.outEdges()) {
|
||||
result.push_back(static_cast<const SvaTransEdge*>(&edger));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -208,6 +214,11 @@ struct BuildResult final {
|
||||
static BuildResult failWithError() { return {nullptr, nullptr, {}, true}; }
|
||||
};
|
||||
|
||||
// Parser-marked SAnd of overlapped implications: a property if/else/case.
|
||||
static bool hasPropertyControlConjunction(const AstNodeExpr* nodep) {
|
||||
return nodep->exists([](const AstSAnd* andp) { return andp->propertyControl(); });
|
||||
}
|
||||
|
||||
static AstConst* newTypedConstp(FileLine* const flp, const AstNodeDType* const dtypep,
|
||||
const uint32_t value) {
|
||||
AstConst* const constp = new AstConst{flp, AstConst::DTyped{}, dtypep};
|
||||
@@ -722,6 +733,7 @@ class SvaNfaBuilder final {
|
||||
// Do not mark liveness sources: first boolean check is deferred.
|
||||
edgep->m_rejectOnFail = true;
|
||||
}
|
||||
freeUnlinkedCondp(pre.finalCondp);
|
||||
currentp = condVtxp;
|
||||
} else {
|
||||
currentp = pre.termVertexp;
|
||||
@@ -965,12 +977,20 @@ class SvaNfaBuilder final {
|
||||
return {mergeVtxp, nullptr, {}};
|
||||
}
|
||||
|
||||
// Free a dropped sub-result condition that is not linked into the AST
|
||||
// (abort folds synthesize unparented finalCondp trees).
|
||||
static void freeUnlinkedCondp(AstNodeExpr* condp) {
|
||||
if (condp && !condp->backp()) VL_DO_DANGLING(condp->deleteTree(), condp);
|
||||
}
|
||||
|
||||
// Build merge vertex for SOr / LogOr: both branches feed into one vertex.
|
||||
BuildResult buildOrMerge(AstNodeExpr* lhsp, AstNodeExpr* rhsp, SvaStateVertex* entryVtxp,
|
||||
FileLine* flp) {
|
||||
const BuildResult lhs = buildExpr(lhsp, entryVtxp);
|
||||
const BuildResult rhs = buildExpr(rhsp, entryVtxp);
|
||||
if (!lhs.valid() || !rhs.valid()) { // LCOV_EXCL_START -- sub-build fail bail
|
||||
freeUnlinkedCondp(lhs.finalCondp);
|
||||
freeUnlinkedCondp(rhs.finalCondp);
|
||||
return BuildResult::fail(lhs.errorEmitted || rhs.errorEmitted);
|
||||
} // LCOV_EXCL_STOP
|
||||
// IEEE 1800-2023 16.14.3: a cover sequence counts every end-of-match. A
|
||||
@@ -980,6 +1000,8 @@ class SvaNfaBuilder final {
|
||||
// is handled by the OR-fold.
|
||||
if (m_isCoverSeq && (lhs.termVertexp != entryVtxp || rhs.termVertexp != entryVtxp)) {
|
||||
warnEndpointUnsupported(flp, "a sequence operand of 'or'");
|
||||
freeUnlinkedCondp(lhs.finalCondp);
|
||||
freeUnlinkedCondp(rhs.finalCondp);
|
||||
return BuildResult::failWithError();
|
||||
}
|
||||
SvaStateVertex* const mergeVtxp = scopedCreateVertex();
|
||||
@@ -995,6 +1017,8 @@ class SvaNfaBuilder final {
|
||||
} else {
|
||||
guardedLink(rhs.termVertexp, mergeVtxp, flp);
|
||||
}
|
||||
freeUnlinkedCondp(lhs.finalCondp);
|
||||
freeUnlinkedCondp(rhs.finalCondp);
|
||||
return {mergeVtxp, nullptr, {}};
|
||||
}
|
||||
|
||||
@@ -1010,6 +1034,8 @@ class SvaNfaBuilder final {
|
||||
const bool rhsScope = m_inUnboundedScope;
|
||||
m_inUnboundedScope = savedScope || lhsScope || rhsScope;
|
||||
if (!lhs.valid() || !rhs.valid()) { // LCOV_EXCL_START -- sub-build fail bail
|
||||
freeUnlinkedCondp(lhs.finalCondp);
|
||||
freeUnlinkedCondp(rhs.finalCondp);
|
||||
return BuildResult::fail(lhs.errorEmitted || rhs.errorEmitted);
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
@@ -1021,12 +1047,20 @@ class SvaNfaBuilder final {
|
||||
"Single-cycle SAnd operands must have finalCondp");
|
||||
AstNodeExpr* const condp = new AstLogAnd{flp, lhs.finalCondp->cloneTreePure(false),
|
||||
rhs.finalCondp->cloneTreePure(false)};
|
||||
freeUnlinkedCondp(lhs.finalCondp);
|
||||
freeUnlinkedCondp(rhs.finalCondp);
|
||||
return {entryVtxp, condp, {}};
|
||||
}
|
||||
// Range-delay mid-window sources in either sub-branch would need
|
||||
// to be folded into the latch's match-now signal, which the
|
||||
// current combiner does not support. Defer (UNSUPPORTED).
|
||||
if (!lhs.midSources.empty() || !rhs.midSources.empty()) return BuildResult::fail();
|
||||
// current combiner does not support.
|
||||
if (!lhs.midSources.empty() || !rhs.midSources.empty()) {
|
||||
flp->v3warn(E_UNSUPPORTED,
|
||||
"Unsupported: ranged cycle delay in an operand of property 'and'");
|
||||
freeUnlinkedCondp(lhs.finalCondp);
|
||||
freeUnlinkedCondp(rhs.finalCondp);
|
||||
return BuildResult::failWithError();
|
||||
}
|
||||
SvaStateVertex* const combVtxp = scopedCreateVertex();
|
||||
combVtxp->m_isAndCombiner = true;
|
||||
combVtxp->m_andLhsTermp = lhs.termVertexp;
|
||||
@@ -1064,6 +1098,8 @@ class SvaNfaBuilder final {
|
||||
}
|
||||
}
|
||||
}
|
||||
freeUnlinkedCondp(lhs.finalCondp);
|
||||
freeUnlinkedCondp(rhs.finalCondp);
|
||||
return {combVtxp, nullptr, {}};
|
||||
}
|
||||
|
||||
@@ -1421,16 +1457,74 @@ class SvaNfaBuilder final {
|
||||
return resultp;
|
||||
}
|
||||
|
||||
// True when a same-tick Link chain already accounts the attempt: a
|
||||
// required-step Link covers both outcomes; followed-by pairs both edges.
|
||||
static bool chainAccountsSource(const SvaStateVertex* srcp,
|
||||
const std::unordered_set<const V3GraphEdge*>& preEdges) {
|
||||
bool plainNonSink = false;
|
||||
bool markedSink = false;
|
||||
for (const V3GraphEdge& edger : srcp->outEdges()) {
|
||||
if (preEdges.count(&edger)) continue;
|
||||
const SvaTransEdge& tedger = static_cast<const SvaTransEdge&>(edger);
|
||||
if (tedger.m_consumesCycle) continue;
|
||||
const bool sink = static_cast<const SvaStateVertex*>(tedger.toVtxp())->m_isRejectSink;
|
||||
if (tedger.m_rejectOnFail) {
|
||||
if (!sink) return true;
|
||||
markedSink = true;
|
||||
} else if (!sink) {
|
||||
plainNonSink = true;
|
||||
}
|
||||
}
|
||||
return plainNonSink && markedSink;
|
||||
}
|
||||
|
||||
// Reject edge: fires when the source is live and the abort samples true.
|
||||
void addAbortRejectEdge(SvaStateVertex* srcp, SvaStateVertex* sinkp, AstNodeExpr* condp,
|
||||
FileLine* flp) {
|
||||
AstNodeExpr* const notFirep = new AstLogNot{flp, sampled(abortFireExpr(condp, flp))};
|
||||
m_graph.addLink(srcp, sinkp, notFirep)->m_rejectOnFail = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// On the fire tick: kill body threads; accept kinds also forgive step misses.
|
||||
void gateBodyEdgesOnAbort(const std::unordered_set<const V3GraphEdge*>& preEdges,
|
||||
AstNodeExpr* condp, VAbortKind kind, FileLine* flp) {
|
||||
for (V3GraphVertex& vtxr : m_graph.m_graph.vertices()) {
|
||||
for (V3GraphEdge& edger : vtxr.outEdges()) {
|
||||
if (preEdges.count(&edger)) continue;
|
||||
SvaTransEdge* const tedgep = static_cast<SvaTransEdge*>(&edger);
|
||||
if (tedgep->m_rejectOnFail) {
|
||||
if (!kind.isAccept()) continue;
|
||||
AstNodeExpr* const firep = sampled(abortFireExpr(condp, flp));
|
||||
tedgep->m_condp
|
||||
= tedgep->m_condp ? new AstLogOr{flp, tedgep->m_condp, firep} : firep;
|
||||
} else if (tedgep->m_consumesCycle) {
|
||||
AstNodeExpr* const notFirep
|
||||
= new AstLogNot{flp, sampled(abortFireExpr(condp, flp))};
|
||||
tedgep->m_condp = tedgep->m_condp
|
||||
? new AstLogAnd{flp, tedgep->m_condp, notFirep}
|
||||
: notFirep;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BuildResult buildAbortOn(AstNodeExpr* condp, AstNodeExpr* bodyp, SvaStateVertex* entryVtxp,
|
||||
VAbortKind kind, FileLine* flp) {
|
||||
// Snapshot pre-body vertices so post-build diff yields the body's sub-NFA.
|
||||
VAbortKind kind, FileLine* flp, bool isTopLevelStep) {
|
||||
// Snapshot pre-body vertices/edges so post-build diff yields the body's sub-NFA.
|
||||
std::unordered_set<const V3GraphVertex*> preExisting;
|
||||
for (const V3GraphVertex& vtxr : m_graph.m_graph.vertices()) preExisting.insert(&vtxr);
|
||||
std::unordered_set<const V3GraphEdge*> preEdges;
|
||||
for (V3GraphVertex& vtxr : m_graph.m_graph.vertices()) {
|
||||
preExisting.insert(&vtxr);
|
||||
for (V3GraphEdge& edger : vtxr.outEdges()) preEdges.insert(&edger);
|
||||
}
|
||||
|
||||
m_outerAbortStack.push_back(condp);
|
||||
const BuildResult bodyResult = buildExpr(bodyp, entryVtxp, /*isTopLevelStep=*/false);
|
||||
const BuildResult bodyResult = buildExpr(bodyp, entryVtxp, isTopLevelStep);
|
||||
m_outerAbortStack.pop_back();
|
||||
UASSERT_OBJ(bodyResult.valid(), bodyp, "abort body must be a valid SVA expression");
|
||||
if (!bodyResult.valid()) return bodyResult;
|
||||
|
||||
gateBodyEdgesOnAbort(preEdges, condp, kind, flp);
|
||||
|
||||
// Live-thread sources for the abort edge: entry + new body vertices,
|
||||
// minus reject sinks (they carry reject fuel, not live-thread fuel).
|
||||
@@ -1439,6 +1533,11 @@ class SvaNfaBuilder final {
|
||||
for (V3GraphVertex& vtxr : m_graph.m_graph.vertices()) {
|
||||
if (preExisting.count(&vtxr)) continue;
|
||||
auto* const sp = static_cast<SvaStateVertex*>(&vtxr);
|
||||
if (sp->m_delayRingSize) {
|
||||
AstNodeExpr* const firep = abortFireExpr(condp, flp);
|
||||
sp->m_abortClearp
|
||||
= sp->m_abortClearp ? new AstLogOr{flp, sp->m_abortClearp, firep} : firep;
|
||||
}
|
||||
if (sp->m_isRejectSink) continue;
|
||||
abortSources.push_back(sp);
|
||||
}
|
||||
@@ -1450,15 +1549,18 @@ class SvaNfaBuilder final {
|
||||
|
||||
if (kind.isAccept()) {
|
||||
// Match-only sink fed by $sampled(abort-fire) from every live source;
|
||||
// registered as midSource so it never contributes a reject. The body
|
||||
// terminal is already in abortSources, so we don't fold abort-fire
|
||||
// into bodyResult.finalCondp.
|
||||
// registered as midSource so it never contributes a reject.
|
||||
SvaStateVertex* const acceptSinkp = scopedCreateVertex();
|
||||
for (SvaStateVertex* const srcp : abortSources)
|
||||
guardedLink(srcp, acceptSinkp, sampledAbortFire(), flp);
|
||||
std::vector<SvaStateVertex*> midSources = bodyResult.midSources;
|
||||
midSources.push_back(acceptSinkp);
|
||||
return {bodyResult.termVertexp, bodyResult.finalCondp, std::move(midSources)};
|
||||
AstNodeExpr* finalCondp = bodyResult.finalCondp;
|
||||
if (finalCondp) {
|
||||
if (finalCondp->backp()) finalCondp = finalCondp->cloneTreePure(false);
|
||||
finalCondp = new AstLogOr{flp, finalCondp, abortFireExpr(condp, flp)};
|
||||
}
|
||||
return {bodyResult.termVertexp, finalCondp, std::move(midSources)};
|
||||
}
|
||||
|
||||
// rejectOnFail treats m_condp as the success condition and fires on
|
||||
@@ -1466,10 +1568,15 @@ class SvaNfaBuilder final {
|
||||
SvaStateVertex* const rejectSinkp = m_graph.createStateVertex();
|
||||
rejectSinkp->m_isRejectSink = true;
|
||||
for (SvaStateVertex* const srcp : abortSources)
|
||||
m_graph.addLink(srcp, rejectSinkp, new AstLogNot{flp, sampledAbortFire()})
|
||||
->m_rejectOnFail
|
||||
= true;
|
||||
return bodyResult;
|
||||
if (!chainAccountsSource(srcp, preEdges))
|
||||
addAbortRejectEdge(srcp, rejectSinkp, condp, flp);
|
||||
AstNodeExpr* finalCondp = bodyResult.finalCondp;
|
||||
if (finalCondp) {
|
||||
if (finalCondp->backp()) finalCondp = finalCondp->cloneTreePure(false);
|
||||
finalCondp
|
||||
= new AstLogAnd{flp, finalCondp, new AstLogNot{flp, abortFireExpr(condp, flp)}};
|
||||
}
|
||||
return {bodyResult.termVertexp, finalCondp, bodyResult.midSources};
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -1482,10 +1589,11 @@ public:
|
||||
, m_isSeqEvent{isSeqEvent} {}
|
||||
|
||||
// Reset scope between antecedent and consequent: liveness must not leak.
|
||||
// m_outerAbortStack survives: an abort wrapping the implication covers the
|
||||
// consequent too (IEEE 1800-2023 16.12.14).
|
||||
void resetScope() {
|
||||
m_inUnboundedScope = false;
|
||||
m_temporalGuardStack.clear();
|
||||
m_outerAbortStack.clear();
|
||||
}
|
||||
|
||||
BuildResult buildExpr(AstNodeExpr* nodep, SvaStateVertex* entryVtxp,
|
||||
@@ -1540,7 +1648,8 @@ public:
|
||||
return buildSWithin(withinp, entryVtxp, isTopLevelStep);
|
||||
}
|
||||
if (AstAbortOn* const ap = VN_CAST(nodep, AbortOn)) {
|
||||
return buildAbortOn(ap->condp(), ap->propp(), entryVtxp, ap->kind(), ap->fileline());
|
||||
return buildAbortOn(ap->condp(), ap->propp(), entryVtxp, ap->kind(), ap->fileline(),
|
||||
isTopLevelStep);
|
||||
}
|
||||
if (VN_IS(nodep, SNonConsRep)) return BuildResult::fail();
|
||||
if (AstImplication* const implp = VN_CAST(nodep, Implication)) {
|
||||
@@ -1746,21 +1855,19 @@ class SvaNfaLowering final {
|
||||
// latches the OR of its incoming contributions.
|
||||
void emitStateRegisterNba(LowerCtx& c) {
|
||||
AstNode* bodyp = nullptr;
|
||||
bool hasDelayRing = false;
|
||||
for (int i = 0; i < c.N; ++i) {
|
||||
if (c.vtx[i]->datap()->delayRingVarp) hasDelayRing = true;
|
||||
if (!c.vtx[i]->datap()->stateVarp) continue;
|
||||
|
||||
AstNodeExpr* nextStatep = nullptr;
|
||||
for (const V3GraphEdge& er : c.vtx[i]->inEdges()) {
|
||||
const SvaTransEdge& te = static_cast<const SvaTransEdge&>(er);
|
||||
if (!te.m_consumesCycle) continue;
|
||||
const int fromIdx = te.fromVtxp()->color();
|
||||
UASSERT_OBJ(c.vtx[fromIdx]->datap()->stateSigp, te.fromVtxp(),
|
||||
for (const V3GraphEdge& edger : c.vtx[i]->inEdges()) {
|
||||
const SvaTransEdge& tedger = static_cast<const SvaTransEdge&>(edger);
|
||||
if (!tedger.m_consumesCycle) continue;
|
||||
const int fromIdx = tedger.fromVtxp()->color();
|
||||
UASSERT_OBJ(c.vtx[fromIdx]->datap()->stateSigp, tedger.fromVtxp(),
|
||||
"Clocked-edge source missing stateSig");
|
||||
|
||||
AstNodeExpr* srcSigp = c.vtx[fromIdx]->datap()->stateSigp->cloneTreePure(false);
|
||||
srcSigp = andCond(c.flp, srcSigp, te.m_condp);
|
||||
srcSigp = andCond(c.flp, srcSigp, tedger.m_condp);
|
||||
|
||||
if (c.disableExprp) {
|
||||
AstNodeExpr* const notDisp
|
||||
@@ -1781,8 +1888,8 @@ class SvaNfaLowering final {
|
||||
}
|
||||
|
||||
// Capture disableCnt in Phase-2 NBA before any reactive re-evaluation.
|
||||
// snapshotVarp and disableCntVarp are allocated together.
|
||||
if (c.snapshotVarp && (bodyp || hasDelayRing)) {
|
||||
// Emitted even for stateless graphs; snapshotOk gates rejects there too.
|
||||
if (c.snapshotVarp) {
|
||||
UASSERT_OBJ(c.disableCntVarp, c.senTreep, "snapshotVarp set without disableCntVarp");
|
||||
// disable_snapshot <= disable_count;
|
||||
AstAssignDly* const snapshotp
|
||||
@@ -1807,15 +1914,15 @@ class SvaNfaLowering final {
|
||||
const uint32_t size = static_cast<uint32_t>(vtxp->m_delayRingSize);
|
||||
|
||||
AstNodeExpr* incomingp = nullptr;
|
||||
for (const SvaTransEdge* const tep : c.edges) {
|
||||
if (static_cast<int>(tep->toVtxp()->color()) != ri) continue;
|
||||
UASSERT_OBJ(tep->m_consumesCycle == vtxp->m_isFixedDelayRing, vtxp,
|
||||
for (const SvaTransEdge* const tedgep : c.edges) {
|
||||
if (static_cast<int>(tedgep->toVtxp()->color()) != ri) continue;
|
||||
UASSERT_OBJ(tedgep->m_consumesCycle == vtxp->m_isFixedDelayRing, vtxp,
|
||||
"Delay-ring incoming edge kind mismatch");
|
||||
const int fi = tep->fromVtxp()->color();
|
||||
const int fi = tedgep->fromVtxp()->color();
|
||||
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp, c.vtx[fi],
|
||||
"Delay-ring incoming source missing stateSig");
|
||||
AstNodeExpr* contribp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false);
|
||||
contribp = andCond(c.flp, contribp, tep->m_condp);
|
||||
contribp = andCond(c.flp, contribp, tedgep->m_condp);
|
||||
if (c.disableExprp) {
|
||||
AstNodeExpr* const notDisp
|
||||
= new AstLogNot{c.flp, c.disableExprp->cloneTreePure(false)};
|
||||
@@ -1858,6 +1965,10 @@ class SvaNfaLowering final {
|
||||
clearCondp = orExprs(c.flp, clearCondp,
|
||||
sampled(vtxp->m_delayRingClearCondp->cloneTreePure(false)));
|
||||
}
|
||||
if (vtxp->m_abortClearp) {
|
||||
clearCondp = orExprs(c.flp, clearCondp,
|
||||
sampled(vtxp->m_abortClearp->cloneTreePure(false)));
|
||||
}
|
||||
if (c.disableExprp) {
|
||||
clearCondp = orExprs(c.flp, clearCondp, c.disableExprp->cloneTreePure(false));
|
||||
}
|
||||
@@ -1960,14 +2071,14 @@ class SvaNfaLowering final {
|
||||
// end-of-match fires the action independently, no OR-fold).
|
||||
void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs,
|
||||
std::vector<AstNodeExpr*>* outPerMidSrcsp = nullptr) {
|
||||
for (const SvaTransEdge* const tep : c.edges) {
|
||||
if (tep->toVtxp() != c.graph.m_matchVertexp) continue;
|
||||
const int fi = tep->fromVtxp()->color();
|
||||
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp, tep->fromVtxp(),
|
||||
for (const SvaTransEdge* const tedgep : c.edges) {
|
||||
if (tedgep->toVtxp() != c.graph.m_matchVertexp) continue;
|
||||
const int fi = tedgep->fromVtxp()->color();
|
||||
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp, tedgep->fromVtxp(),
|
||||
"Terminal-link source missing stateSig");
|
||||
|
||||
AstNodeExpr* srcSigp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false);
|
||||
srcSigp = andCond(c.flp, srcSigp, tep->m_condp);
|
||||
srcSigp = andCond(c.flp, srcSigp, tedgep->m_condp);
|
||||
if (snapshotOkp) {
|
||||
srcSigp = new AstLogAnd{c.flp, srcSigp, snapshotOkp->cloneTreePure(false)};
|
||||
}
|
||||
@@ -1984,19 +2095,19 @@ class SvaNfaLowering final {
|
||||
outPerMidSrcsp->push_back(perMidp);
|
||||
}
|
||||
|
||||
if (tep->fromVtxp()->m_delayRingSize && !tep->fromVtxp()->m_isFixedDelayRing) {
|
||||
if (tedgep->fromVtxp()->m_delayRingSize && !tedgep->fromVtxp()->m_isFixedDelayRing) {
|
||||
sigs.terminalActivep
|
||||
= orExprs(c.flp, sigs.terminalActivep, srcSigp->cloneTreePure(false));
|
||||
// reject |= ring[next_idx] && final_condition;
|
||||
AstNodeExpr* expireContribp = delayRingOutput(c.flp, tep->fromVtxp());
|
||||
expireContribp = andCond(c.flp, expireContribp, tep->m_condp);
|
||||
AstNodeExpr* expireContribp = delayRingOutput(c.flp, tedgep->fromVtxp());
|
||||
expireContribp = andCond(c.flp, expireContribp, tedgep->m_condp);
|
||||
if (snapshotOkp) {
|
||||
expireContribp
|
||||
= new AstLogAnd{c.flp, expireContribp, snapshotOkp->cloneTreePure(false)};
|
||||
}
|
||||
sigs.rejectBasep = orExprs(c.flp, sigs.rejectBasep, expireContribp);
|
||||
VL_DO_DANGLING(srcSigp->deleteTree(), srcSigp);
|
||||
} else if (tep->fromVtxp()->m_isUnbounded || tep->fromVtxp()->m_isAndCombiner) {
|
||||
} else if (tedgep->fromVtxp()->m_isUnbounded || tedgep->fromVtxp()->m_isAndCombiner) {
|
||||
sigs.terminalActivep = orExprs(c.flp, sigs.terminalActivep, srcSigp);
|
||||
} else {
|
||||
sigs.terminalActivep
|
||||
@@ -2075,21 +2186,24 @@ class SvaNfaLowering final {
|
||||
// Phase 3a: required-step rejection.
|
||||
// Builder only sets m_rejectOnFail on non-clocked Links with m_condp
|
||||
// or m_condVtxp, and the source always has a resolved stateSig.
|
||||
for (const SvaTransEdge* const tep : c.edges) {
|
||||
if (!tep->m_rejectOnFail) continue;
|
||||
const int fi = tep->fromVtxp()->color();
|
||||
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && (tep->m_condp || tep->m_condVtxp),
|
||||
tep->fromVtxp(),
|
||||
for (const SvaTransEdge* const tedgep : c.edges) {
|
||||
if (!tedgep->m_rejectOnFail) continue;
|
||||
const int fi = tedgep->fromVtxp()->color();
|
||||
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && (tedgep->m_condp || tedgep->m_condVtxp),
|
||||
tedgep->fromVtxp(),
|
||||
"rejectOnFail Link must have condp/condVtxp and source stateSig");
|
||||
AstNodeExpr* const srcSigp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false);
|
||||
AstNodeExpr* condp = nullptr;
|
||||
if (tep->m_condVtxp) {
|
||||
const int ci = tep->m_condVtxp->color();
|
||||
UASSERT_OBJ(c.vtx[ci]->datap()->stateSigp, tep->m_condVtxp,
|
||||
if (tedgep->m_condVtxp) {
|
||||
const int ci = tedgep->m_condVtxp->color();
|
||||
UASSERT_OBJ(c.vtx[ci]->datap()->stateSigp, tedgep->m_condVtxp,
|
||||
"rejectOnFail condVtxp missing stateSig");
|
||||
condp = c.vtx[ci]->datap()->stateSigp->cloneTreePure(false);
|
||||
if (tedgep->m_condp) {
|
||||
condp = new AstLogOr{c.flp, condp, tedgep->m_condp->cloneTreePure(false)};
|
||||
}
|
||||
} else {
|
||||
condp = tep->m_condp->cloneTreePure(false);
|
||||
condp = tedgep->m_condp->cloneTreePure(false);
|
||||
}
|
||||
AstNodeExpr* const notCondp = new AstLogNot{c.flp, condp};
|
||||
AstNodeExpr* const rawFailp = new AstLogAnd{c.flp, srcSigp, notCondp};
|
||||
@@ -2204,13 +2318,14 @@ class SvaNfaLowering final {
|
||||
// Propagate Link edges
|
||||
for (int fi = 0; fi < c.N; ++fi) {
|
||||
if (!c.vtx[fi]->datap()->stateSigp) continue;
|
||||
for (const V3GraphEdge& er : c.vtx[fi]->outEdges()) {
|
||||
const SvaTransEdge& te = static_cast<const SvaTransEdge&>(er);
|
||||
if (te.m_consumesCycle) continue;
|
||||
const int ti = te.toVtxp()->color();
|
||||
if (te.toVtxp()->m_isMatch || te.toVtxp()->m_isRejectSink) continue;
|
||||
AstNodeExpr* const contributionp = andCond(
|
||||
c.flp, c.vtx[fi]->datap()->stateSigp->cloneTreePure(false), te.m_condp);
|
||||
for (const V3GraphEdge& edger : c.vtx[fi]->outEdges()) {
|
||||
const SvaTransEdge& tedger = static_cast<const SvaTransEdge&>(edger);
|
||||
if (tedger.m_consumesCycle) continue;
|
||||
const int ti = tedger.toVtxp()->color();
|
||||
if (tedger.toVtxp()->m_isMatch || tedger.toVtxp()->m_isRejectSink) continue;
|
||||
AstNodeExpr* const contributionp
|
||||
= andCond(c.flp, c.vtx[fi]->datap()->stateSigp->cloneTreePure(false),
|
||||
tedger.m_condp);
|
||||
if (!c.vtx[ti]->datap()->stateSigp) {
|
||||
c.vtx[ti]->datap()->stateSigp = contributionp;
|
||||
changed = true;
|
||||
@@ -2364,10 +2479,11 @@ public:
|
||||
|
||||
// Identify registered vertices (targets of clocked edges).
|
||||
for (int i = 0; i < N; ++i) {
|
||||
for (const V3GraphEdge& er : vtx[i]->outEdges()) {
|
||||
const SvaTransEdge& te = static_cast<const SvaTransEdge&>(er);
|
||||
const int toIdx = te.toVtxp()->color();
|
||||
if (te.m_consumesCycle && toIdx != matchIdx && !te.toVtxp()->m_isRejectSink) {
|
||||
for (const V3GraphEdge& edger : vtx[i]->outEdges()) {
|
||||
const SvaTransEdge& tedger = static_cast<const SvaTransEdge&>(edger);
|
||||
const int toIdx = tedger.toVtxp()->color();
|
||||
if (tedger.m_consumesCycle && toIdx != matchIdx
|
||||
&& !tedger.toVtxp()->m_isRejectSink) {
|
||||
vtx[toIdx]->datap()->needsReg = true;
|
||||
}
|
||||
}
|
||||
@@ -3019,9 +3135,20 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||
return false;
|
||||
}
|
||||
|
||||
void processAssertion(AstNodeCoverOrAssert* assertp) {
|
||||
if (assertp->immediate()) return;
|
||||
// Outcome counts for a property if/case are wrong in an outcome-multiplying
|
||||
// context. Returns that context, or nullptr when the shape is supported.
|
||||
static const char* unsupportedPropertyControl(const AstNodeCoverOrAssert* assertp,
|
||||
const AstNodeExpr* seqBodyp, bool negated) {
|
||||
if (!hasPropertyControlConjunction(seqBodyp)) return nullptr;
|
||||
if (negated) return "negation";
|
||||
if (VN_IS(assertp, Cover)) return "cover";
|
||||
if (VN_AS(assertp, Assert)->passsp()) return "a pass action";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Inline property/sequence refs and reject unsupported shapes.
|
||||
// Returns the PropSpec to lower, or nullptr when fully handled here.
|
||||
AstPropSpec* prepareAssertionProp(AstNodeCoverOrAssert* assertp) {
|
||||
if (AstPropSpec* const specp = VN_CAST(assertp->propp(), PropSpec)) {
|
||||
if (AstFuncRef* const funcrefp = VN_CAST(specp->propp(), FuncRef)) {
|
||||
if (const AstProperty* const propyp = VN_CAST(funcrefp->taskp(), Property)) {
|
||||
@@ -3033,20 +3160,36 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||
inlineAllSequenceRefs(assertp->propp());
|
||||
|
||||
if (AstPropSpec* const specp = VN_CAST(assertp->propp(), PropSpec)) {
|
||||
if (hoistClockedSeq(specp)) return;
|
||||
if (hoistClockedSeq(specp)) return nullptr;
|
||||
}
|
||||
|
||||
AstPropSpec* const propp = VN_AS(assertp->propp(), PropSpec);
|
||||
const bool isCover = VN_IS(assertp, Cover);
|
||||
if (!isCover && effectiveAssertPropStrength(propp) == VPropStrength::STRONG) {
|
||||
if (!VN_IS(assertp, Cover)
|
||||
&& effectiveAssertPropStrength(propp) == VPropStrength::STRONG) {
|
||||
propp->v3warn(E_UNSUPPORTED,
|
||||
"Unsupported: strong property in " + assertp->verilogKwd() + ".");
|
||||
replaceBodyOnBuildError(assertp->fileline(), propp, /*errorEmitted=*/true);
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!hasMultiCycleExpr(propp)) return;
|
||||
if (isBareTopLevelUntil(propp)) return;
|
||||
if (!hasMultiCycleExpr(propp)) return nullptr;
|
||||
// A nested property instance keeps its body behind the call; lowering would drop it.
|
||||
if (propp->exists([](const AstFuncRef* refp) { return VN_IS(refp->taskp(), Property); })) {
|
||||
assertp->v3warn(E_UNSUPPORTED,
|
||||
"Unsupported: property instance inside a multi-cycle property "
|
||||
"expression");
|
||||
VL_DO_DANGLING(pushDeletep(assertp->unlinkFrBack()), assertp);
|
||||
return nullptr;
|
||||
}
|
||||
if (isBareTopLevelUntil(propp)) return nullptr;
|
||||
return propp;
|
||||
}
|
||||
|
||||
void processAssertion(AstNodeCoverOrAssert* assertp) {
|
||||
if (assertp->immediate()) return;
|
||||
|
||||
AstPropSpec* const propp = prepareAssertionProp(assertp);
|
||||
if (!propp) return;
|
||||
|
||||
PropertyParts parts = decomposeProperty(propp);
|
||||
UASSERT_OBJ(parts.seqExprp, propp, "Property body must be an expression");
|
||||
@@ -3059,6 +3202,9 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||
seqBodyp = notp->lhsp();
|
||||
}
|
||||
|
||||
const char* const propertyControlp
|
||||
= unsupportedPropertyControl(assertp, seqBodyp, negated);
|
||||
|
||||
// Substitute property-local match-item refs in consequent with
|
||||
// $past(rhs, K) before NFA build (IEEE 1800-2023 16.10).
|
||||
if (liftMatchItemSubstitutions(parts, seqBodyp)) {
|
||||
@@ -3068,8 +3214,6 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||
return;
|
||||
}
|
||||
|
||||
AstSenTree* senTreep = assertp->sentreep();
|
||||
bool senTreeOwned = false; // True if we created senTreep locally
|
||||
AstCover* const coverp = VN_CAST(assertp, Cover);
|
||||
const bool isCoverSeq = coverp && coverp->isCoverSeq();
|
||||
// A sequence event control is not an assertion directive; no default
|
||||
@@ -3082,12 +3226,10 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||
if (!propp->disablep() && m_defaultDisablep && !isSeqEvent) {
|
||||
propp->disablep(m_defaultDisablep->condp()->cloneTreePure(true));
|
||||
}
|
||||
if (!senTreep && propp->sensesp()) {
|
||||
senTreep = new AstSenTree{propp->fileline(), propp->sensesp()->cloneTree(true)};
|
||||
senTreeOwned = true;
|
||||
}
|
||||
if (!propp->sensesp()) return;
|
||||
AstSenTree* senTreep
|
||||
= new AstSenTree{propp->fileline(), propp->sensesp()->cloneTree(true)};
|
||||
AstNodeExpr* disableExprp = propp->disablep();
|
||||
if (!senTreep) return;
|
||||
|
||||
// NFA lowering clones repeated operands and may hoist them into an
|
||||
// always_comb block. Resolve implicit sampled-value clocks first, while
|
||||
@@ -3111,7 +3253,15 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||
// from this attempt become orphan MODULETEMPs; V3Dead removes
|
||||
// them along with the dead always_comb driver.
|
||||
replaceBodyOnBuildError(flp, propp, result.errorEmitted);
|
||||
if (senTreeOwned) VL_DO_DANGLING(pushDeletep(senTreep), senTreep);
|
||||
VL_DO_DANGLING(pushDeletep(senTreep), senTreep);
|
||||
return;
|
||||
}
|
||||
// After the build, so a construct the builder rejects reports itself.
|
||||
if (propertyControlp) {
|
||||
seqBodyp->v3warn(E_UNSUPPORTED,
|
||||
"Unsupported: temporal property if/case with " << propertyControlp);
|
||||
replaceBodyOnBuildError(flp, propp, /*errorEmitted=*/true);
|
||||
VL_DO_DANGLING(pushDeletep(senTreep), senTreep);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3149,7 +3299,7 @@ class AssertNfaVisitor final : public VNVisitor {
|
||||
AstSenTree* const threadFailReplaySenTreep
|
||||
= signals.threadFailCountp ? senTreep->cloneTree(false) : nullptr;
|
||||
|
||||
if (senTreeOwned) VL_DO_DANGLING(pushDeletep(senTreep), senTreep);
|
||||
VL_DO_DANGLING(pushDeletep(senTreep), senTreep);
|
||||
if (disableExprUnlinked) VL_DO_DANGLING(pushDeletep(disableExprp), disableExprp);
|
||||
if (result.finalCondp && !result.finalCondp->backp()) pushDeletep(result.finalCondp);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user