Support s_eventually (#7291) (#7508)

This commit is contained in:
Bartłomiej Chmiel
2026-05-04 09:57:03 -04:00
committed by GitHub
parent bc1acc8aa3
commit 4e349971d3
44 changed files with 717 additions and 261 deletions
+9
View File
@@ -150,6 +150,7 @@ class AssertVisitor final : public VNVisitor {
bool m_inRestrict = false; // True inside restrict assertion
AstNode* m_passsp = nullptr; // Current pass statement
AstNode* m_failsp = nullptr; // Current fail statement
AstFinal* m_finalp = nullptr; // Current final block
// 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;
@@ -302,6 +303,14 @@ class AssertVisitor final : public VNVisitor {
if (AstPExpr* const pexprp = VN_CAST(propp, PExpr)) {
AstFork* const forkp = new AstFork{nodep->fileline(), VJoinType::JOIN_NONE};
forkp->addForksp(pexprp->bodyp()->unlinkFrBack());
if (AstNodeStmt* const finalp = pexprp->finalp()) {
if (!m_finalp) {
m_finalp = new AstFinal{m_modp->fileline(), finalp->unlinkFrBack()};
m_modp->addStmtsp(m_finalp);
} else {
m_finalp->addStmtsp(finalp->unlinkFrBack());
}
}
VL_DO_DANGLING2(pushDeletep(pexprp), pexprp, propp);
bodyp = forkp;
} else {
+129
View File
@@ -67,9 +67,11 @@ private:
V3UniqueNames m_nonConsRepNames{"__VnonConsRep"}; // Nonconsecutive rep name generator
V3UniqueNames m_disableCntNames{"__VdisableCnt"}; // Disable condition counter name generator
V3UniqueNames m_propVarNames{"__Vpropvar"}; // Property-local variable name generator
V3UniqueNames m_activeNames{"__VassertsActive"}; // Active asserts map name generator
bool m_inAssign = false; // True if in an AssignNode
bool m_inAssignDlyLhs = false; // True if in AssignDly's LHS
bool m_inSynchDrive = false; // True if in synchronous drive
bool m_hasCycleDelay = false; // True if node has cycle delay beneath
std::vector<AstVarXRef*> m_xrefsp; // list of xrefs that need name fixup
std::vector<AstSequence*> m_seqsToCleanup; // Sequences to clean up after traversal
@@ -411,6 +413,7 @@ private:
}
}
void visit(AstDelay* nodep) override {
m_hasCycleDelay = true;
// Only cycle delays are relevant in this stage; also only process once
if (!nodep->isCycleDelay()) {
if (m_inSynchDrive) {
@@ -792,6 +795,121 @@ private:
nodep->replaceWith(exprp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
static AstAssocArrayDType* getProcessAssocArrayType(FileLine* const flp) {
// Type of __VassertsActive___x[std::process]
AstNodeDType* valp
= v3Global.rootp()->typeTablep()->findBasicDType(flp, VBasicDTypeKwd::BIT);
AstClassRefDType* keyp
= new AstClassRefDType{flp, v3Global.rootp()->stdPackageClassp(), nullptr};
keyp->classOrPackagep(v3Global.rootp()->stdPackageClassp());
v3Global.rootp()->typeTablep()->addTypesp(keyp);
AstAssocArrayDType* const typep = new AstAssocArrayDType{flp, valp, keyp};
typep->dtypep(typep);
v3Global.rootp()->typeTablep()->addTypesp(typep);
return typep;
}
static AstStmtExpr* getProcessAssocArrayDelete(AstVarRef* const refp) {
// Constructs refp.delete(std::process::self()) statement
FileLine* const flp = refp->fileline();
refp->classOrPackagep(v3Global.rootp()->stdPackageClassp());
AstCMethodHard* const deletep = new AstCMethodHard{
flp, refp, VCMethod::ASSOC_ERASE, v3Global.rootp()->stdPackageProcessSelfp(flp)};
deletep->dtypep(refp->findVoidDType());
return new AstStmtExpr{flp, deletep};
}
static AstNodeExpr* getProcessAssocArraySize(AstVarRef* const refp) {
// Constructs refp.size() statement
refp->classOrPackagep(v3Global.rootp()->stdPackageClassp());
AstCMethodHard* const sizep
= new AstCMethodHard{refp->fileline(), refp, VCMethod::ASSOC_SIZE};
sizep->dtypep(refp->findBasicDType(VBasicDTypeKwd::UINT32));
return sizep;
}
void visit(AstSEventually* nodep) override {
UASSERT(v3Global.rootp()->stdPackagep(), "Should be imported");
AstSenTree* const sentreep = newSenTree(nodep);
if (!sentreep->sensesp()) {
VL_DO_DANGLING(pushDeletep(sentreep), sentreep);
nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
FileLine* const flp = nodep->fileline();
// Track active assertions
AstVar* const activep = new AstVar{flp, VVarType::MODULETEMP, m_activeNames.get(""),
getProcessAssocArrayType(flp)};
activep->lifetime(VLifetime::STATIC_EXPLICIT);
m_modp->addStmtsp(activep);
// Assertion condition check
AstLoop* const loopp = new AstLoop{flp};
AstNodeExpr* const condp = new AstSampled{flp, nodep->exprp()->unlinkFrBack()};
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLogNot{flp, condp}});
loopp->addStmtsp(new AstEventControl{flp, sentreep, nullptr});
// Add assertion to the active set
AstAssocSel* const selp = new AstAssocSel{flp, new AstVarRef{flp, activep, VAccess::WRITE},
v3Global.rootp()->stdPackageProcessSelfp(flp)};
AstAssign* const incrementp = new AstAssign{flp, selp, new AstConst{flp, 1}};
AstPExprClause* const clausep = new AstPExprClause{flp};
AstStmtExpr* const deletep
= getProcessAssocArrayDelete(new AstVarRef{flp, activep, VAccess::WRITE});
// Main assertion block
AstBegin* const bodyp = new AstBegin{flp, "", nullptr, true};
bodyp->addStmtsp(incrementp);
bodyp->addStmtsp(loopp);
bodyp->addStmtsp(clausep);
bodyp->addStmtsp(deletep);
// Validate assertion condition for each active assert
AstVar* const activeCountp = new AstVar{flp, VVarType::BLOCKTEMP, "__VassertCount",
nodep->findBasicDType(VBasicDTypeKwd::UINT32)};
activeCountp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
AstAssign* const initActiveCountp
= new AstAssign{flp, new AstVarRef{flp, activeCountp, VAccess::WRITE},
getProcessAssocArraySize(new AstVarRef{flp, activep, VAccess::READ})};
AstLoop* const finalLoopp = new AstLoop{flp};
AstIf* const finalBodypCondp
= new AstIf{flp, condp->cloneTreePure(false), new AstPExprClause{flp},
new AstPExprClause{flp, false}};
finalLoopp->addStmtsp(
new AstLoopTest{flp, finalLoopp,
new AstNeq{flp, new AstVarRef{flp, activeCountp, VAccess::READ},
new AstConst{flp, 0}}});
finalLoopp->addStmtsp(finalBodypCondp);
finalLoopp->addStmtsp(
new AstAssign{flp, new AstVarRef{flp, activeCountp, VAccess::WRITE},
new AstSub{flp, new AstVarRef{flp, activeCountp, VAccess::READ},
new AstConst{flp, 1}}});
// Final assertion block
AstBegin* const finalp = new AstBegin{flp, "", nullptr, true};
finalp->addStmtsp(activeCountp);
finalp->addStmtsp(initActiveCountp);
finalp->addStmtsp(finalLoopp);
m_pexprp = new AstPExpr{flp, bodyp, finalp, nodep->dtypep()};
VL_RESTORER(m_hasCycleDelay);
m_hasCycleDelay = false;
iterate(bodyp);
iterate(finalp);
if (m_hasCycleDelay) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: cycle delay in s_eventually");
nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
VL_DO_DANGLING(pushDeletep(nodep), nodep);
VL_DO_DANGLING(m_pexprp->deleteTree(), m_pexprp);
return;
}
nodep->replaceWith(m_pexprp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstStable* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
@@ -1272,6 +1390,17 @@ private:
m_pexprp = nodep;
if (m_disablep) {
const AstSampled* sampledp = nullptr;
if (m_disablep->exists([&sampledp](const AstSampled* const sp) {
sampledp = sp;
return true;
})) {
sampledp->v3warn(E_UNSUPPORTED,
"Unsupported: $sampled inside disabled condition of a sequence");
m_disablep = new AstConst{m_disablep->fileline(), AstConst::BitFalse{}};
// always a copy is used, so remove it now
pushDeletep(m_disablep);
}
FileLine* const flp = nodep->fileline();
// Add counter which counts times the condition turned true
AstVar* const disableCntp
+21
View File
@@ -1898,10 +1898,18 @@ public:
class AstPExpr final : public AstNodeExpr {
// Property expression
// @astgen op1 := bodyp : AstBegin
// @astgen op2 := finalp : Optional[AstNodeStmt]
public:
explicit AstPExpr(FileLine* fl, AstBegin* bodyp, AstNodeDType* dtypep)
: ASTGEN_SUPER_PExpr(fl) {
this->bodyp(bodyp);
this->finalp(nullptr);
this->dtypep(dtypep);
}
explicit AstPExpr(FileLine* fl, AstBegin* bodyp, AstNodeStmt* finalp, AstNodeDType* dtypep)
: ASTGEN_SUPER_PExpr(fl) {
this->bodyp(bodyp);
this->finalp(finalp);
this->dtypep(dtypep);
}
ASTGEN_MEMBERS_AstPExpr;
@@ -2226,6 +2234,19 @@ public:
bool unbounded() const { return m_unbounded; }
bool isMultiCycleSva() const override { return true; }
};
class AstSEventually final : public AstNodeExpr {
// s_eventually
// @astgen op1 := exprp : AstNodeExpr
public:
explicit AstSEventually(FileLine* fl, AstNodeExpr* exprp)
: ASTGEN_SUPER_SEventually(fl) {
this->exprp(exprp);
}
ASTGEN_MEMBERS_AstSEventually;
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
};
class AstSExpr final : public AstNodeExpr {
// Sequence expression
// @astgen op1 := preExprp: Optional[AstNodeExpr]
+4
View File
@@ -1263,6 +1263,7 @@ class AstNetlist final : public AstNode {
// @astgen ptr := m_constPoolp : AstConstPool // Reference to constant pool, for faster lookup
// @astgen ptr := m_dollarUnitPkgp : Optional[AstPackage] // $unit
// @astgen ptr := m_stdPackagep : Optional[AstPackage] // SystemVerilog std package
// @astgen ptr := m_stdPackageClassp : Optional[AstClass] // SystemVerilog std process class
// @astgen ptr := m_evalp : Optional[AstCFunc] // The '_eval' function
// @astgen ptr := m_evalNbap : Optional[AstCFunc] // The '_eval__nba' function
// @astgen ptr := m_dpiExportTriggerp : Optional[AstVarScope] // DPI export trigger variable
@@ -1304,6 +1305,9 @@ public:
void nbaEventTriggerp(AstVarScope* const varScopep) { m_nbaEventTriggerp = varScopep; }
void stdPackagep(AstPackage* const packagep) { m_stdPackagep = packagep; }
AstPackage* stdPackagep() const { return m_stdPackagep; }
void stdPackageClassp(AstClass* const classp) { m_stdPackageClassp = classp; }
AstClass* stdPackageClassp() const { return m_stdPackageClassp; }
AstFuncRef* stdPackageProcessSelfp(FileLine*) const;
AstTopScope* topScopep() const { return m_topScopep; }
void createTopScope(AstScope* scopep);
VTimescale timeunit() const { return m_timeunit; }
+12
View File
@@ -2703,6 +2703,18 @@ AstVarScope* AstNetlist::stlFirstIterationp() {
AstVarScope* const vscp = m_stlFirstIterationp;
return vscp;
}
AstFuncRef* AstNetlist::stdPackageProcessSelfp(FileLine* flp) const {
UASSERT(v3Global.rootp()->stdPackageClassp(), "'std' should be imported");
AstFunc* selfp = nullptr;
for (AstNode* itemp = v3Global.rootp()->stdPackageClassp()->stmtsp(); itemp;
itemp = itemp->nextp()) {
if (itemp->name() == "self") selfp = VN_AS(itemp, Func);
}
UASSERT(selfp, "'std::process::self' should be found");
AstFuncRef* const processSelfp = new AstFuncRef{flp, selfp};
processSelfp->classOrPackagep(v3Global.rootp()->stdPackageClassp());
return processSelfp;
}
void AstNodeModule::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " L" << level();
+1
View File
@@ -157,6 +157,7 @@ void V3Global::removeStd() {
UINFO(3, "Removing unused std:: package");
if (AstNodeModule* stdp = v3Global.rootp()->stdPackagep()) {
v3Global.rootp()->stdPackagep(nullptr);
v3Global.rootp()->stdPackageClassp(nullptr);
VL_DO_DANGLING(stdp->unlinkFrBack()->deleteTree(), stdp);
}
}
+18 -23
View File
@@ -161,12 +161,6 @@ class LinkJumpVisitor final : public VNVisitor {
if (AstNode* const refp = nodep->op4p()) addPrefixToBlocksRecurse(prefix, refp);
if (AstNode* const refp = nodep->nextp()) addPrefixToBlocksRecurse(prefix, refp);
}
static AstNode* getMemberp(const AstNodeModule* const nodep, const std::string& name) {
for (AstNode* itemp = nodep->stmtsp(); itemp; itemp = itemp->nextp()) {
if (itemp->name() == name) return itemp;
}
return nullptr;
}
bool existsBlockAbove(const std::string& name) const {
for (const AstNodeBlock* const stackp : vlstd::reverse_view(m_blockStack)) {
if (stackp->name() == name) return true;
@@ -175,14 +169,11 @@ class LinkJumpVisitor final : public VNVisitor {
}
static AstStmtExpr* getQueuePushProcessSelfp(AstVarRef* const queueRefp) {
// Constructs queue.push_back(std::process::self()) statement
FileLine* const fl = queueRefp->fileline();
AstClass* const processClassp
= VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class);
AstFunc* const selfMethodp = VN_AS(getMemberp(processClassp, "self"), Func);
AstFuncRef* const processSelfp = new AstFuncRef{fl, selfMethodp};
processSelfp->classOrPackagep(processClassp);
FileLine* const flp = queueRefp->fileline();
return new AstStmtExpr{
fl, new AstMethodCall{fl, queueRefp, "push_back", new AstArg{fl, "", processSelfp}}};
flp,
new AstMethodCall{flp, queueRefp, "push_back",
new AstArg{flp, "", v3Global.rootp()->stdPackageProcessSelfp(flp)}}};
}
static AstStmtExpr* getQueuePushProcessSelfp(FileLine* const fl, AstVar* const processQueuep) {
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
@@ -192,13 +183,18 @@ class LinkJumpVisitor final : public VNVisitor {
}
static AstStmtExpr* getQueueKillStmtp(FileLine* const fl, AstVar* const processQueuep) {
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
AstClass* const processClassp
= VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class);
AstVarRef* const queueRefp = new AstVarRef{fl, topPkgp, processQueuep, VAccess::READWRITE};
AstTaskRef* const killQueueCall
= new AstTaskRef{fl, VN_AS(getMemberp(processClassp, "killQueue"), Task),
new AstArg{fl, "", queueRefp}};
killQueueCall->classOrPackagep(processClassp);
AstTaskRef* killQueueCall = nullptr;
for (AstNode* itemp = v3Global.rootp()->stdPackageClassp()->stmtsp(); itemp;
itemp = itemp->nextp()) {
if (itemp->name() == "killQueue") {
killQueueCall
= new AstTaskRef{fl, VN_AS(itemp, Task), new AstArg{fl, "", queueRefp}};
break;
}
}
UASSERT(killQueueCall, "Should be found");
killQueueCall->classOrPackagep(v3Global.rootp()->stdPackageClassp());
return new AstStmtExpr{fl, killQueueCall};
}
static void prependStmtsp(AstNodeFTask* const nodep, AstNode* const stmtp) {
@@ -238,12 +234,11 @@ class LinkJumpVisitor final : public VNVisitor {
}
AstVar* getProcessQueuep(AstNode* const nodep, FileLine* const fl) {
AstPackage* const topPkgp = v3Global.rootp()->dollarUnitPkgAddp();
AstClass* const processClassp
= VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class);
AstVar* const processQueuep = new AstVar{
fl, VVarType::VAR, m_queueNames.get(nodep->name()), VFlagChildDType{},
new AstQueueDType{fl, VFlagChildDType{},
new AstClassRefDType{fl, processClassp, nullptr}, nullptr}};
new AstQueueDType{
fl, VFlagChildDType{},
new AstClassRefDType{fl, v3Global.rootp()->stdPackageClassp(), nullptr}, nullptr}};
processQueuep->lifetime(VLifetime::STATIC_EXPLICIT);
processQueuep->processQueue(true);
topPkgp->addStmtsp(processQueuep);
+9
View File
@@ -80,6 +80,15 @@ void V3ParseImp::importIfInStd(FileLine* fileline, const string& id, bool doImpo
if (doImport) {
AstPackageImport* const impp = new AstPackageImport{stdpkgp->fileline(), stdpkgp, "*"};
unitPackage(stdpkgp->fileline())->addStmtsp(impp);
for (AstNode* itemp = v3Global.rootp()->stdPackagep()->stmtsp(); itemp;
itemp = itemp->nextp()) {
if (itemp->name() == "process") {
v3Global.rootp()->stdPackageClassp(VN_AS(itemp, Class));
UASSERT_OBJ(v3Global.rootp()->stdPackageClassp(), v3Global.rootp(),
"'std' package class should be found");
break;
}
}
}
}
}
+12
View File
@@ -1728,6 +1728,18 @@ class WidthVisitor final : public VNVisitor {
if (nodep->seedp()) iterateCheckSigned32(nodep, "seed", nodep->seedp(), BOTH);
}
}
void visit(AstSEventually* nodep) override {
if (v3Global.opt.timing().isSetFalse() || !v3Global.opt.timing().isSetTrue()) {
nodep->v3warn(E_NOTIMING, "s_eventually requires --timing");
nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::WidthedValue{}, 1, 0});
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
}
if (m_vup->prelim()) {
iterateCheckBool(nodep, "exprp", nodep->exprp(), BOTH);
nodep->dtypeSetBit();
}
}
void visit(AstSGotoRep* nodep) override {
assertAtExpr(nodep);
if (m_vup->prelim()) {
+4 -1
View File
@@ -6778,7 +6778,10 @@ pexpr<nodeExprp>: // IEEE: property_expr (The name pexpr is important as regex
| yS_ALWAYS pexpr
{ $$ = new AstPropAlways{$1, $2, new AstUnbounded{$1}, new AstUnbounded{$1}, true}; }
| yS_EVENTUALLY pexpr
{ $$ = $2; BBUNSUP($1, "Unsupported: s_eventually (in property expression)"); }
{
$$ = new AstSEventually{$1, $2};
PARSEP->importIfInStd($1, "process", true);
}
| yS_EVENTUALLY anyrange pexpr %prec yS_EVENTUALLY
{ $$ = $3; BBUNSUP($1, "Unsupported: s_eventually[] (in property expression)"); DEL($2); }
| yEVENTUALLY anyrange pexpr %prec yS_EVENTUALLY