Patch coverage for Verilator 5d1b4fe8a..0b11de521
Current view: top level - src/V3AssertPre.cpp (source / functions) Coverage Total Missed
Test: verilator-patch.info Lines: 98.86 % 88 -1
Test Date: 2026-04-28 11:55:42 Functions: - 0 0
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 87.50 % 8 -1

             Branch data     Line data    Source code
       1                 :             : // -*- mode: C++; c-file-style: "cc-mode" -*-
       2                 :             : //*************************************************************************
       3                 :             : // DESCRIPTION: Verilator: Collect and print statistics
       4                 :             : //
       5                 :             : // Code available from: https://verilator.org
       6                 :             : //
       7                 :             : //*************************************************************************
       8                 :             : //
       9                 :             : // This program is free software; you can redistribute it and/or modify it
      10                 :             : // under the terms of either the GNU Lesser General Public License Version 3
      11                 :             : // or the Perl Artistic License Version 2.0.
      12                 :             : // SPDX-FileCopyrightText: 2005-2026 Wilson Snyder
      13                 :             : // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
      14                 :             : //
      15                 :             : //*************************************************************************
      16                 :             : //  Pre steps:
      17                 :             : //      Attach clocks to each assertion
      18                 :             : //      Substitute property references by property body (IEEE 1800-2023 16.12.1).
      19                 :             : //      Transform clocking blocks into imperative logic
      20                 :             : //*************************************************************************
      21                 :             : 
      22                 :             : #include "V3PchAstNoMT.h"  // VL_MT_DISABLED_CODE_UNIT
      23                 :             : 
      24                 :             : #include "V3AssertPre.h"
      25                 :             : 
      26                 :             : #include "V3Const.h"
      27                 :             : #include "V3Task.h"
      28                 :             : #include "V3UniqueNames.h"
      29                 :             : 
      30                 :             : #include <unordered_map>
      31                 :             : 
      32                 :             : VL_DEFINE_DEBUG_FUNCTIONS;
      33                 :             : 
      34                 :             : //######################################################################
      35                 :             : // Assert class functions
      36                 :             : 
      37                 :             : class AssertPreVisitor final : public VNVisitor {
      38                 :             :     // Removes clocks and other pre-optimizations
      39                 :             :     // Eventually inlines calls to sequences, properties, etc.
      40                 :             :     // We're not parsing the tree, or anything more complicated.
      41                 :             : private:
      42                 :             :     // NODE STATE
      43                 :             :     // AstClockingItem::user1p()         // AstVar*.      varp() of ClockingItem after unlink
      44                 :             :     // AstPExpr::user1()                 // bool.         Created from AstUntil
      45                 :             :     const VNUser1InUse m_inuser1;
      46                 :             :     // STATE
      47                 :             :     // Current context:
      48                 :             :     AstNetlist* const m_netlistp = nullptr;  // Current netlist
      49                 :             :     AstNodeModule* m_modp = nullptr;  // Current module
      50                 :             :     AstClocking* m_clockingp = nullptr;  // Current clocking block
      51                 :             :     // Reset each module:
      52                 :             :     AstClocking* m_defaultClockingp = nullptr;  // Default clocking for current module
      53                 :             :     AstVar* m_defaultClkEvtVarp = nullptr;  // Event var for default clocking (for ##0)
      54                 :             :     AstDefaultDisable* m_defaultDisablep = nullptr;  // Default disable for current module
      55                 :             :     // Reset each assertion:
      56                 :             :     AstSenItem* m_senip = nullptr;  // Last sensitivity
      57                 :             :     // Reset each always:
      58                 :             :     AstSenItem* m_seniAlwaysp = nullptr;  // Last sensitivity in always
      59                 :             :     // Reset each assertion:
      60                 :             :     AstNodeExpr* m_disablep = nullptr;  // Last disable
      61                 :             :     AstIf* m_disableSeqIfp = nullptr;  // Used for handling disable iff in sequences
      62                 :             :     AstPExpr* m_pexprp = nullptr;  // Last AstPExpr
      63                 :             :     // Other:
      64                 :             :     V3UniqueNames m_cycleDlyNames{"__VcycleDly"};  // Cycle delay counter name generator
      65                 :             :     V3UniqueNames m_consRepNames{"__VconsRep"};  // Consecutive repetition counter name generator
      66                 :             :     V3UniqueNames m_gotoRepNames{"__VgotoRep"};  // Goto repetition counter name generator
      67                 :             :     V3UniqueNames m_nonConsRepNames{"__VnonConsRep"};  // Nonconsecutive rep name generator
      68                 :             :     V3UniqueNames m_disableCntNames{"__VdisableCnt"};  // Disable condition counter name generator
      69                 :             :     V3UniqueNames m_propVarNames{"__Vpropvar"};  // Property-local variable name generator
      70                 :             :     V3UniqueNames m_activeNames{"__VassertsActive"};  // Active asserts map name generator
      71                 :             :     bool m_inAssign = false;  // True if in an AssignNode
      72                 :             :     bool m_inAssignDlyLhs = false;  // True if in AssignDly's LHS
      73                 :             :     bool m_inSynchDrive = false;  // True if in synchronous drive
      74                 :             :     std::vector<AstVarXRef*> m_xrefsp;  // list of xrefs that need name fixup
      75                 :             :     std::vector<AstSequence*> m_seqsToCleanup;  // Sequences to clean up after traversal
      76                 :             : 
      77                 :             :     // METHODS
      78                 :             : 
      79                 :             :     AstSenTree* newSenTree(AstNode* nodep, AstSenTree* useTreep = nullptr,
      80                 :             :                            AstNodeCoverOrAssert* cassertp = nullptr) {
      81                 :             :         // Create sentree based on clocked or default clock
      82                 :             :         // Return nullptr for always
      83                 :             :         if (useTreep) return useTreep;
      84                 :             :         AstSenTree* newp = nullptr;
      85                 :             :         AstSenItem* senip = m_senip;
      86                 :             :         bool fromAlways = false;
      87                 :             :         if (!senip && m_defaultClockingp) senip = m_defaultClockingp->sensesp();
      88                 :             :         if (!senip) {
      89                 :             :             senip = m_seniAlwaysp;
      90                 :             :             fromAlways = true;
      91                 :             :         }
      92                 :             :         if (!senip) {
      93                 :             :             nodep->v3warn(E_UNSUPPORTED, "Unsupported: Unclocked assertion");
      94                 :             :             newp = new AstSenTree{nodep->fileline(), nullptr};
      95                 :             :         } else {
      96                 :             :             if (cassertp && fromAlways) cassertp->senFromAlways(true);
      97                 :             :             newp = new AstSenTree{nodep->fileline(), senip->cloneTree(true)};
      98                 :             :         }
      99                 :             :         return newp;
     100                 :             :     }
     101                 :             :     AstNodeExpr* getSequenceBodyExprp(const AstSequence* const seqp) const {
     102                 :             :         // The statements in AstSequence are optional AstVar (ports) followed by body expr.
     103                 :             :         AstNode* bodyp = seqp->stmtsp();
     104                 :             :         while (bodyp && VN_IS(bodyp, Var)) bodyp = bodyp->nextp();
     105                 :             :         return VN_CAST(bodyp, NodeExpr);
     106                 :             :     }
     107                 :             :     AstPropSpec* getPropertyExprp(const AstProperty* const propp) {
     108                 :             :         // Statements in AstProperty: AstVar (ports/local vars),
     109                 :             :         // AstInitialStaticStmt/AstInitialAutomaticStmt (var init), AstPropSpec (body).
     110                 :             :         AstNode* propExprp = propp->stmtsp();
     111                 :             :         while (propExprp
     112                 :             :                && (VN_IS(propExprp, Var) || VN_IS(propExprp, InitialStaticStmt)
     113                 :             :                    || VN_IS(propExprp, InitialAutomaticStmt))) {
     114                 :             :             propExprp = propExprp->nextp();
     115                 :             :         }
     116                 :             :         return VN_CAST(propExprp, PropSpec);
     117                 :             :     }
     118                 :             :     void substituteSequenceCall(AstFuncRef* funcrefp, AstSequence* seqp) {
     119                 :             :         // IEEE 1800-2023 16.7 (sequence declarations), 16.8 (sequence instances)
     120                 :             :         // Inline the sequence body at the call site, replacing the FuncRef
     121                 :             :         AstNodeExpr* bodyExprp = getSequenceBodyExprp(seqp);
     122                 :             :         UASSERT_OBJ(bodyExprp, funcrefp, "Sequence has no body expression");
     123                 :             :         // Clone the body expression since the sequence may be referenced multiple times
     124                 :             :         AstNodeExpr* clonedp = bodyExprp->cloneTree(false);
     125                 :             :         // Build substitution map, then do a single traversal to replace all formals
     126                 :             :         // (textual substitution per IEEE 16.8.2).
     127                 :             :         const V3TaskConnects tconnects = V3Task::taskConnects(funcrefp, seqp->stmtsp());
     128                 :             :         std::unordered_map<const AstVar*, AstNodeExpr*> portMap;
     129                 :             :         for (const auto& tconnect : tconnects) {
     130                 :             :             portMap[tconnect.first] = tconnect.second->exprp();
     131                 :             :         }
     132                 :             :         clonedp->foreach([&](AstVarRef* refp) {
     133                 :             :             const auto it = portMap.find(refp->varp());
     134                 :             :             if (it != portMap.end()) {
     135                 :             :                 refp->replaceWith(it->second->cloneTree(false));
     136                 :             :                 VL_DO_DANGLING(pushDeletep(refp), refp);
     137                 :             :             }
     138                 :             :         });
     139                 :             :         for (const auto& tconnect : tconnects) {
     140                 :             :             pushDeletep(tconnect.second->exprp()->unlinkFrBack());
     141                 :             :         }
     142                 :             :         // Replace the FuncRef with the inlined body
     143                 :             :         funcrefp->replaceWith(clonedp);
     144                 :             :         VL_DO_DANGLING(pushDeletep(funcrefp), funcrefp);
     145                 :             :         // Clear referenced flag; sequences with isReferenced==false are deleted in assertPreAll
     146                 :             :         seqp->isReferenced(false);
     147                 :             :     }
     148                 :             :     AstPropSpec* substitutePropertyCall(AstPropSpec* nodep) {
     149                 :             :         if (AstFuncRef* const funcrefp = VN_CAST(nodep->propp(), FuncRef)) {
     150                 :             :             if (const AstProperty* const propp = VN_CAST(funcrefp->taskp(), Property)) {
     151                 :             :                 AstPropSpec* propExprp = getPropertyExprp(propp);
     152                 :             :                 // Substitute inner property call before copying in order to not doing the same for
     153                 :             :                 // each call of outer property call.
     154                 :             :                 propExprp = substitutePropertyCall(propExprp);
     155                 :             :                 // Clone subtree after substitution. It is needed, because property might be called
     156                 :             :                 // multiple times with different arguments.
     157                 :             :                 propExprp = propExprp->cloneTree(false);
     158                 :             :                 // Build substitution maps for formal arguments and property-local
     159                 :             :                 // variables, then perform a single foreach to apply all replacements.
     160                 :             :                 // Map port vars to their actual argument expressions
     161                 :             :                 const V3TaskConnects tconnects = V3Task::taskConnects(funcrefp, propp->stmtsp());
     162                 :             :                 std::unordered_map<const AstVar*, AstNodeExpr*> portMap;
     163                 :             :                 for (const auto& tconnect : tconnects) {
     164                 :             :                     portMap[tconnect.first] = tconnect.second->exprp();
     165                 :             :                 }
     166                 :             : 
     167                 :             :                 // Promote property-local variables (non-port vars, IEEE 16.10) to
     168                 :             :                 // module-level __Vpropvar temps. Cross-cycle persistence is handled
     169                 :             :                 // by the match item lowering in visit(AstImplication*).
     170                 :             :                 std::unordered_map<const AstVar*, AstVar*> localVarMap;
     171                 :             :                 for (AstNode* stmtp = propp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
     172                 :             :                     if (AstVar* const varp = VN_CAST(stmtp, Var)) {
     173                 :             :                         if (!varp->isIO()) {
     174                 :             :                             const string newName = m_propVarNames.get(varp);
     175                 :             :                             AstVar* const newVarp = new AstVar{
     176                 :             :                                 varp->fileline(), VVarType::MODULETEMP, newName, varp->dtypep()};
     177                 :             :                             newVarp->lifetime(VLifetime::STATIC_EXPLICIT);
     178                 :             :                             m_modp->addStmtsp(newVarp);
     179                 :             :                             localVarMap[varp] = newVarp;
     180                 :             :                         }
     181                 :             :                     }
     182                 :             :                 }
     183                 :             : 
     184                 :             :                 // Single traversal: substitute ports and update local var references
     185                 :             :                 propExprp->foreach([&](AstVarRef* refp) {
     186                 :             :                     {
     187                 :             :                         const auto portIt = portMap.find(refp->varp());
     188                 :             :                         if (portIt != portMap.end()) {
     189                 :             :                             refp->replaceWith(portIt->second->cloneTree(false));
     190                 :             :                             VL_DO_DANGLING(pushDeletep(refp), refp);
     191                 :             :                             return;
     192                 :             :                         }
     193                 :             :                     }
     194                 :             :                     {
     195                 :             :                         const auto localIt = localVarMap.find(refp->varp());
     196                 :             :                         if (localIt != localVarMap.end()) { refp->varp(localIt->second); }
     197                 :             :                     }
     198                 :             :                 });
     199                 :             : 
     200                 :             :                 // Clean up argument expressions
     201                 :             :                 for (const auto& tconnect : tconnects) {
     202                 :             :                     pushDeletep(tconnect.second->exprp()->unlinkFrBack());
     203                 :             :                 }
     204                 :             : 
     205                 :             :                 // Handle case with 2 disable iff statement (IEEE 1800-2023 16.12.1)
     206                 :             :                 if (nodep->disablep() && propExprp->disablep()) {
     207                 :             :                     nodep->v3error("disable iff expression before property call and in its "
     208                 :             :                                    "body is not legal");
     209                 :             :                     pushDeletep(propExprp->disablep()->unlinkFrBack());
     210                 :             :                 }
     211                 :             :                 // If disable iff is in outer property, move it to inner
     212                 :             :                 if (nodep->disablep()) {
     213                 :             :                     AstNodeExpr* const disablep = nodep->disablep()->unlinkFrBack();
     214                 :             :                     propExprp->disablep(disablep);
     215                 :             :                 }
     216                 :             : 
     217                 :             :                 if (nodep->sensesp() && propExprp->sensesp()) {
     218                 :             :                     nodep->v3warn(E_UNSUPPORTED,
     219                 :             :                                   "Unsupported: Clock event before property call and in its body");
     220                 :             :                     pushDeletep(propExprp->sensesp()->unlinkFrBack());
     221                 :             :                 }
     222                 :             :                 // If clock event is in outer property, move it to inner
     223                 :             :                 if (nodep->sensesp()) {
     224                 :             :                     AstSenItem* const sensesp = nodep->sensesp();
     225                 :             :                     sensesp->unlinkFrBack();
     226                 :             :                     propExprp->sensesp(sensesp);
     227                 :             :                 }
     228                 :             : 
     229                 :             :                 // Now substitute property reference with property body
     230                 :             :                 nodep->replaceWith(propExprp);
     231                 :             :                 VL_DO_DANGLING(pushDeletep(nodep), nodep);
     232                 :             :                 return propExprp;
     233                 :             :             }
     234                 :             :         }
     235                 :             :         return nodep;
     236                 :             :     }
     237                 :             : 
     238                 :             :     // VISITORS
     239                 :             :     //========== Statements
     240                 :             :     void visit(AstClocking* const nodep) override {
     241                 :             :         VL_RESTORER(m_clockingp);
     242                 :             :         m_clockingp = nodep;
     243                 :             :         UINFO(8, "   CLOCKING" << nodep);
     244                 :             :         iterateChildren(nodep);
     245                 :             :         if (nodep->eventp()) nodep->addNextHere(nodep->eventp()->unlinkFrBack());
     246                 :             :         VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
     247                 :             :     }
     248                 :             :     void visit(AstModportClockingRef* const nodep) override {
     249                 :             :         // It has to be converted to a list of ModportClockingVarRefs,
     250                 :             :         // because clocking blocks are removed in this pass
     251                 :             :         for (AstNode* itemp = nodep->clockingp()->itemsp(); itemp; itemp = itemp->nextp()) {
     252                 :             :             if (const AstClockingItem* citemp = VN_CAST(itemp, ClockingItem)) {
     253                 :             :                 if (AstVar* const varp
     254                 :             :                     = citemp->varp() ? citemp->varp() : VN_AS(citemp->user1p(), Var)) {
     255                 :             :                     AstModportVarRef* const modVarp = new AstModportVarRef{
     256                 :             :                         nodep->fileline(), varp->name(), citemp->direction()};
     257                 :             :                     modVarp->varp(varp);
     258                 :             :                     nodep->addNextHere(modVarp);
     259                 :             :                 }
     260                 :             :             }
     261                 :             :         }
     262                 :             :         VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
     263                 :             :     }
     264                 :             :     void visit(AstClockingItem* const nodep) override {
     265                 :             :         // Get a ref to the sampled/driven variable
     266                 :             :         AstVar* const varp = nodep->varp();
     267                 :             :         if (!varp) {
     268                 :             :             // Unused item
     269                 :             :             return;
     270                 :             :         }
     271                 :             :         FileLine* const flp = nodep->fileline();
     272                 :             :         V3Const::constifyEdit(nodep->skewp());
     273                 :             :         if (!VN_IS(nodep->skewp(), Const)) {
     274                 :             :             nodep->skewp()->v3error("Skew must be constant (IEEE 1800-2023 14.4)");
     275                 :             :             return;
     276                 :             :         }
     277                 :             :         AstConst* const skewp = VN_AS(nodep->skewp(), Const);
     278                 :             :         if (skewp->num().isNegative()) skewp->v3error("Skew cannot be negative");
     279                 :             :         AstNodeExpr* const exprp = nodep->exprp();
     280                 :             :         varp->name(m_clockingp->name() + "__DOT__" + varp->name());
     281                 :             :         m_clockingp->addNextHere(varp->unlinkFrBack());
     282                 :             :         nodep->user1p(varp);
     283                 :             :         varp->user1p(nodep);
     284                 :             :         if (nodep->direction() == VDirection::OUTPUT) {
     285                 :             :             exprp->foreach([](const AstNodeVarRef* varrefp) {
     286                 :             :                 // Prevent confusing BLKANDNBLK warnings on clockvars due to generated assignments
     287                 :             :                 varrefp->fileline()->warnOff(V3ErrorCode::BLKANDNBLK, true);
     288                 :             :             });
     289                 :             :             AstVarRef* const skewedReadRefp = new AstVarRef{flp, varp, VAccess::READ};
     290                 :             :             skewedReadRefp->user1(true);
     291                 :             :             // Initialize the clockvar
     292                 :             :             AstVarRef* const skewedWriteRefp = new AstVarRef{flp, varp, VAccess::WRITE};
     293                 :             :             skewedWriteRefp->user1(true);
     294                 :             :             AstInitialStatic* const initClockvarp = new AstInitialStatic{
     295                 :             :                 flp, new AstAssign{flp, skewedWriteRefp, exprp->cloneTreePure(false)}};
     296                 :             :             m_modp->addStmtsp(initClockvarp);
     297                 :             :             // A var to keep the previous value of the clockvar
     298                 :             :             AstVar* const prevVarp = new AstVar{
     299                 :             :                 flp, VVarType::MODULETEMP, "__Vclocking_prev__" + varp->name(), exprp->dtypep()};
     300                 :             :             prevVarp->lifetime(VLifetime::STATIC_EXPLICIT);
     301                 :             :             AstInitialStatic* const initPrevClockvarp = new AstInitialStatic{
     302                 :             :                 flp, new AstAssign{flp, new AstVarRef{flp, prevVarp, VAccess::WRITE},
     303                 :             :                                    skewedReadRefp->cloneTreePure(false)}};
     304                 :             :             m_modp->addStmtsp(prevVarp);
     305                 :             :             m_modp->addStmtsp(initPrevClockvarp);
     306                 :             :             // Assign the clockvar to the actual var; only do it if the clockvar's value has
     307                 :             :             // changed
     308                 :             :             AstAssign* const assignp
     309                 :             :                 = new AstAssign{flp, exprp->cloneTreePure(false), skewedReadRefp};
     310                 :             :             AstIf* const ifp
     311                 :             :                 = new AstIf{flp,
     312                 :             :                             new AstNeq{flp, new AstVarRef{flp, prevVarp, VAccess::READ},
     313                 :             :                                        skewedReadRefp->cloneTreePure(false)},
     314                 :             :                             assignp};
     315                 :             :             ifp->addThensp(new AstAssign{flp, new AstVarRef{flp, prevVarp, VAccess::WRITE},
     316                 :             :                                          skewedReadRefp->cloneTree(false)});
     317                 :             :             if (skewp->isZero()) {
     318                 :             :                 // Drive the var in Re-NBA (IEEE 1800-2023 14.16)
     319                 :             :                 AstSenTree* senTreep
     320                 :             :                     = new AstSenTree{flp, m_clockingp->sensesp()->cloneTree(false)};
     321                 :             :                 senTreep->addSensesp(
     322                 :             :                     new AstSenItem{flp, VEdgeType::ET_CHANGED, skewedReadRefp->cloneTree(false)});
     323                 :             :                 AstCMethodHard* const trigp = new AstCMethodHard{
     324                 :             :                     nodep->fileline(),
     325                 :             :                     new AstVarRef{flp, m_clockingp->ensureEventp(), VAccess::READ},
     326                 :             :                     VCMethod::EVENT_IS_TRIGGERED};
     327                 :             :                 trigp->dtypeSetBit();
     328                 :             :                 ifp->condp(new AstLogAnd{flp, ifp->condp()->unlinkFrBack(), trigp});
     329                 :             :                 m_clockingp->addNextHere(new AstAlwaysReactive{flp, senTreep, ifp});
     330                 :             :             } else if (skewp->fileline()->timingOn()) {
     331                 :             :                 // Create a fork so that this AlwaysObserved can be retriggered before the
     332                 :             :                 // assignment happens. Also then it can be combo, avoiding the need for creating
     333                 :             :                 // new triggers.
     334                 :             :                 AstFork* const forkp = new AstFork{flp, VJoinType::JOIN_NONE};
     335                 :             :                 forkp->addForksp(new AstBegin{flp, "", ifp, true});
     336                 :             :                 // Use Observed for this to make sure we do not miss the event
     337                 :             :                 m_clockingp->addNextHere(new AstAlwaysObserved{
     338                 :             :                     flp, new AstSenTree{flp, m_clockingp->sensesp()->cloneTree(false)}, forkp});
     339                 :             :                 if (v3Global.opt.timing().isSetTrue()) {
     340                 :             :                     AstDelay* const delayp = new AstDelay{flp, skewp->unlinkFrBack(), false};
     341                 :             :                     delayp->timeunit(m_modp->timeunit());
     342                 :             :                     assignp->timingControlp(delayp);
     343                 :             :                 } else if (v3Global.opt.timing().isSetFalse()) {
     344                 :             :                     nodep->v3warn(E_NOTIMING,
     345                 :             :                                   "Clocking output skew greater than #0 requires --timing");
     346                 :             :                 } else {
     347                 :             :                     nodep->v3warn(E_NEEDTIMINGOPT,
     348                 :             :                                   "Use --timing or --no-timing to specify how "
     349                 :             :                                   "clocking output skew greater than #0 should be handled");
     350                 :             :                 }
     351                 :             :             }
     352                 :             :         } else if (nodep->direction() == VDirection::INPUT) {
     353                 :             :             // Ref to the clockvar
     354                 :             :             AstVarRef* const refp = new AstVarRef{flp, varp, VAccess::WRITE};
     355                 :             :             refp->user1(true);
     356                 :             :             if (skewp->num().is1Step()) {
     357                 :             :                 // #1step means the value that is sampled is always the signal's last value
     358                 :             :                 // before the clock edge (IEEE 1800-2023 14.4)
     359                 :             :                 AstSampled* const sampledp = new AstSampled{flp, exprp->cloneTreePure(false)};
     360                 :             :                 sampledp->dtypeFrom(exprp);
     361                 :             :                 AstAssign* const assignp = new AstAssign{flp, refp, sampledp};
     362                 :             :                 m_clockingp->addNextHere(new AstAlways{
     363                 :             :                     flp, VAlwaysKwd::ALWAYS,
     364                 :             :                     new AstSenTree{flp, m_clockingp->sensesp()->cloneTree(false)}, assignp});
     365                 :             :             } else if (skewp->isZero()) {
     366                 :             :                 // #0 means the var has to be sampled in Observed (IEEE 1800-2023 14.13)
     367                 :             :                 AstAssign* const assignp = new AstAssign{flp, refp, exprp->cloneTreePure(false)};
     368                 :             :                 m_clockingp->addNextHere(new AstAlwaysObserved{
     369                 :             :                     flp, new AstSenTree{flp, m_clockingp->sensesp()->cloneTree(false)}, assignp});
     370                 :             :             } else {
     371                 :             :                 // Create a queue where we'll store sampled values with timestamps
     372                 :             :                 AstSampleQueueDType* const queueDtp
     373                 :             :                     = new AstSampleQueueDType{flp, exprp->dtypep()};
     374                 :             :                 m_netlistp->typeTablep()->addTypesp(queueDtp);
     375                 :             :                 AstVar* const queueVarp
     376                 :             :                     = new AstVar{flp, VVarType::MODULETEMP, "__Vqueue__" + varp->name(), queueDtp};
     377                 :             :                 queueVarp->lifetime(VLifetime::STATIC_EXPLICIT);
     378                 :             :                 m_clockingp->addNextHere(queueVarp);
     379                 :             :                 // Create a process like this:
     380                 :             :                 //     always queue.push(<sampled var>);
     381                 :             :                 AstCMethodHard* const pushp = new AstCMethodHard{
     382                 :             :                     flp, new AstVarRef{flp, queueVarp, VAccess::WRITE}, VCMethod::DYN_PUSH,
     383                 :             :                     new AstTime{nodep->fileline(), m_modp->timeunit()}};
     384                 :             :                 pushp->addPinsp(exprp->cloneTreePure(false));
     385                 :             :                 pushp->dtypeSetVoid();
     386                 :             :                 m_clockingp->addNextHere(
     387                 :             :                     new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, pushp->makeStmt()});
     388                 :             :                 // Create a process like this:
     389                 :             :                 //     always @<clocking event> queue.pop(<skew>, /*out*/<skewed var>);
     390                 :             :                 AstCMethodHard* const popp = new AstCMethodHard{
     391                 :             :                     flp, new AstVarRef{flp, queueVarp, VAccess::READWRITE}, VCMethod::DYN_POP,
     392                 :             :                     new AstTime{nodep->fileline(), m_modp->timeunit()}};
     393                 :             :                 popp->addPinsp(skewp->unlinkFrBack());
     394                 :             :                 popp->addPinsp(refp);
     395                 :             :                 popp->dtypeSetVoid();
     396                 :             :                 m_clockingp->addNextHere(
     397                 :             :                     new AstAlways{flp, VAlwaysKwd::ALWAYS,
     398                 :             :                                   new AstSenTree{flp, m_clockingp->sensesp()->cloneTree(false)},
     399                 :             :                                   popp->makeStmt()});
     400                 :             :             }
     401                 :             :         } else {
     402                 :             :             nodep->v3fatalSrc("Invalid direction");
     403                 :             :         }
     404                 :             :     }
     405                 :             :     void visit(AstDelay* nodep) override {
     406                 :             :         // Only cycle delays are relevant in this stage; also only process once
     407                 :             :         if (!nodep->isCycleDelay()) {
     408                 :             :             if (m_inSynchDrive) {
     409                 :             :                 nodep->v3error("Only cycle delays can be used in synchronous drives"
     410                 :             :                                " (IEEE 1800-2023 14.16)");
     411                 :             :             }
     412                 :             :             iterateChildren(nodep);
     413                 :             :             return;
     414                 :             :         }
     415                 :             :         if (m_inAssign && !m_inSynchDrive) {
     416                 :             :             nodep->v3error("Cycle delays not allowed as intra-assignment delays"
     417                 :             :                            " (IEEE 1800-2023 14.11)");
     418                 :             :             VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
     419                 :             :             return;
     420                 :             :         }
     421                 :             :         if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBackWithNext());
     422                 :             :         FileLine* const flp = nodep->fileline();
     423                 :             :         AstNodeExpr* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack());
     424                 :             :         const AstConst* const constp = VN_CAST(valuep, Const);
     425                 :             :         if (!constp) {
     426                 :             :             // V3AssertNfa handles non-const delays before this pass and
     427                 :             :             // replaces the property; this branch should never be reached.
     428                 :             :             nodep->v3fatalSrc("Non-constant cycle delay in assertion: "
     429                 :             :                               "should have been caught by V3AssertNfa");
     430                 :             :         } else if (constp->isZero()) {
     431                 :             :             VL_DO_DANGLING(pushDeletep(valuep), valuep);
     432                 :             :             if (m_inSynchDrive) {
     433                 :             :                 // ##0 has no effect in synchronous drives (IEEE 1800-2023 14.11)
     434                 :             :                 VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
     435                 :             :                 return;
     436                 :             :             }
     437                 :             :             if (m_pexprp) {
     438                 :             :                 // ##0 in sequence context = zero delay = same clock tick
     439                 :             :                 VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
     440                 :             :                 return;
     441                 :             :             }
     442                 :             :             // Procedural ##0: synchronize with default clocking event (IEEE 1800-2023 14.11)
     443                 :             :             // If the clocking event has not yet occurred this timestep, wait for it;
     444                 :             :             // otherwise continue without suspension.
     445                 :             :             if (!m_defaultClockingp) {
     446                 :             :                 nodep->v3error("Usage of cycle delays requires default clocking"
     447                 :             :                                " (IEEE 1800-2023 14.11)");
     448                 :             :                 VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
     449                 :             :                 return;
     450                 :             :             }
     451                 :             :             AstVar* const evtVarp = m_defaultClkEvtVarp;
     452                 :             :             UASSERT_OBJ(evtVarp, nodep, "Default clocking event var not pre-created");
     453                 :             :             AstCMethodHard* const isTriggeredp = new AstCMethodHard{
     454                 :             :                 flp, new AstVarRef{flp, evtVarp, VAccess::READ}, VCMethod::EVENT_IS_TRIGGERED};
     455                 :             :             isTriggeredp->dtypeSetBit();
     456                 :             :             AstEventControl* const waitp = new AstEventControl{
     457                 :             :                 flp,
     458                 :             :                 new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_EVENT,
     459                 :             :                                                    new AstVarRef{flp, evtVarp, VAccess::READ}}},
     460                 :             :                 nullptr};
     461                 :             :             AstIf* const ifp = new AstIf{flp, new AstNot{flp, isTriggeredp}, waitp};
     462                 :             :             nodep->replaceWith(ifp);
     463                 :             :             VL_DO_DANGLING(pushDeletep(nodep), nodep);
     464                 :             :             return;
     465                 :             :         }
     466                 :             :         AstSenItem* sensesp = nullptr;
     467                 :             :         if (!m_defaultClockingp) {
     468                 :             :             if (!m_pexprp) {
     469                 :             :                 nodep->v3error("Usage of cycle delays requires default clocking"
     470                 :             :                                " (IEEE 1800-2023 14.11)");
     471                 :             :                 VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
     472                 :             :                 VL_DO_DANGLING(valuep->deleteTree(), valuep);
     473                 :             :                 return;
     474                 :             :             }
     475                 :             :             sensesp = m_senip;
     476                 :             :         } else {
     477                 :             :             sensesp = m_defaultClockingp->sensesp();
     478                 :             :         }
     479                 :             :         AstEventControl* const controlp = new AstEventControl{
     480                 :             :             nodep->fileline(), new AstSenTree{flp, sensesp->cloneTree(false)}, nullptr};
     481                 :             :         const std::string delayName = m_cycleDlyNames.get(nodep);
     482                 :             :         AstNodeExpr* throughoutp
     483                 :             :             = nodep->throughoutp() ? nodep->throughoutp()->unlinkFrBack() : nullptr;
     484                 :             : 
     485                 :             :         AstVar* const cntVarp = new AstVar{flp, VVarType::BLOCKTEMP, delayName + "__counter",
     486                 :             :                                            nodep->findBasicDType(VBasicDTypeKwd::UINT32)};
     487                 :             :         cntVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
     488                 :             :         AstBegin* const beginp = new AstBegin{flp, delayName + "__block", cntVarp, true};
     489                 :             :         beginp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, valuep});
     490                 :             : 
     491                 :             :         // Throughout: create flag tracking whether condition held every tick
     492                 :             :         AstVar* throughoutOkp = nullptr;
     493                 :             :         if (throughoutp) {
     494                 :             :             throughoutOkp = new AstVar{flp, VVarType::BLOCKTEMP, delayName + "__throughoutOk",
     495                 :             :                                        nodep->findBasicDType(VBasicDTypeKwd::LOGIC_IMPLICIT)};
     496                 :             :             throughoutOkp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
     497                 :             :             beginp->addStmtsp(throughoutOkp);
     498                 :             :             beginp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, throughoutOkp, VAccess::WRITE},
     499                 :             :                                             new AstConst{flp, AstConst::BitTrue{}}});
     500                 :             :             // Check condition at tick 0 (sequence start, before entering loop)
     501                 :             :             beginp->addStmtsp(
     502                 :             :                 new AstIf{flp, new AstLogNot{flp, throughoutp->cloneTreePure(false)},
     503                 :             :                           new AstAssign{flp, new AstVarRef{flp, throughoutOkp, VAccess::WRITE},
     504                 :             :                                         new AstConst{flp, AstConst::BitFalse{}}}});
     505                 :             :         }
     506                 :             : 
     507                 :             :         {
     508                 :             :             AstLoop* const loopp = new AstLoop{flp};
     509                 :             :             // When throughout is present, exit loop early if condition fails
     510                 :             :             AstNodeExpr* loopCondp
     511                 :             :                 = new AstGt{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 0}};
     512                 :             :             if (throughoutOkp) {
     513                 :             :                 loopCondp = new AstLogAnd{flp, loopCondp,
     514                 :             :                                           new AstVarRef{flp, throughoutOkp, VAccess::READ}};
     515                 :             :             }
     516                 :             :             loopp->addStmtsp(new AstLoopTest{flp, loopp, loopCondp});
     517                 :             :             loopp->addStmtsp(controlp);
     518                 :             :             loopp->addStmtsp(
     519                 :             :                 new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE},
     520                 :             :                               new AstSub{flp, new AstVarRef{flp, cntVarp, VAccess::READ},
     521                 :             :                                          new AstConst{flp, 1}}});
     522                 :             :             // Check throughout condition at each tick during delay (IEEE 1800-2023 16.9.9)
     523                 :             :             if (throughoutp) {
     524                 :             :                 loopp->addStmtsp(
     525                 :             :                     new AstIf{flp, new AstLogNot{flp, throughoutp},
     526                 :             :                               new AstAssign{flp, new AstVarRef{flp, throughoutOkp, VAccess::WRITE},
     527                 :             :                                             new AstConst{flp, AstConst::BitFalse{}}}});
     528                 :             :             }
     529                 :             :             beginp->addStmtsp(loopp);
     530                 :             :         }
     531                 :             :         // Compose wrappers on remaining sequence: throughout gate (inner), disable iff (outer)
     532                 :             :         AstNode* remainp = nodep->nextp() ? nodep->nextp()->unlinkFrBackWithNext() : nullptr;
     533                 :             :         if (throughoutOkp) {
     534                 :             :             // If condition failed during delay, fail assertion
     535                 :             :             remainp = new AstIf{flp, new AstVarRef{flp, throughoutOkp, VAccess::READ}, remainp,
     536                 :             :                                 new AstPExprClause{flp, /*pass=*/false}};
     537                 :             :         }
     538                 :             :         if (m_disableSeqIfp && remainp) {
     539                 :             :             AstIf* const disableSeqIfp = m_disableSeqIfp->cloneTree(false);
     540                 :             :             // Keep continuation statements in a proper statement-list container.
     541                 :             :             disableSeqIfp->addThensp(new AstBegin{flp, "", remainp, true});
     542                 :             :             remainp = disableSeqIfp;
     543                 :             :         }
     544                 :             :         if (remainp) {
     545                 :             :             if (throughoutOkp) {
     546                 :             :                 // throughoutOkp is declared in beginp scope -- check must be inside it
     547                 :             :                 beginp->addStmtsp(remainp);
     548                 :             :             } else {
     549                 :             :                 nodep->addNextHere(remainp);
     550                 :             :             }
     551                 :             :         }
     552                 :             :         nodep->replaceWith(beginp);
     553                 :             :         VL_DO_DANGLING(nodep->deleteTree(), nodep);
     554                 :             :     }
     555                 :             :     void visit(AstSenTree* nodep) override {
     556                 :             :         if (m_inSynchDrive) {
     557                 :             :             nodep->v3error("Event controls cannot be used in "
     558                 :             :                            "synchronous drives (IEEE 1800-2023 14.16)");
     559                 :             :         }
     560                 :             : 
     561                 :             :         const AstSampled* sampledp;
     562                 :             :         if (nodep->exists([&sampledp](const AstSampled* const sp) {
     563                 :             :                 sampledp = sp;
     564                 :             :                 return true;
     565                 :             :             })) {
     566                 :             :             sampledp->v3warn(E_UNSUPPORTED, "Unsupported: $sampled inside sensitivity list");
     567                 :             :         }
     568                 :             :     }
     569                 :             :     void visit(AstNodeVarRef* nodep) override {
     570                 :             :         UINFO(8, " -varref:  " << nodep);
     571                 :             :         UINFO(8, " -varref-var-back:  " << nodep->varp()->backp());
     572                 :             :         UINFO(8, " -varref-var-user1:  " << nodep->varp()->user1p());
     573                 :             :         if (AstClockingItem* const itemp = VN_CAST(
     574                 :             :                 nodep->varp()->user1p() ? nodep->varp()->user1p() : nodep->varp()->firstAbovep(),
     575                 :             :                 ClockingItem)) {
     576                 :             :             if (nodep->user1()) return;
     577                 :             : 
     578                 :             :             // ensure linking still works, this has to be done only once
     579                 :             :             if (AstVarXRef* xrefp = VN_CAST(nodep, VarXRef)) {
     580                 :             :                 UINFO(8, " -clockvarxref-in:  " << xrefp);
     581                 :             :                 string dotted = xrefp->dotted();
     582                 :             :                 const size_t dotPos = dotted.rfind('.');
     583                 :             :                 dotted.erase(dotPos, string::npos);
     584                 :             :                 xrefp->dotted(dotted);
     585                 :             :                 UINFO(8, " -clockvarxref-out: " << xrefp);
     586                 :             :                 m_xrefsp.emplace_back(xrefp);
     587                 :             :             }
     588                 :             : 
     589                 :             :             if (nodep->access().isReadOrRW() && itemp->direction() == VDirection::OUTPUT) {
     590                 :             :                 nodep->v3error("Cannot read from output clockvar (IEEE 1800-2023 14.3)");
     591                 :             :             }
     592                 :             :             if (nodep->access().isWriteOrRW()) {
     593                 :             :                 if (itemp->direction() == VDirection::OUTPUT) {
     594                 :             :                     if (!m_inAssignDlyLhs) {
     595                 :             :                         nodep->v3error("Only non-blocking assignments can write "
     596                 :             :                                        "to clockvars (IEEE 1800-2023 14.16)");
     597                 :             :                     }
     598                 :             :                     if (m_inAssign) m_inSynchDrive = true;
     599                 :             :                 } else if (itemp->direction() == VDirection::INPUT) {
     600                 :             :                     nodep->v3error("Cannot write to input clockvar (IEEE 1800-2023 14.3)");
     601                 :             :                 }
     602                 :             :             }
     603                 :             :             nodep->user1(true);
     604                 :             :         }
     605                 :             :     }
     606                 :             :     void visit(AstMemberSel* nodep) override {
     607                 :             :         if (AstClockingItem* const itemp = VN_CAST(
     608                 :             :                 nodep->varp()->user1p() ? nodep->varp()->user1p() : nodep->varp()->firstAbovep(),
     609                 :             :                 ClockingItem)) {
     610                 :             :             if (nodep->access().isReadOrRW() && itemp->direction() == VDirection::OUTPUT) {
     611                 :             :                 nodep->v3error("Cannot read from output clockvar (IEEE 1800-2023 14.3)");
     612                 :             :             }
     613                 :             :             if (nodep->access().isWriteOrRW()) {
     614                 :             :                 if (itemp->direction() == VDirection::OUTPUT) {
     615                 :             :                     if (!m_inAssignDlyLhs) {
     616                 :             :                         nodep->v3error("Only non-blocking assignments can write "
     617                 :             :                                        "to clockvars (IEEE 1800-2023 14.16)");
     618                 :             :                     }
     619                 :             :                     if (m_inAssign) m_inSynchDrive = true;
     620                 :             :                 } else if (itemp->direction() == VDirection::INPUT) {
     621                 :             :                     nodep->v3error("Cannot write to input clockvar (IEEE 1800-2023 14.3)");
     622                 :             :                 }
     623                 :             :             }
     624                 :             :         }
     625                 :             :     }
     626                 :             :     void visit(AstNodeAssign* nodep) override {
     627                 :             :         if (nodep->user1()) return;
     628                 :             :         VL_RESTORER(m_inAssign);
     629                 :             :         VL_RESTORER(m_inSynchDrive);
     630                 :             :         m_inAssign = true;
     631                 :             :         m_inSynchDrive = false;
     632                 :             :         {
     633                 :             :             VL_RESTORER(m_inAssignDlyLhs);
     634                 :             :             m_inAssignDlyLhs = VN_IS(nodep, AssignDly);
     635                 :             :             iterate(nodep->lhsp());
     636                 :             :         }
     637                 :             :         iterate(nodep->rhsp());
     638                 :             :         if (nodep->timingControlp()) {
     639                 :             :             iterate(nodep->timingControlp());
     640                 :             :         } else if (m_inSynchDrive) {
     641                 :             :             AstAssign* const assignp = new AstAssign{
     642                 :             :                 nodep->fileline(), nodep->lhsp()->unlinkFrBack(), nodep->rhsp()->unlinkFrBack()};
     643                 :             :             assignp->user1(true);
     644                 :             :             nodep->replaceWith(assignp);
     645                 :             :             VL_DO_DANGLING(nodep->deleteTree(), nodep);
     646                 :             :         }
     647                 :             :     }
     648                 :             :     void visit(AstAlways* nodep) override {
     649                 :             :         iterateAndNextNull(nodep->sentreep());
     650                 :             :         if (nodep->sentreep()) m_seniAlwaysp = nodep->sentreep()->sensesp();
     651                 :             :         iterateAndNextNull(nodep->stmtsp());
     652                 :             :         m_seniAlwaysp = nullptr;
     653                 :             :     }
     654                 :             : 
     655                 :             :     void visit(AstNodeCoverOrAssert* nodep) override {
     656                 :             :         if (nodep->sentreep()) return;  // Already processed
     657                 :             : 
     658                 :             :         VL_RESTORER(m_senip);
     659                 :             :         VL_RESTORER(m_disablep);
     660                 :             :         m_senip = nullptr;
     661                 :             :         m_disablep = nullptr;
     662                 :             : 
     663                 :             :         // Find Clocking's buried under nodep->exprsp
     664                 :             :         iterateChildren(nodep);
     665                 :             :         if (!nodep->immediate()) nodep->sentreep(newSenTree(nodep, nullptr, nodep));
     666                 :             :     }
     667                 :             :     void visit(AstFalling* nodep) override {
     668                 :             :         if (nodep->user1SetOnce()) return;
     669                 :             :         iterateChildren(nodep);
     670                 :             :         FileLine* const fl = nodep->fileline();
     671                 :             :         AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
     672                 :             :         if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
     673                 :             :         AstNodeExpr* const futurep = new AstFuture{fl, exprp, newSenTree(nodep)};
     674                 :             :         futurep->dtypeFrom(exprp);
     675                 :             :         exprp = new AstAnd{fl, exprp->cloneTreePure(false), new AstNot{fl, futurep}};
     676                 :             :         exprp->dtypeSetBit();
     677                 :             :         nodep->replaceWith(exprp);
     678                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
     679                 :             :     }
     680                 :             :     void visit(AstFell* nodep) override {
     681                 :             :         if (nodep->user1SetOnce()) return;
     682                 :             :         iterateChildren(nodep);
     683                 :             :         FileLine* const fl = nodep->fileline();
     684                 :             :         AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
     685                 :             :         if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
     686                 :             :         AstSenTree* sentreep = nodep->sentreep();
     687                 :             :         if (sentreep) sentreep->unlinkFrBack();
     688                 :             :         AstPast* const pastp = new AstPast{fl, exprp};
     689                 :             :         pastp->dtypeFrom(exprp);
     690                 :             :         pastp->sentreep(newSenTree(nodep, sentreep));
     691                 :             :         exprp = new AstAnd{fl, pastp, new AstNot{fl, exprp->cloneTreePure(false)}};
     692                 :             :         exprp->dtypeSetBit();
     693                 :             :         nodep->replaceWith(exprp);
     694                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
     695                 :             :     }
     696                 :             :     void visit(AstFuture* nodep) override {
     697                 :             :         if (nodep->user1SetOnce()) return;
     698                 :             :         iterateChildren(nodep);
     699                 :             :         AstSenTree* const sentreep = nodep->sentreep();
     700                 :             :         if (sentreep) VL_DO_DANGLING(pushDeletep(sentreep->unlinkFrBack()), sentreep);
     701                 :             :         nodep->sentreep(newSenTree(nodep));
     702                 :             :     }
     703                 :             :     void visit(AstPast* nodep) override {
     704                 :             :         if (nodep->sentreep()) return;  // Already processed
     705                 :             :         iterateChildren(nodep);
     706                 :             :         nodep->sentreep(newSenTree(nodep));
     707                 :             :     }
     708                 :             :     void visit(AstSConsRep* nodep) override {
     709                 :             :         // IEEE 1800-2023 16.9.2 -- Lower standalone exact [*N] (N >= 2) via saturating counter.
     710                 :             :         // Range/unbounded forms and SExpr-contained forms are lowered by V3AssertNfa.
     711                 :             :         iterateChildren(nodep);
     712                 :             :         // V3AssertNfa handles unbounded/ranged forms upstream, so this fast-path
     713                 :             :         // is effectively unreachable when NFA is enabled.
     714                 :             :         if (nodep->unbounded() || nodep->maxCountp()) return;  // LCOV_EXCL_LINE
     715                 :             :         const AstConst* const constp = VN_CAST(nodep->countp(), Const);
     716                 :             :         if (VL_UNLIKELY(!constp || constp->toSInt() < 1)) {
     717                 :             :             nodep->v3fatalSrc("Consecutive repetition count must be a positive constant"
     718                 :             :                               " (should have been caught by V3Width)");
     719                 :             :             return;
     720                 :             :         }
     721                 :             :         const int n = constp->toSInt();
     722                 :             :         FileLine* const flp = nodep->fileline();
     723                 :             :         AstNodeExpr* const exprp = nodep->exprp()->unlinkFrBack();
     724                 :             :         if (n == 1) {
     725                 :             :             nodep->replaceWith(exprp);
     726                 :             :             VL_DO_DANGLING(pushDeletep(nodep), nodep);
     727                 :             :             return;
     728                 :             :         }
     729                 :             :         // Saturating counter: if (expr) cnt <= min(cnt+1, N); else cnt <= 0;
     730                 :             :         AstVar* const cntVarp = new AstVar{flp, VVarType::MODULETEMP, m_consRepNames.get(""),
     731                 :             :                                            nodep->findBasicDType(VBasicDTypeKwd::UINT32)};
     732                 :             :         cntVarp->lifetime(VLifetime::STATIC_EXPLICIT);
     733                 :             :         m_modp->addStmtsp(cntVarp);
     734                 :             :         AstNodeExpr* const exprClonep = exprp->cloneTreePure(false);
     735                 :             :         AstNodeExpr* const saturatingIncrp = new AstCond{
     736                 :             :             flp,
     737                 :             :             new AstLt{flp, new AstVarRef{flp, cntVarp, VAccess::READ},
     738                 :             :                       new AstConst{flp, static_cast<uint32_t>(n)}},
     739                 :             :             new AstAdd{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 1u}},
     740                 :             :             new AstConst{flp, static_cast<uint32_t>(n)}};
     741                 :             :         AstAssignDly* const incrAssignp
     742                 :             :             = new AstAssignDly{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, saturatingIncrp};
     743                 :             :         AstAssignDly* const resetAssignp = new AstAssignDly{
     744                 :             :             flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, new AstConst{flp, 0u}};
     745                 :             :         AstIf* const ifp = new AstIf{flp, exprClonep, incrAssignp, resetAssignp};
     746                 :             :         AstSenTree* const senTreep = newSenTree(nodep);
     747                 :             :         AstAlways* const alwaysp = new AstAlways{flp, VAlwaysKwd::ALWAYS, senTreep, ifp};
     748                 :             :         cntVarp->addNextHere(alwaysp);
     749                 :             :         // Match: cnt >= N-1 (previous cycles via NBA) && expr (current cycle)
     750                 :             :         AstNodeExpr* const cntCheckp = new AstGte{flp, new AstVarRef{flp, cntVarp, VAccess::READ},
     751                 :             :                                                   new AstConst{flp, static_cast<uint32_t>(n - 1)}};
     752                 :             :         cntCheckp->dtypeSetBit();
     753                 :             :         AstNodeExpr* const matchp = new AstAnd{flp, cntCheckp, exprp};
     754                 :             :         matchp->dtypeSetBit();
     755                 :             :         nodep->replaceWith(matchp);
     756                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
     757                 :             :     }
     758                 :             :     void visit(AstRising* nodep) override {
     759                 :             :         if (nodep->user1SetOnce()) return;
     760                 :             :         iterateChildren(nodep);
     761                 :             :         FileLine* const fl = nodep->fileline();
     762                 :             :         AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
     763                 :             :         if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
     764                 :             :         AstNodeExpr* const futurep = new AstFuture{fl, exprp, newSenTree(nodep)};
     765                 :             :         futurep->dtypeFrom(exprp);
     766                 :             :         exprp = new AstAnd{fl, new AstNot{fl, exprp->cloneTreePure(false)}, futurep};
     767                 :             :         exprp->dtypeSetBit();
     768                 :             :         nodep->replaceWith(exprp);
     769                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
     770                 :             :     }
     771                 :             :     void visit(AstRose* nodep) override {
     772                 :             :         if (nodep->user1SetOnce()) return;
     773                 :             :         iterateChildren(nodep);
     774                 :             :         FileLine* const fl = nodep->fileline();
     775                 :             :         AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
     776                 :             :         if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
     777                 :             :         AstSenTree* sentreep = nodep->sentreep();
     778                 :             :         if (sentreep) sentreep->unlinkFrBack();
     779                 :             :         AstPast* const pastp = new AstPast{fl, exprp};
     780                 :             :         pastp->dtypeFrom(exprp);
     781                 :             :         pastp->sentreep(newSenTree(nodep, sentreep));
     782                 :             :         exprp = new AstAnd{fl, new AstNot{fl, pastp}, exprp->cloneTreePure(false)};
     783                 :             :         exprp->dtypeSetBit();
     784                 :             :         nodep->replaceWith(exprp);
     785                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
     786                 :             :     }
     787                 :          49 :     static AstNode* getMemberp(const AstNodeModule* const nodep, const std::string& name) {
     788         [ +  - ]:         112 :         for (AstNode* itemp = nodep->stmtsp(); itemp; itemp = itemp->nextp()) {
     789         [ +  + ]:         112 :             if (itemp->name() == name) return itemp;
     790                 :             :         }
     791                 :           0 :         return nullptr;
     792                 :             :     }
     793                 :           7 :     static AstAssocArrayDType* getProcessAssocArrayType(FileLine* const flp) {
     794                 :             :         // Type of __VassertsActive___x[std::process]
     795                 :             :         AstClass* const processClassp
     796                 :           7 :             = VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class);
     797                 :             :         AstNodeDType* valp
     798                 :           7 :             = v3Global.rootp()->typeTablep()->findBasicDType(flp, VBasicDTypeKwd::BIT);
     799                 :           7 :         AstClassRefDType* keyp = new AstClassRefDType{flp, processClassp, nullptr};
     800                 :           7 :         keyp->classOrPackagep(processClassp);
     801                 :           7 :         v3Global.rootp()->typeTablep()->addTypesp(keyp);
     802                 :           7 :         AstAssocArrayDType* const typep = new AstAssocArrayDType{flp, valp, keyp};
     803                 :           7 :         typep->dtypep(typep);
     804                 :           7 :         v3Global.rootp()->typeTablep()->addTypesp(typep);
     805                 :           7 :         return typep;
     806                 :             :     }
     807                 :          14 :     static AstNodeExpr* getProcessSelf(FileLine* const flp) {
     808                 :             :         // Constructs std::process::self() expression
     809                 :             :         AstClass* const processClassp
     810                 :          28 :             = VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class);
     811                 :          14 :         AstFunc* const selfMethodp = VN_AS(getMemberp(processClassp, "self"), Func);
     812                 :          14 :         AstFuncRef* const processSelfp = new AstFuncRef{flp, selfMethodp};
     813                 :          14 :         processSelfp->classOrPackagep(processClassp);
     814                 :          14 :         return processSelfp;
     815                 :             :     }
     816                 :           7 :     static AstStmtExpr* getProcessAssocArrayDelete(AstVarRef* const refp) {
     817                 :             :         // Constructs refp.delete(std::process::self()) statement
     818                 :           7 :         FileLine* const flp = refp->fileline();
     819                 :             :         AstClass* const processClassp
     820                 :           7 :             = VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class);
     821                 :           7 :         refp->classOrPackagep(processClassp);
     822                 :             :         AstCMethodHard* const deletep
     823                 :           7 :             = new AstCMethodHard{flp, refp, VCMethod::ASSOC_ERASE, getProcessSelf(flp)};
     824                 :           7 :         deletep->dtypep(refp->findVoidDType());
     825                 :           7 :         return new AstStmtExpr{flp, deletep};
     826                 :             :     }
     827                 :           7 :     static AstNodeExpr* getProcessAssocArraySize(AstVarRef* const refp) {
     828                 :             :         // Constructs refp.size() statement
     829                 :             :         AstClass* const processClassp
     830                 :           7 :             = VN_AS(getMemberp(v3Global.rootp()->stdPackagep(), "process"), Class);
     831                 :           7 :         refp->classOrPackagep(processClassp);
     832                 :             :         AstCMethodHard* const sizep
     833                 :           7 :             = new AstCMethodHard{refp->fileline(), refp, VCMethod::ASSOC_SIZE};
     834                 :           7 :         sizep->dtypep(refp->findBasicDType(VBasicDTypeKwd::UINT32));
     835                 :           7 :         return sizep;
     836                 :             :     }
     837                 :          11 :     void visit(AstSEventually* nodep) override {
     838                 :          11 :         UASSERT(v3Global.rootp()->stdPackagep(), "Should be imported");
     839                 :          11 :         AstSenTree* const sentreep = newSenTree(nodep);
     840         [ +  + ]:          11 :         if (!sentreep->sensesp()) {
     841                 :           4 :             VL_DO_DANGLING(pushDeletep(sentreep), sentreep);
     842                 :           4 :             nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
     843                 :           4 :             VL_DO_DANGLING(pushDeletep(nodep), nodep);
     844                 :           4 :             return;
     845                 :             :         }
     846                 :             : 
     847                 :           7 :         iterateChildren(nodep);
     848                 :           7 :         FileLine* const flp = nodep->fileline();
     849                 :             : 
     850                 :             :         const auto readRef
     851                 :          21 :             = [flp](AstVar* varp) { return new AstVarRef{flp, varp, VAccess::READ}; };
     852                 :             :         const auto writeRef
     853                 :          28 :             = [flp](AstVar* varp) { return new AstVarRef{flp, varp, VAccess::WRITE}; };
     854                 :             : 
     855                 :             :         // Track active assertions
     856                 :          21 :         AstVar* const activep = new AstVar{flp, VVarType::MODULETEMP, m_activeNames.get(""),
     857                 :          14 :                                            getProcessAssocArrayType(flp)};
     858                 :           7 :         activep->lifetime(VLifetime::STATIC_EXPLICIT);
     859                 :           7 :         m_modp->addStmtsp(activep);
     860                 :             : 
     861                 :             :         // Assertion condition check
     862                 :           7 :         AstLoop* const loopp = new AstLoop{flp};
     863                 :           7 :         AstNodeExpr* const condp = new AstSampled{flp, nodep->exprp()->unlinkFrBack()};
     864                 :           7 :         loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLogNot{flp, condp}});
     865                 :           7 :         loopp->addStmtsp(new AstEventControl{flp, sentreep, nullptr});
     866                 :             : 
     867                 :             :         // Add assertion to the active set
     868                 :           7 :         AstAssocSel* const selp = new AstAssocSel{flp, writeRef(activep), getProcessSelf(flp)};
     869                 :           7 :         AstAssign* const incrementp = new AstAssign{flp, selp, new AstConst{flp, 1}};
     870                 :           7 :         AstPExprClause* const clausep = new AstPExprClause{flp};
     871                 :           7 :         AstStmtExpr* const deletep = getProcessAssocArrayDelete(writeRef(activep));
     872                 :             : 
     873                 :             :         // Main assertion block
     874                 :          14 :         AstBegin* const bodyp = new AstBegin{flp, "", nullptr, true};
     875                 :           7 :         bodyp->addStmtsp(incrementp);
     876                 :           7 :         bodyp->addStmtsp(loopp);
     877                 :           7 :         bodyp->addStmtsp(clausep);
     878                 :           7 :         bodyp->addStmtsp(deletep);
     879                 :             : 
     880                 :             :         // Validate assertion condition for each active assert
     881                 :             :         AstVar* const activeCountp = new AstVar{flp, VVarType::BLOCKTEMP, "__VassertCount",
     882                 :          14 :                                                 nodep->findBasicDType(VBasicDTypeKwd::UINT32)};
     883                 :           7 :         activeCountp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
     884                 :             : 
     885                 :             :         AstAssign* const initActiveCountp = new AstAssign{
     886                 :           7 :             flp, writeRef(activeCountp), getProcessAssocArraySize(readRef(activep))};
     887                 :           7 :         AstLoop* const finalLoopp = new AstLoop{flp};
     888                 :             :         AstIf* const finalBodypCondp
     889                 :           7 :             = new AstIf{flp, condp->cloneTreePure(false), new AstPExprClause{flp},
     890                 :          14 :                         new AstPExprClause{flp, false}};
     891                 :           7 :         finalLoopp->addStmtsp(new AstLoopTest{
     892                 :           7 :             flp, finalLoopp, new AstNeq{flp, readRef(activeCountp), new AstConst{flp, 0}}});
     893                 :           7 :         finalLoopp->addStmtsp(finalBodypCondp);
     894                 :           7 :         finalLoopp->addStmtsp(
     895                 :           7 :             new AstAssign{flp, writeRef(activeCountp),
     896                 :           7 :                           new AstSub{flp, readRef(activeCountp), new AstConst{flp, 1}}});
     897                 :             : 
     898                 :             :         // Final assertion block
     899                 :          14 :         AstBegin* const finalp = new AstBegin{flp, "", nullptr, true};
     900                 :           7 :         finalp->addStmtsp(activeCountp);
     901                 :           7 :         finalp->addStmtsp(initActiveCountp);
     902                 :           7 :         finalp->addStmtsp(finalLoopp);
     903                 :             : 
     904                 :           7 :         AstPExpr* const pexprp = new AstPExpr{flp, bodyp, finalp, nodep->dtypep()};
     905                 :           7 :         nodep->replaceWith(pexprp);
     906                 :           7 :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
     907                 :             :     }
     908                 :             :     void visit(AstStable* nodep) override {
     909                 :             :         if (nodep->user1SetOnce()) return;
     910                 :             :         iterateChildren(nodep);
     911                 :             :         FileLine* const fl = nodep->fileline();
     912                 :             :         AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
     913                 :             :         AstSenTree* sentreep = nodep->sentreep();
     914                 :             :         if (sentreep) sentreep->unlinkFrBack();
     915                 :             :         AstPast* const pastp = new AstPast{fl, exprp};
     916                 :             :         pastp->dtypeFrom(exprp);
     917                 :             :         pastp->sentreep(newSenTree(nodep, sentreep));
     918                 :             :         exprp = new AstEq{fl, pastp, exprp->cloneTreePure(false)};
     919                 :             :         exprp->dtypeSetBit();
     920                 :             :         nodep->replaceWith(exprp);
     921                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
     922                 :             :     }
     923                 :             :     void visit(AstSteady* nodep) override {
     924                 :             :         if (nodep->user1SetOnce()) return;
     925                 :             :         iterateChildren(nodep);
     926                 :             :         FileLine* const fl = nodep->fileline();
     927                 :             :         AstNodeExpr* exprp = nodep->exprp()->unlinkFrBack();
     928                 :             :         if (exprp->width() > 1) exprp = new AstSel{fl, exprp, 0, 1};
     929                 :             :         AstNodeExpr* const futurep = new AstFuture{fl, exprp, newSenTree(nodep)};
     930                 :             :         futurep->dtypeFrom(exprp);
     931                 :             :         exprp = new AstEq{fl, exprp->cloneTreePure(false), futurep};
     932                 :             :         exprp->dtypeSetBit();
     933                 :             :         nodep->replaceWith(exprp);
     934                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
     935                 :             :     }
     936                 :             : 
     937                 :             :     // Validate repetition count: must be a non-negative elaboration-time constant.
     938                 :             :     // Shared by goto [->N] and nonconsecutive [=N] repetition.
     939                 :             :     // On error, replaces nodep with BitFalse placeholder and returns nullptr.
     940                 :             :     const AstConst* validateRepCount(AstNode* nodep, AstNodeExpr*& countp) {
     941                 :             :         countp = V3Const::constifyEdit(countp);
     942                 :             :         const AstConst* const constp = VN_CAST(countp, Const);
     943                 :             :         if (!constp) {
     944                 :             :             nodep->v3error("Repetition count is not an elaboration-time constant"
     945                 :             :                            " (IEEE 1800-2023 16.9.2)");
     946                 :             :             VL_DO_DANGLING(pushDeletep(countp), countp);
     947                 :             :             nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
     948                 :             :             VL_DO_DANGLING(pushDeletep(nodep), nodep);
     949                 :             :             return nullptr;
     950                 :             :         }
     951                 :             :         if (constp->toSInt() < 0) {
     952                 :             :             nodep->v3error("Repetition count must be non-negative"
     953                 :             :                            " (IEEE 1800-2023 16.9.2)");
     954                 :             :             VL_DO_DANGLING(pushDeletep(countp), countp);
     955                 :             :             nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
     956                 :             :             VL_DO_DANGLING(pushDeletep(nodep), nodep);
     957                 :             :             return nullptr;
     958                 :             :         }
     959                 :             :         if (constp->isZero()) {
     960                 :             :             nodep->v3warn(E_UNSUPPORTED, "Unsupported: zero repetition count"
     961                 :             :                                          " (IEEE 1800-2023 16.9.2)");
     962                 :             :             VL_DO_DANGLING(pushDeletep(countp), countp);
     963                 :             :             nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
     964                 :             :             VL_DO_DANGLING(pushDeletep(nodep), nodep);
     965                 :             :             return nullptr;
     966                 :             :         }
     967                 :             :         return constp;
     968                 :             :     }
     969                 :             : 
     970                 :             :     // Lower goto/nonconsecutive repetition to a counter-based PExpr loop.
     971                 :             :     // IEEE 1800-2023 16.9.2:
     972                 :             :     //   Goto [->N]: count N occurrences, then check consequent
     973                 :             :     //   Nonconsec [=N] = [->N] ##1 !b[*0:$]: count N, then scan trailing !b window
     974                 :             :     // Generated structure for goto [->N]:
     975                 :             :     //   begin
     976                 :             :     //     automatic uint32 cnt = 0;
     977                 :             :     //     loop { test(cnt < N); if (sampled(expr)) cnt++; if (cnt < N) @(clk); }
     978                 :             :     //     // consequent check or pass clause
     979                 :             :     //   end
     980                 :             :     // Generated structure for nonconsec [=N] with implication:
     981                 :             :     //   begin
     982                 :             :     //     automatic uint32 cnt = 0;
     983                 :             :     //     loop { test(cnt < N); if (sampled(expr)) cnt++; if (cnt < N) @(clk); }
     984                 :             :     //     @(clk);  // ##1
     985                 :             :     //     if (!isOverlapped) @(clk);  // |=> delay
     986                 :             :     //     loop { if (sampled(expr)) fail; if (sampled(rhs)) pass; @(clk); }
     987                 :             :     //   end
     988                 :             :     AstPExpr* createRepPExpr(FileLine* flp, AstNodeExpr* exprp, AstNodeExpr* countp,
     989                 :             :                              AstNodeExpr* rhsp, bool isOverlapped, bool isNonConsec) {
     990                 :             :         AstSenItem* const sensesp = m_senip;
     991                 :             :         UASSERT_OBJ(sensesp, exprp, "Repetition requires a clock");
     992                 :             : 
     993                 :             :         const std::string name
     994                 :             :             = isNonConsec ? m_nonConsRepNames.get(exprp) : m_gotoRepNames.get(exprp);
     995                 :             :         AstVar* const cntVarp = new AstVar{flp, VVarType::BLOCKTEMP, name + "__counter",
     996                 :             :                                            exprp->findBasicDType(VBasicDTypeKwd::UINT32)};
     997                 :             :         cntVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
     998                 :             : 
     999                 :             :         AstBegin* const beginp = new AstBegin{flp, name + "__block", cntVarp, true};
    1000                 :             :         beginp->addStmtsp(
    1001                 :             :             new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, new AstConst{flp, 0}});
    1002                 :             : 
    1003                 :             :         // Counting loop: find N occurrences of expr (shared by goto and nonconsec)
    1004                 :             :         AstLoop* const loopp = new AstLoop{flp};
    1005                 :             :         loopp->addStmtsp(new AstLoopTest{flp, loopp,
    1006                 :             :                                          new AstLt{flp, new AstVarRef{flp, cntVarp, VAccess::READ},
    1007                 :             :                                                    countp->cloneTreePure(false)}});
    1008                 :             :         // if (expr) cnt++ -- sampled is applied to whole property expr by V3Assert
    1009                 :             :         loopp->addStmtsp(
    1010                 :             :             new AstIf{flp, exprp,
    1011                 :             :                       new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE},
    1012                 :             :                                     new AstAdd{flp, new AstVarRef{flp, cntVarp, VAccess::READ},
    1013                 :             :                                                new AstConst{flp, 1}}}});
    1014                 :             :         loopp->addStmtsp(new AstIf{
    1015                 :             :             flp, new AstLt{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, countp},
    1016                 :             :             new AstEventControl{flp, new AstSenTree{flp, sensesp->cloneTree(false)}, nullptr}});
    1017                 :             : 
    1018                 :             :         beginp->addStmtsp(loopp);
    1019                 :             : 
    1020                 :             :         if (isNonConsec && rhsp) {
    1021                 :             :             // IEEE 16.9.2: b[=N] = b[->N] ##1 !b[*0:$]
    1022                 :             :             // Trailing window: ##1 advance, then scan !expr cycles checking rhs.
    1023                 :             :             // Window closes when expr becomes true again (fail if rhs was never true).
    1024                 :             :             beginp->addStmtsp(new AstEventControl{
    1025                 :             :                 flp, new AstSenTree{flp, sensesp->cloneTree(false)}, nullptr});  // ##1
    1026                 :             :             if (!isOverlapped) {
    1027                 :             :                 beginp->addStmtsp(new AstEventControl{
    1028                 :             :                     flp, new AstSenTree{flp, sensesp->cloneTree(false)}, nullptr});  // |=>
    1029                 :             :             }
    1030                 :             :             // Window loop: check rhs at each !expr cycle (done variable for termination)
    1031                 :             :             AstVar* const doneVarp
    1032                 :             :                 = new AstVar{flp, VVarType::BLOCKTEMP, name + "__done", exprp->findBitDType()};
    1033                 :             :             doneVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
    1034                 :             :             beginp->addStmtsp(doneVarp);
    1035                 :             :             beginp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, doneVarp, VAccess::WRITE},
    1036                 :             :                                             new AstConst{flp, AstConst::BitFalse{}}});
    1037                 :             :             auto setDone = [&]() {
    1038                 :             :                 return new AstAssign{flp, new AstVarRef{flp, doneVarp, VAccess::WRITE},
    1039                 :             :                                      new AstConst{flp, AstConst::BitTrue{}}};
    1040                 :             :             };
    1041                 :             :             AstLoop* const windowp = new AstLoop{flp};
    1042                 :             :             // LoopTest: continue while !done
    1043                 :             :             windowp->addStmtsp(new AstLoopTest{
    1044                 :             :                 flp, windowp, new AstNot{flp, new AstVarRef{flp, doneVarp, VAccess::READ}}});
    1045                 :             :             // if (expr) { fail; done = 1; } -- window closed, expr true again
    1046                 :             :             AstBegin* const failBlockp = new AstBegin{flp, "", nullptr, true};
    1047                 :             :             failBlockp->addStmtsp(new AstPExprClause{flp, false});
    1048                 :             :             failBlockp->addStmtsp(setDone());
    1049                 :             :             windowp->addStmtsp(new AstIf{flp, exprp->cloneTreePure(false), failBlockp});
    1050                 :             :             // if (rhs) { pass; done = 1; } -- consequent true at this !expr endpoint
    1051                 :             :             AstBegin* const passBlockp = new AstBegin{flp, "", nullptr, true};
    1052                 :             :             passBlockp->addStmtsp(new AstPExprClause{flp, true});
    1053                 :             :             passBlockp->addStmtsp(setDone());
    1054                 :             :             windowp->addStmtsp(new AstIf{flp, rhsp, passBlockp});
    1055                 :             :             // @(clk) -- advance to next cycle in window
    1056                 :             :             windowp->addStmtsp(
    1057                 :             :                 new AstEventControl{flp, new AstSenTree{flp, sensesp->cloneTree(false)}, nullptr});
    1058                 :             :             beginp->addStmtsp(windowp);
    1059                 :             :         } else if (isNonConsec) {
    1060                 :             :             // Standalone nonconsec: ##1 into window, then pass
    1061                 :             :             beginp->addStmtsp(
    1062                 :             :                 new AstEventControl{flp, new AstSenTree{flp, sensesp->cloneTree(false)}, nullptr});
    1063                 :             :             beginp->addStmtsp(new AstPExprClause{flp, true});
    1064                 :             :         } else if (rhsp) {
    1065                 :             :             // Goto rep: check consequent once at match endpoint
    1066                 :             :             if (!isOverlapped) {
    1067                 :             :                 beginp->addStmtsp(new AstEventControl{
    1068                 :             :                     flp, new AstSenTree{flp, sensesp->cloneTree(false)}, nullptr});
    1069                 :             :             }
    1070                 :             :             beginp->addStmtsp(new AstIf{flp, rhsp, new AstPExprClause{flp, true},
    1071                 :             :                                         new AstPExprClause{flp, false}});
    1072                 :             :         } else {
    1073                 :             :             // Standalone goto: pass after counting
    1074                 :             :             beginp->addStmtsp(new AstPExprClause{flp, true});
    1075                 :             :         }
    1076                 :             : 
    1077                 :             :         return new AstPExpr{flp, beginp, exprp->findBitDType()};
    1078                 :             :     }
    1079                 :             : 
    1080                 :             :     void visit(AstSGotoRep* nodep) override {
    1081                 :             :         // Standalone goto rep (not inside implication antecedent)
    1082                 :             :         iterateChildren(nodep);
    1083                 :             :         FileLine* const flp = nodep->fileline();
    1084                 :             :         AstNodeExpr* countp = nodep->countp()->unlinkFrBack();
    1085                 :             :         if (!validateRepCount(nodep, countp)) return;
    1086                 :             :         AstNodeExpr* const exprp = nodep->exprp()->unlinkFrBack();
    1087                 :             :         AstPExpr* const pexprp = createRepPExpr(flp, exprp, countp, nullptr, true, false);
    1088                 :             :         nodep->replaceWith(pexprp);
    1089                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
    1090                 :             :     }
    1091                 :             : 
    1092                 :             :     void visit(AstSNonConsRep* nodep) override {
    1093                 :             :         // Standalone nonconsecutive rep (not inside implication antecedent)
    1094                 :             :         iterateChildren(nodep);
    1095                 :             :         FileLine* const flp = nodep->fileline();
    1096                 :             :         AstNodeExpr* countp = nodep->countp()->unlinkFrBack();
    1097                 :             :         if (!validateRepCount(nodep, countp)) return;
    1098                 :             :         AstNodeExpr* const exprp = nodep->exprp()->unlinkFrBack();
    1099                 :             :         AstPExpr* const pexprp = createRepPExpr(flp, exprp, countp, nullptr, true, true);
    1100                 :             :         nodep->replaceWith(pexprp);
    1101                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
    1102                 :             :     }
    1103                 :             : 
    1104                 :             :     void visit(AstFuncRef* nodep) override {
    1105                 :             :         // IEEE 1800-2023 16.8: Inline sequence instances wherever they appear
    1106                 :             :         // in the expression tree (inside implications, boolean ops, nested refs, etc.)
    1107                 :             :         if (AstSequence* const seqp = VN_CAST(nodep->taskp(), Sequence)) {
    1108                 :             :             substituteSequenceCall(nodep, seqp);
    1109                 :             :             // The FuncRef has been replaced; do not access nodep after this point.
    1110                 :             :             // The replacement node will be visited by the parent's iterateChildren.
    1111                 :             :             return;
    1112                 :             :         }
    1113                 :             :         iterateChildren(nodep);
    1114                 :             :     }
    1115                 :             :     void visit(AstImplication* nodep) override {
    1116                 :             :         if (nodep->sentreep()) return;  // Already processed
    1117                 :             : 
    1118                 :             :         // Handle goto repetition as antecedent before iterateChildren,
    1119                 :             :         // so the standalone AstSGotoRep visitor doesn't process it
    1120                 :             :         if (AstSGotoRep* const gotop = VN_CAST(nodep->lhsp(), SGotoRep)) {
    1121                 :             :             iterateChildren(gotop);
    1122                 :             :             iterateAndNextNull(nodep->rhsp());
    1123                 :             :             FileLine* const flp = nodep->fileline();
    1124                 :             :             AstNodeExpr* countp = gotop->countp()->unlinkFrBack();
    1125                 :             :             if (!validateRepCount(nodep, countp)) return;
    1126                 :             :             AstNodeExpr* const exprp = gotop->exprp()->unlinkFrBack();
    1127                 :             :             AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack();
    1128                 :             :             AstPExpr* const pexprp
    1129                 :             :                 = createRepPExpr(flp, exprp, countp, rhsp, nodep->isOverlapped(), false);
    1130                 :             :             nodep->replaceWith(pexprp);
    1131                 :             :             VL_DO_DANGLING(pushDeletep(nodep), nodep);
    1132                 :             :             return;
    1133                 :             :         }
    1134                 :             : 
    1135                 :             :         // Handle nonconsecutive repetition as antecedent before iterateChildren,
    1136                 :             :         // so the standalone AstSNonConsRep visitor doesn't process it
    1137                 :             :         if (AstSNonConsRep* const ncrp = VN_CAST(nodep->lhsp(), SNonConsRep)) {
    1138                 :             :             iterateChildren(ncrp);
    1139                 :             :             iterateAndNextNull(nodep->rhsp());
    1140                 :             :             FileLine* const flp = nodep->fileline();
    1141                 :             :             AstNodeExpr* countp = ncrp->countp()->unlinkFrBack();
    1142                 :             :             if (!validateRepCount(nodep, countp)) return;
    1143                 :             :             AstNodeExpr* const exprp = ncrp->exprp()->unlinkFrBack();
    1144                 :             :             AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack();
    1145                 :             :             AstPExpr* const pexprp
    1146                 :             :                 = createRepPExpr(flp, exprp, countp, rhsp, nodep->isOverlapped(), true);
    1147                 :             :             nodep->replaceWith(pexprp);
    1148                 :             :             VL_DO_DANGLING(pushDeletep(nodep), nodep);
    1149                 :             :             return;
    1150                 :             :         }
    1151                 :             : 
    1152                 :             :         iterateChildren(nodep);
    1153                 :             : 
    1154                 :             :         FileLine* const flp = nodep->fileline();
    1155                 :             :         AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack();
    1156                 :             :         AstNodeExpr* lhsp = nodep->lhsp()->unlinkFrBack();
    1157                 :             : 
    1158                 :             :         // Lower sequence match items (IEEE 16.11): (expr, var = val, ...) |-> / |=>
    1159                 :             :         if (AstExprStmt* const exprStmtp = VN_CAST(lhsp, ExprStmt)) {
    1160                 :             :             AstNodeExpr* const antExprp = exprStmtp->resultp()->unlinkFrBack();
    1161                 :             : 
    1162                 :             :             if (nodep->isOverlapped()) {
    1163                 :             :                 // |-> : assign to __Vpropvar via always_comb (continuous).
    1164                 :             :                 // The assign evaluates RHS once; V3Sampled snapshots the
    1165                 :             :                 // result so all consequent refs read the same value.
    1166                 :             :                 AstNode* matchAssignsp = nullptr;
    1167                 :             :                 for (AstNode* stmtp = exprStmtp->stmtsp(); stmtp;) {
    1168                 :             :                     AstNode* const nextp = stmtp->nextp();
    1169                 :             :                     if (AstAssign* const assignp = VN_CAST(stmtp, Assign)) {
    1170                 :             :                         assignp->unlinkFrBack();
    1171                 :             :                         if (!matchAssignsp) {
    1172                 :             :                             matchAssignsp = assignp;
    1173                 :             :                         } else {
    1174                 :             :                             matchAssignsp->addNext(assignp);
    1175                 :             :                         }
    1176                 :             :                     }
    1177                 :             :                     stmtp = nextp;
    1178                 :             :                 }
    1179                 :             :                 VL_DO_DANGLING(pushDeletep(lhsp), lhsp);
    1180                 :             :                 lhsp = antExprp;
    1181                 :             : 
    1182                 :             :                 if (matchAssignsp) {
    1183                 :             :                     AstAlways* const alwaysp
    1184                 :             :                         = new AstAlways{flp, VAlwaysKwd::ALWAYS_COMB, nullptr, matchAssignsp};
    1185                 :             :                     m_modp->addStmtsp(alwaysp);
    1186                 :             :                 }
    1187                 :             :             } else {
    1188                 :             :                 // |=> : assign to __Vpropvar via NBA in a clocked always block.
    1189                 :             :                 // The NBA commits before the next cycle's sampled snapshot,
    1190                 :             :                 // so the consequent (which already references __Vpropvar)
    1191                 :             :                 // sees the captured value.
    1192                 :             :                 AstNode* matchAssignsp = nullptr;
    1193                 :             :                 for (AstNode* stmtp = exprStmtp->stmtsp(); stmtp;) {
    1194                 :             :                     AstNode* const nextp = stmtp->nextp();
    1195                 :             :                     if (AstAssign* const assignp = VN_CAST(stmtp, Assign)) {
    1196                 :             :                         assignp->unlinkFrBack();
    1197                 :             :                         AstNodeExpr* const assignLhsp = assignp->lhsp()->unlinkFrBack();
    1198                 :             :                         AstNodeExpr* const assignRhsp = assignp->rhsp()->unlinkFrBack();
    1199                 :             :                         AstAssignDly* const dlyp = new AstAssignDly{flp, assignLhsp, assignRhsp};
    1200                 :             :                         VL_DO_DANGLING(pushDeletep(assignp), assignp);
    1201                 :             :                         if (!matchAssignsp) {
    1202                 :             :                             matchAssignsp = dlyp;
    1203                 :             :                         } else {
    1204                 :             :                             matchAssignsp->addNext(dlyp);
    1205                 :             :                         }
    1206                 :             :                     }
    1207                 :             :                     stmtp = nextp;
    1208                 :             :                 }
    1209                 :             :                 VL_DO_DANGLING(pushDeletep(lhsp), lhsp);
    1210                 :             :                 lhsp = antExprp;
    1211                 :             : 
    1212                 :             :                 if (matchAssignsp) {
    1213                 :             :                     AstIf* const condp
    1214                 :             :                         = new AstIf{flp, antExprp->cloneTreePure(false), matchAssignsp};
    1215                 :             :                     AstAlways* const alwaysp
    1216                 :             :                         = new AstAlways{flp, VAlwaysKwd::ALWAYS, newSenTree(nodep), condp};
    1217                 :             :                     m_modp->addStmtsp(alwaysp);
    1218                 :             :                 }
    1219                 :             :             }
    1220                 :             :         }
    1221                 :             : 
    1222                 :             :         if (AstPExpr* const pexprp = VN_CAST(rhsp, PExpr)) {
    1223                 :             :             // Implication with sequence expression on RHS (IEEE 1800-2023 16.11, 16.12.7).
    1224                 :             :             // The PExpr was lowered from the property expression earlier in this pass.
    1225                 :             :             // Wrap the PExpr body with the antecedent check so the sequence only
    1226                 :             :             // starts when the antecedent holds.
    1227                 :             :             AstNodeExpr* condp;
    1228                 :             :             if (nodep->isOverlapped()) {
    1229                 :             :                 // Overlapped implication (|->): check antecedent on same cycle.
    1230                 :             :                 // disable iff is applied at the assertion level, not at the
    1231                 :             :                 // antecedent gate, matching the existing non-PExpr overlapped path.
    1232                 :             :                 condp = lhsp;
    1233                 :             :             } else {
    1234                 :             :                 // Non-overlapped implication (|=>): check antecedent from previous cycle
    1235                 :             :                 if (m_disablep) {
    1236                 :             :                     lhsp
    1237                 :             :                         = new AstAnd{flp, new AstNot{flp, m_disablep->cloneTreePure(false)}, lhsp};
    1238                 :             :                 }
    1239                 :             :                 AstPast* const pastp = new AstPast{flp, lhsp};
    1240                 :             :                 pastp->dtypeFrom(lhsp);
    1241                 :             :                 pastp->sentreep(newSenTree(nodep));
    1242                 :             :                 condp = pastp;
    1243                 :             :             }
    1244                 :             :             // Wrap existing PExpr body: if (antecedent) { <original body> } else { /* vacuous pass
    1245                 :             :             // */ }
    1246                 :             :             AstBegin* const bodyp = pexprp->bodyp();
    1247                 :             :             AstNode* const origStmtsp = bodyp->stmtsp()->unlinkFrBackWithNext();
    1248                 :             :             AstIf* const guardp = new AstIf{flp, condp, new AstBegin{flp, "", origStmtsp, true}};
    1249                 :             :             bodyp->addStmtsp(guardp);
    1250                 :             :             nodep->replaceWith(pexprp);
    1251                 :             :             // Don't iterate pexprp here -- it was already iterated when created
    1252                 :             :             // (in visit(AstSExpr*)), so delays and disable iff are already processed.
    1253                 :             :         } else if (nodep->isOverlapped()) {
    1254                 :             :             nodep->replaceWith(new AstLogOr{flp, new AstLogNot{flp, lhsp}, rhsp});
    1255                 :             :         } else {
    1256                 :             :             if (m_disablep) {
    1257                 :             :                 lhsp = new AstAnd{flp, new AstNot{flp, m_disablep->cloneTreePure(false)}, lhsp};
    1258                 :             :             }
    1259                 :             : 
    1260                 :             :             AstPast* const pastp = new AstPast{flp, lhsp};
    1261                 :             :             pastp->dtypeFrom(lhsp);
    1262                 :             :             pastp->sentreep(newSenTree(nodep));
    1263                 :             :             AstNodeExpr* const exprp = new AstOr{flp, new AstNot{flp, pastp}, rhsp};
    1264                 :             :             exprp->dtypeSetBit();
    1265                 :             :             nodep->replaceWith(exprp);
    1266                 :             :         }
    1267                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
    1268                 :             :     }
    1269                 :             :     void visit(AstUntil* nodep) override {
    1270                 :             :         FileLine* const flp = nodep->fileline();
    1271                 :             :         if (m_pexprp) {
    1272                 :             :             nodep->v3warn(E_UNSUPPORTED, "Unsupported: 'until' in complex property expression");
    1273                 :             :             nodep->replaceWith(new AstConst{flp, AstConst::BitFalse{}});
    1274                 :             :             VL_DO_DANGLING(pushDeletep(nodep), nodep);
    1275                 :             :             return;
    1276                 :             :         }
    1277                 :             :         if (nodep->isStrong()) {
    1278                 :             :             nodep->v3warn(E_UNSUPPORTED, "Unsupported: s_until"
    1279                 :             :                                              << (nodep->isOverlapping() ? "_with" : "")
    1280                 :             :                                              << " (in property expresion)");
    1281                 :             :             nodep->replaceWith(new AstConst{flp, AstConst::BitFalse{}});
    1282                 :             :             VL_DO_DANGLING(pushDeletep(nodep), nodep);
    1283                 :             :             return;
    1284                 :             :         }
    1285                 :             :         AstLoop* const loopp = new AstLoop{flp};
    1286                 :             :         AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack();
    1287                 :             :         AstNodeExpr* const lhsp = nodep->lhsp()->unlinkFrBack();
    1288                 :             :         AstLogAnd* const loopCondp = new AstLogAnd{flp, lhsp, new AstLogNot{flp, rhsp}};
    1289                 :             :         loopp->addStmtsp(new AstLoopTest{flp, loopp, loopCondp});
    1290                 :             :         loopp->addStmtsp(new AstEventControl{flp, newSenTree(nodep), nullptr});
    1291                 :             : 
    1292                 :             :         AstNodeExpr* const rhsCopyp = rhsp->cloneTreePure(false);
    1293                 :             :         AstNodeExpr* const passCondp
    1294                 :             :             = nodep->isOverlapping() ? new AstLogAnd{flp, lhsp->cloneTreePure(false), rhsCopyp}
    1295                 :             :                                      : rhsCopyp;
    1296                 :             :         AstBegin* const beginp = new AstBegin{flp, "", loopp, true};
    1297                 :             :         beginp->addStmtsp(
    1298                 :             :             new AstIf{flp, passCondp, new AstPExprClause{flp}, new AstPExprClause{flp, false}});
    1299                 :             : 
    1300                 :             :         AstPExpr* const pexprp = new AstPExpr{flp, beginp, nodep->dtypep()};
    1301                 :             :         pexprp->user1(1);
    1302                 :             :         nodep->replaceWith(pexprp);
    1303                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
    1304                 :             :     }
    1305                 :             : 
    1306                 :             :     void visit(AstDefaultDisable* nodep) override {
    1307                 :             :         // Done with these
    1308                 :             :         VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
    1309                 :             :     }
    1310                 :             :     void visit(AstInferredDisable* nodep) override {
    1311                 :             :         AstNode* newp;
    1312                 :             :         if (m_defaultDisablep) {
    1313                 :             :             newp = m_defaultDisablep->condp()->cloneTreePure(true);
    1314                 :             :         } else {
    1315                 :             :             newp = new AstConst{nodep->fileline(), AstConst::BitFalse{}};
    1316                 :             :         }
    1317                 :             :         nodep->replaceWith(newp);
    1318                 :             :         VL_DO_DANGLING(pushDeletep(nodep), nodep);
    1319                 :             :     }
    1320                 :             : 
    1321                 :             :     void visit(AstPropSpec* nodep) override {
    1322                 :             :         nodep = substitutePropertyCall(nodep);
    1323                 :             :         iterateAndNextNull(nodep->sensesp());
    1324                 :             :         if (m_senip && m_senip != nodep->sensesp())
    1325                 :             :             nodep->v3warn(E_UNSUPPORTED, "Unsupported: Only one PSL clock allowed per assertion");
    1326                 :             :         if (!nodep->disablep() && m_defaultDisablep) {
    1327                 :             :             nodep->disablep(m_defaultDisablep->condp()->cloneTreePure(true));
    1328                 :             :         }
    1329                 :             :         m_disablep = nodep->disablep();
    1330                 :             :         // Unlink and just keep a pointer to it, convert to sentree as needed
    1331                 :             :         m_senip = nodep->sensesp();
    1332                 :             :         iterateNull(nodep->disablep());
    1333                 :             :         if (!VN_AS(nodep->backp(), NodeCoverOrAssert)->immediate()) {
    1334                 :             :             const AstNodeDType* const propDtp = nodep->propp()->dtypep();
    1335                 :             :             nodep->propp(new AstSampled{nodep->fileline(), nodep->propp()->unlinkFrBack()});
    1336                 :             :             nodep->propp()->dtypeFrom(propDtp);
    1337                 :             :         }
    1338                 :             :         iterate(nodep->propp());
    1339                 :             :     }
    1340                 :             :     void visit(AstPExpr* nodep) override {
    1341                 :             :         // V3AssertNfa handles multi-cycle property expressions before this pass,
    1342                 :             :         // so the following unsupported paths are defensive and typically unreached.
    1343                 :             :         if (m_pexprp && m_pexprp->user1()) {  // LCOV_EXCL_START
    1344                 :             :             nodep->v3warn(E_UNSUPPORTED,
    1345                 :             :                           "Unsupported: Complex property expression inside 'until''");
    1346                 :             :             nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
    1347                 :             :             VL_DO_DANGLING(pushDeletep(nodep), nodep);
    1348                 :             :             return;
    1349                 :             :         }  // LCOV_EXCL_STOP
    1350                 :             :         if (AstLogNot* const notp = VN_CAST(nodep->backp(), LogNot)) {
    1351                 :             :             notp->replaceWith(nodep->unlinkFrBack());
    1352                 :             :             VL_DO_DANGLING(pushDeletep(notp), notp);
    1353                 :             :             iterate(nodep);
    1354                 :             :             return;
    1355                 :             :         }
    1356                 :             :         // Sequence expression as antecedent of implication is not yet supported
    1357                 :             :         if (AstImplication* const implp = VN_CAST(nodep->backp(), Implication)) {
    1358                 :             :             if (implp->lhsp() == nodep) {  // LCOV_EXCL_START
    1359                 :             :                 implp->v3warn(E_UNSUPPORTED,
    1360                 :             :                               "Unsupported: Implication with sequence expression as antecedent");
    1361                 :             :                 nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitFalse{}});
    1362                 :             :                 VL_DO_DANGLING(pushDeletep(nodep), nodep);
    1363                 :             :                 return;
    1364                 :             :             }  // LCOV_EXCL_STOP
    1365                 :             :         }
    1366                 :             :         VL_RESTORER(m_pexprp);
    1367                 :             :         VL_RESTORER(m_disableSeqIfp);
    1368                 :             :         m_pexprp = nodep;
    1369                 :             : 
    1370                 :             :         if (m_disablep) {
    1371                 :           7 :             const AstSampled* sampledp = nullptr;
    1372         [ +  + ]:           7 :             if (m_disablep->exists([&sampledp](const AstSampled* const sp) {
    1373                 :           1 :                     sampledp = sp;
    1374                 :           1 :                     return true;
    1375                 :             :                 })) {
    1376                 :           1 :                 sampledp->v3warn(E_UNSUPPORTED,
    1377                 :             :                                  "Unsupported: $sampled inside disabled condition of a sequence");
    1378                 :           1 :                 m_disablep = new AstConst{m_disablep->fileline(), AstConst::BitFalse{}};
    1379                 :             :                 // always a copy is used, so remove it now
    1380                 :           1 :                 pushDeletep(m_disablep);
    1381                 :             :             }
    1382                 :             :             FileLine* const flp = nodep->fileline();
    1383                 :             :             // Add counter which counts times the condition turned true
    1384                 :             :             AstVar* const disableCntp
    1385                 :             :                 = new AstVar{flp, VVarType::MODULETEMP, m_disableCntNames.get(""),
    1386                 :             :                              nodep->findBasicDType(VBasicDTypeKwd::UINT32)};
    1387                 :             :             disableCntp->lifetime(VLifetime::STATIC_EXPLICIT);
    1388                 :             :             m_modp->addStmtsp(disableCntp);
    1389                 :             :             AstVarRef* const readCntRefp = new AstVarRef{flp, disableCntp, VAccess::READ};
    1390                 :             :             AstVarRef* const writeCntRefp = new AstVarRef{flp, disableCntp, VAccess::WRITE};
    1391                 :             :             AstAssign* const incrStmtp = new AstAssign{
    1392                 :             :                 flp, writeCntRefp, new AstAdd{flp, readCntRefp, new AstConst{flp, 1}}};
    1393                 :             :             AstAlways* const alwaysp
    1394                 :             :                 = new AstAlways{flp, VAlwaysKwd::ALWAYS,
    1395                 :             :                                 new AstSenTree{flp, new AstSenItem{flp, VEdgeType::ET_POSEDGE,
    1396                 :             :                                                                    m_disablep->cloneTree(false)}},
    1397                 :             :                                 incrStmtp};
    1398                 :             :             disableCntp->addNextHere(alwaysp);
    1399                 :             : 
    1400                 :             :             // Store value of that counter at the beginning of sequence evaluation
    1401                 :             :             AstBegin* const bodyp = nodep->bodyp();
    1402                 :             :             AstVar* const initialCntp = new AstVar{flp, VVarType::BLOCKTEMP, "__VinitialCnt",
    1403                 :             :                                                    nodep->findBasicDType(VBasicDTypeKwd::UINT32)};
    1404                 :             :             initialCntp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
    1405                 :             :             AstAssign* const assignp
    1406                 :             :                 = new AstAssign{flp, new AstVarRef{flp, initialCntp, VAccess::WRITE},
    1407                 :             :                                 readCntRefp->cloneTree(false)};
    1408                 :             :             // Prepend to the sequence body to keep statement list structure valid.
    1409                 :             :             AstNode* const origStmtsp = bodyp->stmtsp()->unlinkFrBackWithNext();
    1410                 :             :             bodyp->addStmtsp(initialCntp);
    1411                 :             :             initialCntp->addNextHere(assignp);
    1412                 :             :             assignp->addNextHere(origStmtsp);
    1413                 :             : 
    1414                 :             :             m_disableSeqIfp
    1415                 :             :                 = new AstIf{flp, new AstEq{flp, new AstVarRef{flp, initialCntp, VAccess::READ},
    1416                 :             :                                            readCntRefp->cloneTree(false)}};
    1417                 :             :             // Delete it, because it is always copied before insetion to the AST
    1418                 :             :             pushDeletep(m_disableSeqIfp);
    1419                 :             :         }
    1420                 :             :         iterateChildren(nodep);
    1421                 :             :     }
    1422                 :             :     void visit(AstNodeModule* nodep) override {
    1423                 :             :         VL_RESTORER(m_defaultClockingp);
    1424                 :             :         VL_RESTORER(m_defaultClkEvtVarp);
    1425                 :             :         VL_RESTORER(m_defaultDisablep);
    1426                 :             :         VL_RESTORER(m_modp);
    1427                 :             :         m_defaultClockingp = nullptr;
    1428                 :             :         m_defaultClkEvtVarp = nullptr;
    1429                 :             :         nodep->foreach([&](AstClocking* const clockingp) {
    1430                 :             :             if (clockingp->isDefault()) {
    1431                 :             :                 if (m_defaultClockingp) {
    1432                 :             :                     clockingp->v3error("Only one default clocking block allowed per module"
    1433                 :             :                                        " (IEEE 1800-2023 14.12)");
    1434                 :             :                 }
    1435                 :             :                 m_defaultClockingp = clockingp;
    1436                 :             :             }
    1437                 :             :         });
    1438                 :             :         m_defaultDisablep = nullptr;
    1439                 :             :         nodep->foreach([&](AstDefaultDisable* const disablep) {
    1440                 :             :             if (m_defaultDisablep) {
    1441                 :             :                 disablep->v3error("Only one 'default disable iff' allowed per module"
    1442                 :             :                                   " (IEEE 1800-2023 16.15)");
    1443                 :             :             }
    1444                 :             :             m_defaultDisablep = disablep;
    1445                 :             :         });
    1446                 :             :         m_modp = nodep;
    1447                 :             :         // Pre-create and cache the clocking event var before iterating children.
    1448                 :             :         // visit(AstClocking) will unlink the event from the clocking node and place it
    1449                 :             :         // in the module tree, then delete the clocking. After that, ensureEventp() would
    1450                 :             :         // create an orphaned var. Caching here avoids this.
    1451                 :             :         m_defaultClkEvtVarp = m_defaultClockingp ? m_defaultClockingp->ensureEventp() : nullptr;
    1452                 :             :         iterateChildren(nodep);
    1453                 :             :     }
    1454                 :             :     void visit(AstProperty* nodep) override {
    1455                 :             :         // The body will be visited when will be substituted in place of property reference
    1456                 :             :         // (AstFuncRef)
    1457                 :             :         VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
    1458                 :             :     }
    1459                 :             :     void visit(AstSequence* nodep) override {
    1460                 :             :         // Sequence declarations are not visited directly; their bodies are inlined
    1461                 :             :         // at call sites by visit(AstFuncRef*). Collect for post-traversal cleanup.
    1462                 :             :         m_seqsToCleanup.push_back(nodep);
    1463                 :             :     }
    1464                 :             :     void visit(AstNode* nodep) override { iterateChildren(nodep); }
    1465                 :             : 
    1466                 :             : public:
    1467                 :             :     // CONSTRUCTORS
    1468                 :             :     explicit AssertPreVisitor(AstNetlist* nodep)
    1469                 :             :         : m_netlistp{nodep} {
    1470                 :             :         // Process
    1471                 :             :         iterate(nodep);
    1472                 :             :         // Fix up varref names
    1473                 :             :         for (AstVarXRef* xrefp : m_xrefsp) xrefp->name(xrefp->varp()->name());
    1474                 :             :         // Clean up sequence declarations after inlining.
    1475                 :             :         // Referenced sequences that were inlined have isReferenced cleared.
    1476                 :             :         // Remaining referenced sequences are in unsupported contexts (e.g. @seq event).
    1477                 :             :         for (AstSequence* seqp : m_seqsToCleanup) {
    1478                 :             :             if (seqp->isReferenced()) {
    1479                 :             :                 seqp->v3warn(E_UNSUPPORTED,
    1480                 :             :                              "Unsupported: sequence referenced outside assertion property");
    1481                 :             :             } else {
    1482                 :             :                 VL_DO_DANGLING(seqp->unlinkFrBack()->deleteTree(), seqp);
    1483                 :             :             }
    1484                 :             :         }
    1485                 :             :     }
    1486                 :             :     ~AssertPreVisitor() override = default;
    1487                 :             : };
    1488                 :             : 
    1489                 :             : //######################################################################
    1490                 :             : // Top Assert class
    1491                 :             : 
    1492                 :             : void V3AssertPre::assertPreAll(AstNetlist* nodep) {
    1493                 :             :     UINFO(2, __FUNCTION__ << ":");
    1494                 :             :     { AssertPreVisitor{nodep}; }  // Destruct before checking
    1495                 :             :     V3Global::dumpCheckGlobalTree("assertpre", 0, dumpTreeEitherLevel() >= 3);
    1496                 :             : }
        

Generated by: LCOV version 2.0-1