Fix $past in final blocks on a sampling-tick finish (#8153)

This commit is contained in:
Yilou Wang
2026-08-24 11:09:02 -04:00
committed by GitHub
parent 33ad9d530e
commit 3c027baa44
6 changed files with 99 additions and 17 deletions
+51 -5
View File
@@ -292,6 +292,8 @@ class AssertVisitor final : public VNVisitor {
// Map from (expression, senTree) to AstAlways that computes delayed values of the expression
std::unordered_map<VNRef<AstNodeExpr>, std::unordered_map<VNRef<AstSenTree>, AstAlways*>>
m_modExpr2Sen2DelayedAlwaysp;
// Map from delayed-value AstAlways to its last-sampling-tick time variable
std::unordered_map<const AstAlways*, AstVar*> m_delayedAlways2TickTimep;
// METHODS
static string assertCtlGetCall(const char* query, VAssertType type,
@@ -537,23 +539,43 @@ class AssertVisitor final : public VNVisitor {
AstNodeExpr* getPastValue(AstNodeExpr* exprp, AstSenTree* senTreep, uint32_t ticks) {
UASSERT_OBJ(ticks > 0, exprp, "Delay must be > 0");
AstAlways* const alwaysp = getDelayedAlways(exprp, senTreep);
FileLine* const flp = exprp->fileline();
return pastValueRef(getDelayedAlways(exprp, senTreep), flp, ticks);
}
AstNodeExpr* pastValueRef(AstAlways* alwaysp, FileLine* flp, uint32_t ticks) {
std::vector<AstVar*>& delayedr = m_delayed(alwaysp);
// Ensure the required delay exists
while (delayedr.size() < ticks) {
AstVar* const firstp = delayedr.front();
FileLine* const flp = firstp->fileline();
FileLine* const varFlp = firstp->fileline();
// Create once more delayed value
std::string name = firstp->name();
name.resize(name.size() - 1);
name += std::to_string(delayedr.size() + 1);
AstNodeExpr* const prevp = new AstVarRef{flp, delayedr.back(), VAccess::READ};
AstNodeExpr* const prevp = new AstVarRef{varFlp, delayedr.back(), VAccess::READ};
AstVar* const varp = createDelayedVar(name, alwaysp, prevp);
// Add it to delayed variable vector
delayedr.emplace_back(varp);
}
// Return a reference to the appropriately delayed variable
return new AstVarRef{exprp->fileline(), delayedr.at(ticks - 1), VAccess::READ};
return new AstVarRef{flp, delayedr.at(ticks - 1), VAccess::READ};
}
// Time of the pipeline's last sampling tick, for end-of-simulation readers
AstVar* getPastTickTimeVar(AstAlways* alwaysp) {
AstVar*& varpr = m_delayedAlways2TickTimep[alwaysp];
if (!varpr) {
FileLine* const flp = alwaysp->fileline();
varpr = new AstVar{flp, VVarType::MODULETEMP,
"_Vpast_" + cvtToStr(m_modPastNum++) + "_t",
m_modp->findUInt64DType()};
m_modp->addStmtsp(varpr);
AstNodeExpr* const timep = new AstCExpr{flp, "vlSymsp->_vm_contextp__->time()", 64};
alwaysp->addStmtsp(
new AstAssign{flp, new AstVarRef{flp, varpr, VAccess::WRITE}, timep});
}
return varpr;
}
void visitAssertionIterate(AstNodeCoverOrAssert* nodep, AstNode* failsp) {
@@ -975,7 +997,25 @@ class AssertVisitor final : public VNVisitor {
}
UASSERT_OBJ(ticks >= 1, nodep, "0 tick should have been checked in V3Width");
AstNodeExpr* const exprp = newSampledExpr(nodep->exprp()->unlinkFrBack());
AstNodeExpr* inp = getPastValue(exprp, nodep->sentreep()->unlinkFrBack(), ticks);
AstSenTree* const senTreep = nodep->sentreep()->unlinkFrBack();
AstNodeExpr* inp = nullptr;
// Deliberately emitted for every ftask $past; calls outside final take the normal stage
if (VN_IS(m_procedurep, Final) || m_ftaskp) {
// A sampling-tick finish reads one stage deeper (IEEE 1800-2023 16.9.3).
FileLine* const flp = nodep->fileline();
AstAlways* const alwaysp = getDelayedAlways(exprp, senTreep);
AstNodeExpr* const betweenTicksp = pastValueRef(alwaysp, flp, ticks);
AstNodeExpr* const onTickp = pastValueRef(alwaysp, flp, ticks + 1);
AstNodeExpr* const onTickCondp = new AstLogAnd{
flp, new AstCExpr{flp, "vlSymsp->_vm_contextp__->executingFinal()", 1},
new AstEq{flp, new AstVarRef{flp, getPastTickTimeVar(alwaysp), VAccess::READ},
new AstCExpr{flp, "vlSymsp->_vm_contextp__->finishPendingTime()", 64}}};
AstCond* const condp = new AstCond{flp, onTickCondp, onTickp, betweenTicksp};
condp->dtypeFrom(onTickp);
inp = condp;
} else {
inp = getPastValue(exprp, senTreep, ticks);
}
nodep->replaceWith(inp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
@@ -1213,6 +1253,7 @@ class AssertVisitor final : public VNVisitor {
VL_RESTORER(m_modStrobeNum);
VL_RESTORER(m_finalp);
VL_RESTORER_CLEAR(m_modExpr2Sen2DelayedAlwaysp);
VL_RESTORER_CLEAR(m_delayedAlways2TickTimep);
m_modp = nodep;
m_modPastNum = 0;
m_modStrobeNum = 0;
@@ -1224,6 +1265,11 @@ class AssertVisitor final : public VNVisitor {
m_procedurep = nodep;
iterateChildren(nodep);
}
void visit(AstNodeFTask* nodep) override {
VL_RESTORER(m_ftaskp);
m_ftaskp = nodep;
iterateChildren(nodep);
}
void visit(AstGenBlock* nodep) override {
// This code is needed rather than a visitor in V3Begin,
// because V3Assert is called before V3Begin