diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 8e290a826..a0c82ad26 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -516,7 +516,8 @@ class AssertVisitor final : public VNVisitor { AstNodeExpr* onep; if (AstInsideRange* const rcondp = VN_CAST(icondp, InsideRange)) { onep = rcondp->newAndFromInside( - nodep->exprp(), rcondp->lhsp()->cloneTreePure(true), + nodep->exprp()->cloneTreePure(true), + rcondp->lhsp()->cloneTreePure(true), rcondp->rhsp()->cloneTreePure(true)); } else if (nodep->casex() || nodep->casez() || nodep->caseInside()) { onep = AstEqWild::newTyped(itemp->fileline(), diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index a9c927b5a..a7ef87ae0 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -342,8 +342,14 @@ AstExecGraph::~AstExecGraph() { VL_DO_DANGLING(delete m_depGraphp, m_depGraphp); AstNodeExpr* AstInsideRange::newAndFromInside(AstNodeExpr* exprp, AstNodeExpr* lhsp, AstNodeExpr* rhsp) { - AstNodeExpr* const ap = new AstGte{fileline(), exprp->cloneTreePure(true), lhsp}; - AstNodeExpr* const bp = new AstLte{fileline(), exprp->cloneTreePure(true), rhsp}; + AstNodeExpr* const ap = new AstGte{fileline(), exprp, lhsp}; + AstNodeExpr* lteLhsp; + if (const AstExprStmt* const exprStmt = VN_CAST(exprp, ExprStmt)) { + lteLhsp = exprStmt->resultp()->cloneTreePure(true); + } else { + lteLhsp = exprp->cloneTreePure(true); + } + AstNodeExpr* const bp = new AstLte{fileline(), lteLhsp, rhsp}; ap->fileline()->modifyWarnOff(V3ErrorCode::UNSIGNED, true); bp->fileline()->modifyWarnOff(V3ErrorCode::CMPCONST, true); return new AstLogAnd{fileline(), ap, bp}; diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 49906f1a1..3217a7ec0 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -476,7 +476,8 @@ class CaseVisitor final : public VNVisitor { condp = new AstConst{itemp->fileline(), AstConst::BitFalse{}}; } else if (AstInsideRange* const irangep = VN_CAST(icondp, InsideRange)) { // Similar logic in V3Width::visit(AstInside) - condp = irangep->newAndFromInside(cexprp, irangep->lhsp()->unlinkFrBack(), + condp = irangep->newAndFromInside(cexprp->cloneTreePure(true), + irangep->lhsp()->unlinkFrBack(), irangep->rhsp()->unlinkFrBack()); VL_DO_DANGLING(icondp->deleteTree(), icondp); } else if (iconstp && iconstp->num().isFourState() diff --git a/src/V3Width.cpp b/src/V3Width.cpp index b8e1fc171..0b162b508 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -78,6 +78,7 @@ #include "V3Randomize.h" #include "V3String.h" #include "V3Task.h" +#include "V3UniqueNames.h" #include "V3WidthCommit.h" // More code; this file was getting too large; see actions there @@ -208,15 +209,18 @@ class WidthVisitor final : public VNVisitor { using DTypeMap = std::map; // STATE + V3UniqueNames m_insideTempNames; // For generating unique temporary variable names for + // `inside` expressions VMemberMap m_memberMap; // Member names cached for fast lookup V3TaskConnectState m_taskConnectState; // State to cache V3Task::taskConnects WidthVP* m_vup = nullptr; // Current node state bool m_underFork = false; // Visiting under a fork const AstCell* m_cellp = nullptr; // Current cell for arrayed instantiations const AstEnumItem* m_enumItemp = nullptr; // Current enum item - const AstNodeFTask* m_ftaskp = nullptr; // Current function/task + AstNodeFTask* m_ftaskp = nullptr; // Current function/task + AstNodeModule* m_modep = nullptr; // Current module const AstConstraint* m_constraintp = nullptr; // Current constraint - const AstNodeProcedure* m_procedurep = nullptr; // Current final/always + AstNodeProcedure* m_procedurep = nullptr; // Current final/always const AstWith* m_withp = nullptr; // Current 'with' statement const AstFunc* m_funcp = nullptr; // Current function const AstAttrOf* m_attrp = nullptr; // Current attribute @@ -2999,7 +3003,8 @@ class WidthVisitor final : public VNVisitor { AstNodeExpr* newp = nullptr; for (AstDistItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), DistItem)) { - AstNodeExpr* inewp = insideItem(nodep, nodep->exprp(), itemp->rangep()); + AstNodeExpr* inewp + = insideItem(nodep, nodep->exprp()->cloneTreePure(false), itemp->rangep()); if (!inewp) continue; AstNodeExpr* const cmpp = new AstGt{itemp->fileline(), itemp->weightp()->unlinkFrBack(), @@ -3058,15 +3063,47 @@ class WidthVisitor final : public VNVisitor { EXTEND_EXP); } + AstNodeExpr* exprp; + AstExprStmt* exprStmtp = nullptr; + // Skip constraints since those are declarative expressions and they are never actually + // executed so, there is no need for purification since they cannot generate sideeffects. + if (!m_constraintp && !nodep->exprp()->isPure()) { + FileLine* const fl = nodep->exprp()->fileline(); + AstVar* const varp = new AstVar{fl, VVarType::XTEMP, m_insideTempNames.get(nodep), + nodep->exprp()->dtypep()}; + exprp = new AstVarRef{fl, varp, VAccess::READ}; + exprStmtp = new AstExprStmt{fl, + new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, + nodep->exprp()->unlinkFrBack()}, + exprp}; + if (m_ftaskp) { + varp->funcLocal(true); + varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); + m_ftaskp->stmtsp()->addHereThisAsNext(varp); + } else { + m_modep->stmtsp()->addHereThisAsNext(varp); + } + iterate(varp); + iterate(exprStmtp); + } else { + exprp = nodep->exprp(); + } UINFOTREE(9, nodep, "", "inside-in"); // Now rip out the inside and replace with simple math AstNodeExpr* newp = nullptr; for (AstNodeExpr *nextip, *itemp = nodep->itemsp(); itemp; itemp = nextip) { nextip = VN_AS(itemp->nextp(), NodeExpr); // Will be unlinking - AstNodeExpr* const inewp = insideItem(nodep, nodep->exprp(), itemp); + AstNodeExpr* inewp; + if (exprStmtp) { + inewp = insideItem(nodep, exprStmtp, itemp); + exprStmtp = nullptr; + } else { + inewp = insideItem(nodep, exprp->cloneTreePure(false), itemp); + } if (!inewp) continue; newp = newp ? new AstLogOr{nodep->fileline(), newp, inewp} : inewp; } + if (exprStmtp) VL_DO_DANGLING(exprStmtp->deleteTree(), exprStmtp); if (!newp) newp = new AstConst{nodep->fileline(), AstConst::BitFalse{}}; UINFOTREE(9, newp, "", "inside-out"); nodep->replaceWith(newp); @@ -3081,21 +3118,19 @@ class WidthVisitor final : public VNVisitor { } else if (VN_IS(itemDtp, UnpackArrayDType) || VN_IS(itemDtp, DynArrayDType) || VN_IS(itemDtp, QueueDType)) { // Unsupported in parameters - AstNodeExpr* const cexprp = exprp->cloneTreePure(true); AstNodeExpr* const inewp = new AstCMethodHard{nodep->fileline(), itemp->unlinkFrBack(), - VCMethod::ARRAY_INSIDE, cexprp}; - iterateCheckTyped(nodep, "inside value", cexprp, itemDtp->subDTypep(), BOTH); - VL_DANGLING(cexprp); // Might have been replaced + VCMethod::ARRAY_INSIDE, exprp}; + iterateCheckTyped(nodep, "inside value", exprp, itemDtp->subDTypep(), BOTH); inewp->dtypeSetBit(); inewp->didWidth(true); return inewp; } else if (VN_IS(itemDtp, AssocArrayDType)) { nodep->v3error("Inside operator not specified on associative arrays " "(IEEE 1800-2023 11.4.13)"); + VL_DO_DANGLING(exprp->deleteTree(), exprp); return nullptr; } - return AstEqWild::newTyped(itemp->fileline(), exprp->cloneTreePure(true), - itemp->unlinkFrBack()); + return AstEqWild::newTyped(itemp->fileline(), exprp, itemp->unlinkFrBack()); } void visit(AstInsideRange* nodep) override { // Just do each side; AstInside will rip these nodes out later @@ -3183,38 +3218,6 @@ class WidthVisitor final : public VNVisitor { nodep->doingWidth(false); // UINFOTREE(9, nodep, "", "class-out"); } - void visit(AstClass* nodep) override { - if (nodep->didWidthAndSet()) return; - - // If the class is std::process - if (nodep->name() == "process") { - AstPackage* const packagep = getItemPackage(nodep); - if (packagep && packagep->name() == "std") { - // Change type of m_process to VlProcessRef - if (AstVar* const varp - = VN_CAST(m_memberMap.findMember(nodep, "m_process"), Var)) { - AstNodeDType* const dtypep = varp->getChildDTypep(); - if (!varp->dtypep()) { - VL_DO_DANGLING(pushDeletep(dtypep->unlinkFrBack()), dtypep); - } - AstBasicDType* const newdtypep = new AstBasicDType{ - nodep->fileline(), VBasicDTypeKwd::PROCESS_REFERENCE, VSigning::UNSIGNED}; - v3Global.rootp()->typeTablep()->addTypesp(newdtypep); - varp->dtypep(newdtypep); - } - // Mark that self requires process instance - if (AstNodeFTask* const ftaskp - = VN_CAST(m_memberMap.findMember(nodep, "self"), NodeFTask)) { - ftaskp->setNeedProcess(); - } - } - } - - // Must do extends first, as we may in functions under this class - // start following a tree of extends that takes us to other classes - userIterateAndNext(nodep->extendsp(), nullptr); - userIterateChildren(nodep, nullptr); // First size all members - } void visit(AstThisRef* nodep) override { if (nodep->didWidthAndSet()) return; nodep->dtypep(iterateEditMoveDTypep(nodep, nodep->childDTypep())); @@ -6877,6 +6880,49 @@ class WidthVisitor final : public VNVisitor { } userIterateChildren(nodep, nullptr); } + void visitClass(AstClass* nodep) { + if (nodep->didWidthAndSet()) return; + + // If the class is std::process + if (nodep->name() == "process") { + AstPackage* const packagep = getItemPackage(nodep); + if (packagep && packagep->name() == "std") { + // Change type of m_process to VlProcessRef + if (AstVar* const varp + = VN_CAST(m_memberMap.findMember(nodep, "m_process"), Var)) { + AstNodeDType* const dtypep = varp->getChildDTypep(); + if (!varp->dtypep()) { + VL_DO_DANGLING(pushDeletep(dtypep->unlinkFrBack()), dtypep); + } + AstBasicDType* const newdtypep = new AstBasicDType{ + nodep->fileline(), VBasicDTypeKwd::PROCESS_REFERENCE, VSigning::UNSIGNED}; + v3Global.rootp()->typeTablep()->addTypesp(newdtypep); + varp->dtypep(newdtypep); + } + // Mark that self requires process instance + if (AstNodeFTask* const ftaskp + = VN_CAST(m_memberMap.findMember(nodep, "self"), NodeFTask)) { + ftaskp->setNeedProcess(); + } + } + } + + // Must do extends first, as we may in functions under this class + // start following a tree of extends that takes us to other classes + userIterateAndNext(nodep->extendsp(), nullptr); + userIterateChildren(nodep, nullptr); // First size all members + } + void visit(AstNodeModule* nodep) override { + assertAtStatement(nodep); + VL_RESTORER(m_insideTempNames); + if (AstClass* const classp = VN_CAST(nodep, Class)) { + visitClass(classp); + } else { + VL_RESTORER(m_modep); + m_modep = nodep; + userIterateChildren(nodep, nullptr); + } + } void visit(AstNode* nodep) override { // Default: Just iterate UASSERT_OBJ(!m_vup, nodep, @@ -8904,7 +8950,8 @@ public: WidthVisitor(bool paramsOnly, // [in] TRUE if we are considering parameters only. bool doGenerate) // [in] TRUE if we are inside a generate statement and // // don't wish to trigger errors - : m_paramsOnly{paramsOnly} + : m_insideTempNames{"__VInside"} + , m_paramsOnly{paramsOnly} , m_doGenerate{doGenerate} {} AstNode* mainAcceptEdit(AstNode* nodep) { return userIterateSubtreeReturnEdits(nodep, WidthVP{SELF, BOTH}.p()); diff --git a/test_regress/t/t_inside3.py b/test_regress/t/t_inside3.py new file mode 100755 index 000000000..f989a35fb --- /dev/null +++ b/test_regress/t/t_inside3.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_inside3.v b/test_regress/t/t_inside3.v new file mode 100644 index 000000000..c90662d3b --- /dev/null +++ b/test_regress/t/t_inside3.v @@ -0,0 +1,37 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +class Foo; + int callCount = 0; + int value = 6; + function int get(); + callCount += 1; + return value; + endfunction +endclass + +module t; + Foo foo; + Foo array[100]; + Foo res[$]; + initial begin + foo = new; + for (int i = 0; i < 100; ++i) begin + array[i] = new; + end + if (!(foo.get() inside {3,4,5,6,7,8,9})) $stop; + if (foo.callCount != 1) $stop; + if (!(foo.get() inside {[3:9]})) $stop; + if (foo.callCount != 2) $stop; + res = array.find(x) with (x.get() inside {5,7,8,9}); + if (res.size() != 0) $stop; + for (int i = 0; i < 100; ++i) begin + if (array[i].callCount != 1) $stop; + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule