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 : : }
|