Fix not failing assertion when RHS of a range window rejects once (#7773)
This commit is contained in:
parent
0233e5044c
commit
7061c1f04d
|
|
@ -110,6 +110,9 @@ public:
|
|||
// Reject when source is active and condp is false; set only on
|
||||
// outermost required-step Link
|
||||
bool m_rejectOnFail = false;
|
||||
// Optional dynamic condition vertex for m_rejectOnFail. Used when the
|
||||
// success condition is another NFA state rather than a static expression.
|
||||
SvaStateVertex* m_condVtxp = nullptr;
|
||||
|
||||
// CONSTRUCTORS
|
||||
SvaTransEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, AstNodeExpr* condp,
|
||||
|
|
@ -208,6 +211,12 @@ class SvaNfaBuilder final {
|
|||
// topology when true. Default false preserves cover_property semantics.
|
||||
bool m_isCoverSeq = false;
|
||||
|
||||
struct RangeDelayRejectInfo final {
|
||||
SvaStateVertex* startp = nullptr;
|
||||
int range = 0;
|
||||
int rhsLen = 0;
|
||||
};
|
||||
|
||||
AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) {
|
||||
if (m_throughoutStack.empty()) return baseCondp;
|
||||
// AND all throughout conditions (supports nesting)
|
||||
|
|
@ -374,7 +383,7 @@ class SvaNfaBuilder final {
|
|||
// failure, sets outErrorEmitted per semantic-error policy and returns false.
|
||||
bool applyRangeDelay(AstDelay* delayp, AstNodeExpr* rhsExprp, SvaStateVertex*& currentp,
|
||||
std::vector<SvaStateVertex*>& midSources, FileLine* flp,
|
||||
bool& outErrorEmitted) {
|
||||
bool& outErrorEmitted, RangeDelayRejectInfo* rangeRejectInfop = nullptr) {
|
||||
const int minDelay = getConstInt(delayp->lhsp());
|
||||
if (minDelay < 0) {
|
||||
delayp->v3error("Range delay minimum is not a non-negative elaboration-time constant"
|
||||
|
|
@ -433,8 +442,14 @@ class SvaNfaBuilder final {
|
|||
guardedEdge(currentp, counterVtxp, flp);
|
||||
currentp = counterVtxp;
|
||||
} else if (VN_IS(rhsExprp, SExpr)) {
|
||||
// Nested-SExpr RHS: merge all [M,N] positions; continuation is per-attempt.
|
||||
// Nested-SExpr RHS: merge all [M,N] positions. Candidate-local misses
|
||||
// are not assertion rejects while a later position can still match.
|
||||
if (rangeRejectInfop) {
|
||||
const int rhsLen = fixedLength(rhsExprp);
|
||||
if (rhsLen >= 0) *rangeRejectInfop = {currentp, range, rhsLen};
|
||||
}
|
||||
SvaStateVertex* const mergeVtxp = scopedCreateVertex();
|
||||
mergeVtxp->m_isUnbounded = true;
|
||||
guardedLink(currentp, mergeVtxp, flp);
|
||||
for (int i = 0; i < range; ++i) {
|
||||
SvaStateVertex* const nextVtxp = scopedCreateVertex();
|
||||
|
|
@ -443,6 +458,7 @@ class SvaNfaBuilder final {
|
|||
currentp = nextVtxp;
|
||||
}
|
||||
currentp = mergeVtxp;
|
||||
m_inUnboundedScope = true;
|
||||
} else {
|
||||
// Pure boolean RHS: register chain. Each mid-position links to
|
||||
// match (match-only); last position is the reject source.
|
||||
|
|
@ -468,12 +484,44 @@ class SvaNfaBuilder final {
|
|||
return true;
|
||||
}
|
||||
|
||||
void addFiniteRangeReject(const RangeDelayRejectInfo& info, const BuildResult& result,
|
||||
FileLine* flp) {
|
||||
if (!info.startp) return;
|
||||
|
||||
SvaStateVertex* const expiryVtxp
|
||||
= addDelayChain(info.startp, info.range + info.rhsLen, flp);
|
||||
SvaStateVertex* const expiryMatchp = scopedCreateVertex();
|
||||
std::vector<SvaStateVertex*> sources = result.midSources;
|
||||
sources.push_back(result.termVertexp);
|
||||
for (SvaStateVertex* const srcp : sources) {
|
||||
AstNodeExpr* const condp
|
||||
= result.finalCondp ? sampled(result.finalCondp->cloneTreePure(false)) : nullptr;
|
||||
SvaStateVertex* const successNowp = scopedCreateVertex();
|
||||
guardedLink(srcp, successNowp, condp, flp);
|
||||
SvaStateVertex* stagep = successNowp;
|
||||
guardedLink(stagep, expiryMatchp, flp);
|
||||
for (int i = 0; i < info.range; ++i) {
|
||||
SvaStateVertex* const nextp = scopedCreateVertex();
|
||||
guardedEdge(stagep, nextp, flp);
|
||||
stagep = nextp;
|
||||
guardedLink(stagep, expiryMatchp, flp);
|
||||
}
|
||||
}
|
||||
|
||||
SvaStateVertex* const sinkVtxp = m_graph.createStateVertex();
|
||||
sinkVtxp->m_isRejectSink = true;
|
||||
SvaTransEdge* const rejectp = m_graph.addLink(expiryVtxp, sinkVtxp);
|
||||
rejectp->m_rejectOnFail = true;
|
||||
rejectp->m_condVtxp = expiryMatchp;
|
||||
}
|
||||
|
||||
BuildResult buildSExpr(AstSExpr* sexprp, SvaStateVertex* entryVtxp,
|
||||
bool isTopLevelStep = false) {
|
||||
AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay);
|
||||
if (!delayp || !delayp->isCycleDelay()) return BuildResult::fail();
|
||||
|
||||
FileLine* const flp = sexprp->fileline();
|
||||
AstNodeExpr* const exprp = sexprp->exprp();
|
||||
|
||||
// Handle LHS (preExpr)
|
||||
SvaStateVertex* currentp = entryVtxp;
|
||||
|
|
@ -496,10 +544,12 @@ class SvaNfaBuilder final {
|
|||
|
||||
// Handle delay
|
||||
std::vector<SvaStateVertex*> rangeMidSources;
|
||||
RangeDelayRejectInfo rangeRejectInfo;
|
||||
const bool addRangeReject = isTopLevelStep && !m_inUnboundedScope;
|
||||
if (delayp->isRangeDelay()) {
|
||||
bool errorEmitted = false;
|
||||
if (!applyRangeDelay(delayp, sexprp->exprp(), currentp, rangeMidSources, flp,
|
||||
errorEmitted)) {
|
||||
errorEmitted, addRangeReject ? &rangeRejectInfo : nullptr)) {
|
||||
return BuildResult::fail(errorEmitted);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -514,8 +564,11 @@ class SvaNfaBuilder final {
|
|||
}
|
||||
|
||||
// Multi-cycle RHS: recurse (only plain boolean is returned as finalCondp).
|
||||
AstNodeExpr* const exprp = sexprp->exprp();
|
||||
if (exprp->isMultiCycleSva()) return buildExpr(exprp, currentp, isTopLevelStep);
|
||||
if (exprp->isMultiCycleSva()) {
|
||||
const BuildResult result = buildExpr(exprp, currentp, isTopLevelStep);
|
||||
if (result.valid()) addFiniteRangeReject(rangeRejectInfo, result, flp);
|
||||
return result;
|
||||
}
|
||||
return {currentp, exprp, std::move(rangeMidSources)};
|
||||
}
|
||||
|
||||
|
|
@ -1505,15 +1558,25 @@ class SvaNfaLowering final {
|
|||
computeTerminalMatchAndReject(c, snapshotOkp, sigs, outPerMidSrcsp);
|
||||
|
||||
// Phase 3a: required-step rejection.
|
||||
// Builder only sets m_rejectOnFail on non-clocked Links with m_condp,
|
||||
// and the source always has a resolved stateSig.
|
||||
// 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->fromVtxp(),
|
||||
"rejectOnFail Link must have condp and source stateSig");
|
||||
UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && (tep->m_condp || tep->m_condVtxp),
|
||||
tep->fromVtxp(),
|
||||
"rejectOnFail Link must have condp/condVtxp and source stateSig");
|
||||
AstNodeExpr* const srcSigp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false);
|
||||
AstNodeExpr* const notCondp = new AstLogNot{c.flp, tep->m_condp->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,
|
||||
"rejectOnFail condVtxp missing stateSig");
|
||||
condp = c.vtx[ci]->datap()->stateSigp->cloneTreePure(false);
|
||||
} else {
|
||||
condp = tep->m_condp->cloneTreePure(false);
|
||||
}
|
||||
AstNodeExpr* const notCondp = new AstLogNot{c.flp, condp};
|
||||
AstNodeExpr* const failp = new AstLogAnd{c.flp, srcSigp, notCondp};
|
||||
if (outRequiredStepSrcsp) {
|
||||
outRequiredStepSrcsp->push_back(failp->cloneTreePure(false));
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ module t (
|
|||
int count_fail2 = 0;
|
||||
int count_fail3 = 0;
|
||||
int count_fail4 = 0;
|
||||
int count_fail5 = 0;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
`ifdef TEST_VERBOSE
|
||||
|
|
@ -49,13 +50,11 @@ module t (
|
|||
else if (cyc == 99) begin
|
||||
`checkh(crc, 64'hc77bb9b3784ea091);
|
||||
`checkh(sum, 64'h38c614665c6b71ad);
|
||||
// count_fail1 overcounts Questa by 1: NFA per-cycle reject merging
|
||||
// OR's requiredStep-fail and terminal-fail into one signal; Questa
|
||||
// resolves the same overlap as a single per-attempt miss.
|
||||
`checkd(count_fail1, 25); // Questa: 24
|
||||
`checkd(count_fail2, 50); // Questa: 50
|
||||
`checkd(count_fail3, 24); // Questa: 24
|
||||
`checkd(count_fail4, 1); // Questa: 1
|
||||
`checkd(count_fail1, 24);
|
||||
`checkd(count_fail2, 50);
|
||||
`checkd(count_fail3, 24);
|
||||
`checkd(count_fail4, 1);
|
||||
`checkd(count_fail5, 1);
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
|
|
@ -131,4 +130,25 @@ module t (
|
|||
assert property (@(posedge clk) disable iff (cyc < 2)
|
||||
a |-> ##[+] b ##1 (a | b | c | d | e));
|
||||
|
||||
// Finite range delay with a multi-cycle RHS must not reject on an earlier
|
||||
// candidate when a later candidate in the same window matches.
|
||||
assert property (@(posedge clk)
|
||||
cyc == 10 |-> ##[2:4] ((cyc == 12 || cyc == 13) ##1 cyc == 14));
|
||||
|
||||
// Same shape, but every RHS candidate in the range window rejects, so the
|
||||
// assertion attempt itself must reject once.
|
||||
assert property (@(posedge clk)
|
||||
cyc == 20 |-> ##[2:4] ((cyc == 22 || cyc == 23) ##1 cyc == 25))
|
||||
else count_fail5 <= count_fail5 + 1;
|
||||
|
||||
// Variable-length nested RHS, then another finite range below
|
||||
// the liveness scope.
|
||||
assert property (@(posedge clk)
|
||||
cyc == 30 |-> ##[1:2] ((cyc == 31) ##[1:2] ((cyc == 32) ##1 1'b1)));
|
||||
|
||||
// Finite range whose RHS ends in an NFA state, not a final
|
||||
// boolean condition.
|
||||
assert property (@(posedge clk)
|
||||
cyc == 40 |-> ##[1:2] (##1 (((cyc == 43) ##1 1'b1) or ((cyc == 43) ##1 1'b1))));
|
||||
|
||||
endmodule
|
||||
|
|
|
|||
Loading…
Reference in New Issue