diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d03324eed..8393ec3f8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,6 +132,7 @@ set(HEADERS V3LinkLevel.h V3LinkParse.h V3LinkResolve.h + V3LinkWith.h V3List.h V3Localize.h V3MemberMap.h @@ -306,6 +307,7 @@ set(COMMON_SOURCES V3LinkLevel.cpp V3LinkParse.cpp V3LinkResolve.cpp + V3LinkWith.cpp V3Localize.cpp V3MergeCond.cpp V3Name.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 855e0835f..00ae0273c 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -300,6 +300,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3LinkLevel.o \ V3LinkParse.o \ V3LinkResolve.o \ + V3LinkWith.o \ V3Localize.o \ V3MergeCond.o \ V3Name.o \ diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 36c7136c2..1014e6332 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -918,6 +918,17 @@ class LinkParseVisitor final : public VNVisitor { cleanFileline(nodep); iterateChildren(nodep); } + void visit(AstCaseItem* nodep) override { + // Move default caseItems to the bottom of the list + // That saves us from having to search each case list twice, for non-defaults and defaults + iterateChildren(nodep); + if (!nodep->user2() && nodep->isDefault() && nodep->nextp()) { + nodep->user2(true); + AstNode* const nextp = nodep->nextp(); + nodep->unlinkFrBack(); + nextp->addNext(nodep); + } + } void visit(AstDot* nodep) override { cleanFileline(nodep); iterateChildren(nodep); diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp index 3690fdb39..110203633 100644 --- a/src/V3LinkResolve.cpp +++ b/src/V3LinkResolve.cpp @@ -47,19 +47,16 @@ class LinkResolveVisitor final : public VNVisitor { // Below state needs to be preserved between each module call. AstNodeModule* m_modp = nullptr; // Current module AstClass* m_classp = nullptr; // Class we're inside - string m_randcIllegalWhy; // Why randc illegal - AstNode* m_randcIllegalp = nullptr; // Node causing randc illegal AstNodeFTask* m_ftaskp = nullptr; // Function or task we're inside int m_senitemCvtNum = 0; // Temporary signal counter std::deque m_underGenFors; // Stack of GenFor underneath bool m_underGenerate = false; // Under GenFor/GenIf - AstNodeExpr* m_currentRandomizeSelectp = nullptr; // fromp() of current `randomize()` call - bool m_inRandomizeWith = false; // If in randomize() with (and no other with afterwards) // VISITORS // TODO: Most of these visitors are here for historical reasons. // TODO: ExpectDescriptor can move to data type resolution, and the rest // TODO: could move to V3LinkParse to get them out of the way of elaboration + // TODO: Some also can move to V3LinkWidth, to happen once post-LinkDot void visit(AstNodeModule* nodep) override { // Module: Create sim table for entire module and iterate UINFO(8, "MODULE " << nodep); @@ -80,39 +77,6 @@ class LinkResolveVisitor final : public VNVisitor { } iterateChildren(nodep); } - void visit(AstConstraint* nodep) override { - // V3LinkDot moved the isExternDef into the class, the extern proto was - // checked to exist, and now isn't needed - nodep->isExternDef(false); - if (nodep->isExternProto()) { - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - return; - } - iterateChildren(nodep); - } - void visit(AstConstraintBefore* nodep) override { - VL_RESTORER(m_randcIllegalWhy); - VL_RESTORER(m_randcIllegalp); - m_randcIllegalWhy = "'solve before' (IEEE 1800-2023 18.5.9)"; - m_randcIllegalp = nodep; - iterateChildrenConst(nodep); - } - void visit(AstDist* nodep) override { - VL_RESTORER(m_randcIllegalWhy); - VL_RESTORER(m_randcIllegalp); - m_randcIllegalWhy = "'constraint dist' (IEEE 1800-2023 18.5.3)"; - m_randcIllegalp = nodep; - iterateChildrenConst(nodep); - } - void visit(AstConstraintExpr* nodep) override { - VL_RESTORER(m_randcIllegalWhy); - VL_RESTORER(m_randcIllegalp); - if (nodep->isSoft()) { - m_randcIllegalWhy = "'constraint soft' (IEEE 1800-2023 18.5.13.1)"; - m_randcIllegalp = nodep; - } - iterateChildrenConst(nodep); - } void visit(AstInitialAutomatic* nodep) override { iterateChildren(nodep); @@ -151,14 +115,6 @@ class LinkResolveVisitor final : public VNVisitor { << nodep->prettyNameQ() << " used outside generate for loop (IEEE 1800-2023 27.4)"); } - if (nodep->varp()->isRandC() && m_randcIllegalp) { - nodep->v3error("Randc variables not allowed in " - << m_randcIllegalWhy << '\n' - << nodep->warnContextPrimary() << '\n' - << m_randcIllegalp->warnOther() - << "... Location of restricting expression\n" - << m_randcIllegalp->warnContextSecondary()); - } } iterateChildren(nodep); } @@ -196,22 +152,6 @@ class LinkResolveVisitor final : public VNVisitor { if (nodep->dpiExport()) nodep->scopeNamep(new AstScopeName{nodep->fileline(), false}); } void visit(AstNodeFTaskRef* nodep) override { - VL_RESTORER(m_currentRandomizeSelectp); - if (nodep->taskp()) { - if (AstSequence* const seqp = VN_CAST(nodep->taskp(), Sequence)) - seqp->isReferenced(true); - } - - if (nodep->name() == "randomize") { - if (const AstMethodCall* const methodcallp = VN_CAST(nodep, MethodCall)) { - if (m_inRandomizeWith) { - nodep->v3warn( - E_UNSUPPORTED, - "Unsupported: randomize() nested in inline randomize() constraints"); - } - m_currentRandomizeSelectp = methodcallp->fromp(); - } - } iterateChildren(nodep); if (AstLet* letp = VN_CAST(nodep->taskp(), Let)) { UINFO(7, "letSubstitute() " << nodep << " <- " << letp); @@ -260,18 +200,6 @@ class LinkResolveVisitor final : public VNVisitor { } } - void visit(AstCaseItem* nodep) override { - // Move default caseItems to the bottom of the list - // That saves us from having to search each case list twice, for non-defaults and defaults - iterateChildren(nodep); - if (!nodep->user2() && nodep->isDefault() && nodep->nextp()) { - nodep->user2(true); - AstNode* const nextp = nodep->nextp(); - nodep->unlinkFrBack(); - nextp->addNext(nodep); - } - } - void visit(AstLet* nodep) override { // Lets have been (or about to be) substituted, we can remove nodep->unlinkFrBack(); @@ -528,30 +456,6 @@ class LinkResolveVisitor final : public VNVisitor { iterateChildren(nodep); } - void visit(AstMemberSel* nodep) override { - if (m_inRandomizeWith && nodep->fromp()->isSame(m_currentRandomizeSelectp)) { - // Replace member selects to the element - // on which the randomize() is called with LambdaArgRef - // This allows V3Randomize to work properly when - // constrained variables are referred using that object - AstNodeExpr* const prevFromp = nodep->fromp(); - prevFromp->replaceWith( - new AstLambdaArgRef{prevFromp->fileline(), prevFromp->name(), false}); - pushDeletep(prevFromp); - } - iterateChildren(nodep); - } - - void visit(AstWith* nodep) override { - VL_RESTORER(m_inRandomizeWith); - if (const AstMethodCall* const methodCallp = VN_CAST(nodep->backp(), MethodCall)) { - m_inRandomizeWith = methodCallp->name() == "randomize"; - } else { - m_inRandomizeWith = false; - } - iterateChildren(nodep); - } - void visit(AstNode* nodep) override { iterateChildren(nodep); } public: @@ -594,7 +498,7 @@ public: }; //###################################################################### -// Link class functions +// V3LinkResolve class functions void V3LinkResolve::linkResolve(AstNetlist* rootp) { UINFO(4, __FUNCTION__ << ": "); diff --git a/src/V3LinkWith.cpp b/src/V3LinkWith.cpp new file mode 100644 index 000000000..e83536de8 --- /dev/null +++ b/src/V3LinkWith.cpp @@ -0,0 +1,149 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Resolve module/signal name references +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// 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-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* +// LinkResolve TRANSFORMATIONS: +// Top-down traversal +// With vars: Fixup LambdaRefs +//************************************************************************* + +#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT + +#include "V3LinkWith.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +//###################################################################### +// Link state, as a visitor of each AstNode + +class LinkWithVisitor final : public VNVisitor { + // NODE STATE + + // STATE + // Below state needs to be preserved between each module call. + string m_randcIllegalWhy; // Why randc illegal + AstNode* m_randcIllegalp = nullptr; // Node causing randc illegal + AstNodeExpr* m_currentRandomizeSelectp = nullptr; // fromp() of current `randomize()` call + bool m_inRandomizeWith = false; // If in randomize() with (and no other with afterwards) + + // VISITORS + void visit(AstConstraint* nodep) override { + // V3LinkDot moved the isExternDef into the class, the extern proto was + // checked to exist, and now isn't needed + nodep->isExternDef(false); + if (nodep->isExternProto()) { + VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); + return; + } + iterateChildren(nodep); + } + void visit(AstConstraintBefore* nodep) override { + VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalp); + m_randcIllegalWhy = "'solve before' (IEEE 1800-2023 18.5.9)"; + m_randcIllegalp = nodep; + iterateChildrenConst(nodep); + } + void visit(AstDist* nodep) override { + VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalp); + m_randcIllegalWhy = "'constraint dist' (IEEE 1800-2023 18.5.3)"; + m_randcIllegalp = nodep; + iterateChildrenConst(nodep); + } + void visit(AstConstraintExpr* nodep) override { + VL_RESTORER(m_randcIllegalWhy); + VL_RESTORER(m_randcIllegalp); + if (nodep->isSoft()) { + m_randcIllegalWhy = "'constraint soft' (IEEE 1800-2023 18.5.13.1)"; + m_randcIllegalp = nodep; + } + iterateChildrenConst(nodep); + } + + void visit(AstNodeVarRef* nodep) override { + if (nodep->varp()) { // Else due to dead code, might not have var pointer + if (nodep->varp()->isRandC() && m_randcIllegalp) { + nodep->v3error("Randc variables not allowed in " + << m_randcIllegalWhy << '\n' + << nodep->warnContextPrimary() << '\n' + << m_randcIllegalp->warnOther() + << "... Location of restricting expression\n" + << m_randcIllegalp->warnContextSecondary()); + } + } + iterateChildren(nodep); + } + + void visit(AstNodeFTaskRef* nodep) override { + VL_RESTORER(m_currentRandomizeSelectp); + if (nodep->taskp()) { + if (AstSequence* const seqp = VN_CAST(nodep->taskp(), Sequence)) + seqp->isReferenced(true); + } + + if (nodep->name() == "randomize") { + if (const AstMethodCall* const methodcallp = VN_CAST(nodep, MethodCall)) { + if (m_inRandomizeWith) { + nodep->v3warn( + E_UNSUPPORTED, + "Unsupported: randomize() nested in inline randomize() constraints"); + } + m_currentRandomizeSelectp = methodcallp->fromp(); + } + } + iterateChildren(nodep); + } + + void visit(AstMemberSel* nodep) override { + if (m_inRandomizeWith && nodep->fromp()->isSame(m_currentRandomizeSelectp)) { + // Replace member selects to the element + // on which the randomize() is called with LambdaArgRef + // This allows V3Randomize to work properly when + // constrained variables are referred using that object + AstNodeExpr* const prevFromp = nodep->fromp(); + AstNodeExpr* const newp + = new AstLambdaArgRef{prevFromp->fileline(), prevFromp->name(), false}; + prevFromp->replaceWith(newp); + pushDeletep(prevFromp); + } + iterateChildren(nodep); + } + + void visit(AstWith* nodep) override { + VL_RESTORER(m_inRandomizeWith); + if (const AstMethodCall* const methodCallp = VN_CAST(nodep->backp(), MethodCall)) { + m_inRandomizeWith = methodCallp->name() == "randomize"; + } else { + m_inRandomizeWith = false; + } + iterateChildren(nodep); + } + + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + explicit LinkWithVisitor(AstNetlist* rootp) { iterate(rootp); } + ~LinkWithVisitor() override = default; +}; + +//###################################################################### +// V3LinkWith class functions + +void V3LinkWith::linkWith(AstNetlist* rootp) { + UINFO(4, __FUNCTION__ << ": "); + { const LinkWithVisitor visitor{rootp}; } // Destruct before checking + V3Global::dumpCheckGlobalTree("linkwith", 0, dumpTreeEitherLevel() >= 6); +} diff --git a/src/V3LinkWith.h b/src/V3LinkWith.h new file mode 100644 index 000000000..3315e8614 --- /dev/null +++ b/src/V3LinkWith.h @@ -0,0 +1,32 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Link modules/signals together +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// 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-FileCopyrightText: 2003-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//************************************************************************* + +#ifndef VERILATOR_V3LINKWITH_H_ +#define VERILATOR_V3LINKWITH_H_ + +#include "config_build.h" +#include "verilatedos.h" + +class AstNetlist; + +//============================================================================ + +class V3LinkWith final { +public: + static void linkWith(AstNetlist* rootp) VL_MT_DISABLED; +}; + +#endif // Guard diff --git a/src/Verilator.cpp b/src/Verilator.cpp index f0f21eaab..f0b4d714b 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -76,6 +76,7 @@ #include "V3LinkLevel.h" #include "V3LinkParse.h" #include "V3LinkResolve.h" +#include "V3LinkWith.h" #include "V3Localize.h" #include "V3MergeCond.h" #include "V3Name.h" @@ -182,6 +183,11 @@ static void process() { V3LinkDot::linkDotParamed(v3Global.rootp()); // Cleanup as made new modules V3LinkLValue::linkLValue(v3Global.rootp()); // Resolve new VarRefs + + // Link cleanup of 'with' as final link phase before V3Width + // (called only once, not when width a single params) + V3LinkWith::linkWith(v3Global.rootp()); + V3Error::abortIfErrors(); // Fix any remaining cross-interface refs created during V3Width::widthParamsEdit diff --git a/test_regress/t/t_constraint_before_randc_bad.out b/test_regress/t/t_constraint_before_randc_bad.out index 54ab30db7..56f77b430 100644 --- a/test_regress/t/t_constraint_before_randc_bad.out +++ b/test_regress/t/t_constraint_before_randc_bad.out @@ -1,4 +1,5 @@ %Error: t/t_constraint_before_randc_bad.v:11:42: Randc variables not allowed in 'solve before' (IEEE 1800-2023 18.5.9) + : ... note: In instance 't' 11 | constraint raint2_bad {solve b1 before b2;} | ^~ t/t_constraint_before_randc_bad.v:11:26: ... Location of restricting expression diff --git a/test_regress/t/t_constraint_dist_randc_bad.out b/test_regress/t/t_constraint_dist_randc_bad.out index a4afdd756..c9acd0cbb 100644 --- a/test_regress/t/t_constraint_dist_randc_bad.out +++ b/test_regress/t/t_constraint_dist_randc_bad.out @@ -1,4 +1,5 @@ %Error: t/t_constraint_dist_randc_bad.v:10:22: Randc variables not allowed in 'constraint dist' (IEEE 1800-2023 18.5.3) + : ... note: In instance 't' 10 | constraint c_bad { rc dist {3 := 0, 10 := 5}; } | ^~ t/t_constraint_dist_randc_bad.v:10:25: ... Location of restricting expression diff --git a/test_regress/t/t_constraint_soft_randc_bad.out b/test_regress/t/t_constraint_soft_randc_bad.out index 5a20ca3cd..470e0e1f3 100644 --- a/test_regress/t/t_constraint_soft_randc_bad.out +++ b/test_regress/t/t_constraint_soft_randc_bad.out @@ -1,4 +1,5 @@ %Error: t/t_constraint_soft_randc_bad.v:10:27: Randc variables not allowed in 'constraint soft' (IEEE 1800-2023 18.5.13.1) + : ... note: In instance 't' 10 | constraint c_bad { soft rc > 4; } | ^~ t/t_constraint_soft_randc_bad.v:10:22: ... Location of restricting expression diff --git a/test_regress/t/t_randomize_nested_unsup.out b/test_regress/t/t_randomize_nested_unsup.out index f8d432abe..90b392e1e 100644 --- a/test_regress/t/t_randomize_nested_unsup.out +++ b/test_regress/t/t_randomize_nested_unsup.out @@ -1,4 +1,5 @@ %Error-UNSUPPORTED: t/t_randomize_nested_unsup.v:17:41: Unsupported: randomize() nested in inline randomize() constraints + : ... note: In instance 't' 17 | if (a.randomize() with {rdata == aa.randomize();} == 0) $stop; | ^~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest