diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 608cd2462..2cc6a0166 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,6 +142,7 @@ set(HEADERS V3Randomize.h V3Reloop.h V3Rtti.h + V3Sampled.h V3Sched.h V3Scope.h V3Scoreboard.h @@ -288,6 +289,7 @@ set(COMMON_SOURCES V3ProtectLib.cpp V3Randomize.cpp V3Reloop.cpp + V3Sampled.cpp V3Sched.cpp V3SchedAcyclic.cpp V3SchedPartition.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 30dd7dad1..a524c16a4 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -284,6 +284,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3ProtectLib.o \ V3Randomize.o \ V3Reloop.o \ + V3Sampled.o \ V3Sched.o \ V3SchedAcyclic.o \ V3SchedPartition.o \ diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index dff5f0f2d..bd014c5f9 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -440,6 +440,7 @@ class AssertVisitor final : public VNVisitor { AstSampled* const newp = newSampledExpr(nodep); relinkHandle.relink(newp); newp->user1(1); + v3Global.setHasSampled(); } } } diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index 567581eef..7632eea49 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -357,6 +357,26 @@ private: nodep->user1(true); } } + void visit(AstMemberSel* nodep) override { + if (AstClockingItem* const itemp = VN_CAST( + nodep->varp()->user1p() ? nodep->varp()->user1p() : nodep->varp()->firstAbovep(), + ClockingItem)) { + if (nodep->access().isReadOrRW() && itemp->direction() == VDirection::OUTPUT) { + nodep->v3error("Cannot read from output clockvar (IEEE 1800-2023 14.3)"); + } + if (nodep->access().isWriteOrRW()) { + if (itemp->direction() == VDirection::OUTPUT) { + if (!m_inAssignDlyLhs) { + nodep->v3error("Only non-blocking assignments can write " + "to clockvars (IEEE 1800-2023 14.16)"); + } + if (m_inAssign) m_inSynchDrive = true; + } else if (itemp->direction() == VDirection::INPUT) { + nodep->v3error("Cannot write to input clockvar (IEEE 1800-2023 14.3)"); + } + } + } + } void visit(AstNodeAssign* nodep) override { if (nodep->user1()) return; VL_RESTORER(m_inAssign); diff --git a/src/V3Clock.cpp b/src/V3Clock.cpp index 6b2c5004f..0c67de410 100644 --- a/src/V3Clock.cpp +++ b/src/V3Clock.cpp @@ -68,14 +68,10 @@ public: class ClockVisitor final : public VNVisitor { // NODE STATE - // ??? - const VNUser1InUse m_user1InUse; // STATE AstCFunc* m_evalp = nullptr; // The '_eval' function - AstScope* m_scopep = nullptr; // Current scope AstSenTree* m_lastSenp = nullptr; // Last sensitivity match, so we can detect duplicates. AstIf* m_lastIfp = nullptr; // Last sensitivity if active to add more under - bool m_inSampled = false; // True inside a sampled expression // METHODS @@ -88,24 +84,6 @@ class ClockVisitor final : public VNVisitor { } return senEqnp; } - AstVarScope* createSampledVar(AstVarScope* vscp) { - if (vscp->user1p()) return VN_AS(vscp->user1p(), VarScope); - const AstVar* const varp = vscp->varp(); - const string newvarname - = string{"__Vsampled__"} + vscp->scopep()->nameDotless() + "__" + varp->name(); - FileLine* const flp = vscp->fileline(); - AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()}; - m_scopep->modp()->addStmtsp(newvarp); - AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; - vscp->user1p(newvscp); - m_scopep->addVarsp(newvscp); - // At the top of _eval, assign them - AstAssign* const finalp = new AstAssign{flp, new AstVarRef{flp, newvscp, VAccess::WRITE}, - new AstVarRef{flp, vscp, VAccess::READ}}; - m_evalp->addInitsp(finalp); - UINFO(4, "New Sampled: " << newvscp << endl); - return newvscp; - } AstIf* makeActiveIf(AstSenTree* sensesp) { AstNodeExpr* const senEqnp = createSenseEquation(sensesp->sensesp()); UASSERT_OBJ(senEqnp, sensesp, "No sense equation, shouldn't be in sequent activation."); @@ -179,33 +157,15 @@ class ClockVisitor final : public VNVisitor { clearLastSen(); } - //========== Create sampled values - void visit(AstScope* nodep) override { - VL_RESTORER(m_scopep); - { - m_scopep = nodep; - iterateChildren(nodep); - } - } - void visit(AstSampled* nodep) override { - VL_RESTORER(m_inSampled); - { - m_inSampled = true; - iterateChildren(nodep); - nodep->replaceWith(nodep->exprp()->unlinkFrBack()); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } - } - void visit(AstVarRef* nodep) override { - iterateChildren(nodep); - if (m_inSampled && !nodep->user1SetOnce()) { - UASSERT_OBJ(nodep->access().isReadOnly(), nodep, "Should have failed in V3Access"); - AstVarScope* const varscp = nodep->varScopep(); - AstVarScope* const lastscp = createSampledVar(varscp); - AstNode* const newp = new AstVarRef{nodep->fileline(), lastscp, VAccess::READ}; - newp->user1SetOnce(); // Don't sample this one - nodep->replaceWith(newp); - VL_DO_DANGLING(pushDeletep(nodep), nodep); + //========== Move sampled assignments + void visit(AstVarScope* nodep) override { + AstVar* varp = nodep->varp(); + if (varp->valuep() && varp->name().substr(0, strlen("__Vsampled")) == "__Vsampled") { + m_evalp->addInitsp(new AstAssign{ + nodep->fileline(), new AstVarRef{nodep->fileline(), nodep, VAccess::WRITE}, + VN_AS(varp->valuep()->unlinkFrBack(), NodeExpr)}); + varp->direction(VDirection::NONE); // Restore defaults + varp->primaryIO(false); } } diff --git a/src/V3Global.h b/src/V3Global.h index 5d6614df8..0826bf994 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -114,6 +114,7 @@ class V3Global final { bool m_dpi = false; // Need __Dpi include files bool m_hasEvents = false; // Design uses SystemVerilog named events bool m_hasClasses = false; // Design uses SystemVerilog classes + bool m_hasSampled = false; // Design uses SAMPLED expresions bool m_hasVirtIfaces = false; // Design uses virtual interfaces bool m_usesProbDist = false; // Uses $dist_* bool m_usesStdPackage = false; // Design uses the std package @@ -168,6 +169,8 @@ public: void setHasEvents() { m_hasEvents = true; } bool hasClasses() const { return m_hasClasses; } void setHasClasses() { m_hasClasses = true; } + bool hasSampled() const { return m_hasSampled; } + void setHasSampled() { m_hasSampled = true; } bool hasVirtIfaces() const { return m_hasVirtIfaces; } void setHasVirtIfaces() { m_hasVirtIfaces = true; } bool usesProbDist() const { return m_usesProbDist; } diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index b3d2f1302..1bcada129 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -2209,9 +2209,10 @@ class LinkDotResolveVisitor final : public VNVisitor { new AstVarRef{clockingp->fileline(), eventp, VAccess::WRITE}, false}}); v3Global.setHasEvents(); - eventp->user1p(new VSymEnt{m_statep->symsp(), eventp}); } - return reinterpret_cast(clockingp->eventp()->user1p()); + AstVar* const eventp = clockingp->eventp(); + if (!eventp->user1p()) eventp->user1p(new VSymEnt{m_statep->symsp(), eventp}); + return reinterpret_cast(eventp->user1p()); } bool isParamedClassRefDType(const AstNode* classp) { @@ -3045,7 +3046,12 @@ class LinkDotResolveVisitor final : public VNVisitor { dotSymp = m_statep->findDotted(nodep->fileline(), dotSymp, nodep->dotted(), baddot, okSymp); // Maybe nullptr if (!m_statep->forScopeCreation()) { - VSymEnt* const foundp = m_statep->findSymPrefixed(dotSymp, nodep->name(), baddot); + VSymEnt* foundp = m_statep->findSymPrefixed(dotSymp, nodep->name(), baddot); + if (m_inSens && foundp) { + if (AstClocking* const clockingp = VN_CAST(foundp->nodep(), Clocking)) { + foundp = getCreateClockingEventSymEnt(clockingp); + } + } AstVar* const varp = foundp ? foundToVarp(foundp, nodep, nodep->access()) : nullptr; nodep->varp(varp); @@ -3062,11 +3068,14 @@ class LinkDotResolveVisitor final : public VNVisitor { // AstVarXRef's even though they are in the same module detect // this and convert to normal VarRefs if (!m_statep->forPrearray() && !m_statep->forScopeCreation()) { - if (VN_IS(nodep->dtypep(), IfaceRefDType)) { - AstVarRef* const newrefp - = new AstVarRef{nodep->fileline(), nodep->varp(), nodep->access()}; - nodep->replaceWith(newrefp); - VL_DO_DANGLING(nodep->deleteTree(), nodep); + if (const AstIfaceRefDType* const ifaceDtp + = VN_CAST(nodep->dtypep(), IfaceRefDType)) { + if (!ifaceDtp->isVirtual()) { + AstVarRef* const newrefp + = new AstVarRef{nodep->fileline(), nodep->varp(), nodep->access()}; + nodep->replaceWith(newrefp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } } } } else { diff --git a/src/V3LinkLValue.cpp b/src/V3LinkLValue.cpp index 72a533183..b13d022a0 100644 --- a/src/V3LinkLValue.cpp +++ b/src/V3LinkLValue.cpp @@ -50,7 +50,8 @@ class LinkLValueVisitor final : public VNVisitor { // so it is needed to check only if m_setContinuously is true if (m_setStrengthSpecified) nodep->varp()->hasStrengthAssignment(true); } - if (const AstClockingItem* itemp = VN_CAST(nodep->varp()->backp(), ClockingItem)) { + if (const AstClockingItem* const itemp + = VN_CAST(nodep->varp()->backp(), ClockingItem)) { UINFO(5, "ClkOut " << nodep << endl); if (itemp->outputp()) nodep->varp(itemp->outputp()->varp()); } @@ -285,6 +286,13 @@ class LinkLValueVisitor final : public VNVisitor { void visit(AstMemberSel* nodep) override { if (m_setRefLvalue != VAccess::NOCHANGE) { nodep->access(m_setRefLvalue); + if (nodep->varp() && nodep->access().isWriteOrRW()) { + if (const AstClockingItem* const itemp + = VN_CAST(nodep->varp()->backp(), ClockingItem)) { + UINFO(5, "ClkOut " << nodep << endl); + if (itemp->outputp()) nodep->varp(itemp->outputp()->varp()); + } + } } else { // It is the only place where the access is set to member select nodes. // If it doesn't have to be set to WRITE, it means that it is READ. diff --git a/src/V3OrderProcessDomains.cpp b/src/V3OrderProcessDomains.cpp index 9e4186e23..b89c1872f 100644 --- a/src/V3OrderProcessDomains.cpp +++ b/src/V3OrderProcessDomains.cpp @@ -58,6 +58,15 @@ class V3OrderProcessDomains final { // METHODS + // Dump a domain for debugging + std::string debugDomain(AstSenTree* domainp) { + return cvtToHex(domainp) + + (domainp == m_deleteDomainp ? " [DEL]" + : domainp->hasCombo() ? " [COMB]" + : domainp->isMulti() ? " [MULT]" + : ""); + } + // Make a domain that merges the two domains AstSenTree* combineDomains(AstSenTree* ap, AstSenTree* bp) { if (ap == bp) return ap; @@ -93,6 +102,7 @@ class V3OrderProcessDomains final { // For logic, start with the explicit hybrid sensitivities OrderLogicVertex* const lvtxp = vtxp->cast(); if (lvtxp) domainp = lvtxp->hybridp(); + if (domainp) UINFO(6, " hybr d=" << debugDomain(domainp) << " " << vtxp << endl); // For each incoming edge, examine the source vertex for (V3GraphEdge& edge : vtxp->inEdges()) { @@ -104,6 +114,7 @@ class V3OrderProcessDomains final { AstSenTree* fromDomainp = fromVtxp->domainp(); + UINFO(6, " from d=" << debugDomain(fromDomainp) << " " << fromVtxp << endl); UASSERT(fromDomainp == m_deleteDomainp || !fromDomainp->hasCombo(), "There should be no need for combinational domains"); @@ -113,6 +124,8 @@ class V3OrderProcessDomains final { externalDomainps.clear(); m_externalDomains(vscp, externalDomainps); for (AstSenTree* const externalDomainp : externalDomainps) { + UINFO(6, " xtrn d=" << debugDomain(externalDomainp) << " " << fromVtxp + << " because of " << vscp << endl); UASSERT_OBJ(!externalDomainp->hasCombo(), vscp, "There should be no need for combinational domains"); fromDomainp = combineDomains(fromDomainp, externalDomainp); @@ -140,12 +153,7 @@ class V3OrderProcessDomains final { // Set the domain of the vertex vtxp->domainp(domainp); - UINFO(5, " done d=" << cvtToHex(domainp) - << (domainp == m_deleteDomainp ? " [DEL]" - : domainp->hasCombo() ? " [COMB]" - : domainp->isMulti() ? " [MULT]" - : "") - << " " << vtxp << endl); + UINFO(5, " done d=" << debugDomain(domainp) << " " << vtxp << endl); } } diff --git a/src/V3OrderSerial.cpp b/src/V3OrderSerial.cpp index f556e51f5..1627b78f7 100644 --- a/src/V3OrderSerial.cpp +++ b/src/V3OrderSerial.cpp @@ -34,7 +34,7 @@ VL_DEFINE_DEBUG_FUNCTIONS; std::vector V3Order::createSerial(OrderGraph& graph, const std::string& tag, const TrigToSenMap& trigToSen, bool slow) { - UINFO(2, " Constructing serial code for '" + tag + "'"); + UINFO(2, " Constructing serial code for '" + tag + "'\n"); // Build the move graph OrderMoveDomScope::clear(); diff --git a/src/V3Sampled.cpp b/src/V3Sampled.cpp new file mode 100644 index 000000000..e933ba869 --- /dev/null +++ b/src/V3Sampled.cpp @@ -0,0 +1,105 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Removal of SAMPLED +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2024 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 +// +//************************************************************************* +// V3Sampled's Transformations: +// +// Top Scope: +// Replace each variable reference under SAMPLED with a new variable. +// Remove SAMPLED. +// +//************************************************************************* + +#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT + +#include "V3Sampled.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +//###################################################################### +// Clock state, as a visitor of each AstNode + +class SampledVisitor final : public VNVisitor { + // NODE STATE + // AstVarScope::user1() -> AstVarScope*. The VarScope that stores sampled value + // AstVarRef::user1() -> bool. Whether already converted + const VNUser1InUse m_user1InUse; + // STATE + AstScope* m_scopep = nullptr; // Current scope + bool m_inSampled = false; // True inside a sampled expression + + // METHODS + + AstVarScope* createSampledVar(AstVarScope* vscp) { + if (vscp->user1p()) return VN_AS(vscp->user1p(), VarScope); + const AstVar* const varp = vscp->varp(); + const string newvarname + = "__Vsampled_" + vscp->scopep()->nameDotless() + "__" + varp->name(); + FileLine* const flp = vscp->fileline(); + AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()}; + m_scopep->modp()->addStmtsp(newvarp); + AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; + newvarp->direction(VDirection::INPUT); // Inform V3Sched that it will be driven later + newvarp->primaryIO(true); + vscp->user1p(newvscp); + m_scopep->addVarsp(newvscp); + // At the top of _eval, assign them (use valuep here as temporary storage during V3Sched) + newvarp->valuep(new AstVarRef{flp, vscp, VAccess::READ}); + UINFO(4, "New Sampled: " << newvscp << endl); + return newvscp; + } + + // VISITORS + void visit(AstScope* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + iterateChildren(nodep); + } + void visit(AstSampled* nodep) override { + VL_RESTORER(m_inSampled); + m_inSampled = true; + iterateChildren(nodep); + nodep->replaceWith(nodep->exprp()->unlinkFrBack()); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + void visit(AstVarRef* nodep) override { + iterateChildren(nodep); + if (m_inSampled && !nodep->user1SetOnce()) { + UASSERT_OBJ(nodep->access().isReadOnly(), nodep, "Should have failed in V3Access"); + AstVarScope* const varscp = nodep->varScopep(); + AstVarScope* const lastscp = createSampledVar(varscp); + AstNode* const newp = new AstVarRef{nodep->fileline(), lastscp, VAccess::READ}; + newp->user1SetOnce(); // Don't sample this one + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + } + + //-------------------- + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + explicit SampledVisitor(AstNetlist* netlistp) { iterate(netlistp); } + ~SampledVisitor() override = default; +}; + +//###################################################################### +// Sampled class functions + +void V3Sampled::sampledAll(AstNetlist* nodep) { + UINFO(2, __FUNCTION__ << ": " << endl); + { SampledVisitor{nodep}; } // Destruct before checking + V3Global::dumpCheckGlobalTree("sampled", 0, dumpTreeEitherLevel() >= 3); +} diff --git a/src/V3Sampled.h b/src/V3Sampled.h new file mode 100644 index 000000000..30d5e94a4 --- /dev/null +++ b/src/V3Sampled.h @@ -0,0 +1,34 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Removal of SAMPLED +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2024 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 +// +//************************************************************************* + +#ifndef VERILATOR_V3SAMPLED_H_ +#define VERILATOR_V3SAMPLED_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3ThreadSafety.h" + +class AstNetlist; + +//============================================================================ + +class V3Sampled final { +public: + static void sampledAll(AstNetlist* nodep) VL_MT_DISABLED; +}; + +#endif // Guard diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index b00c68d34..931f5b987 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -1170,10 +1170,14 @@ void schedule(AstNetlist* netlistp) { // (including combinationally generated signals) are computed within the act region. LogicRegions logicRegions = partition(logicClasses.m_clocked, logicClasses.m_comb, logicClasses.m_hybrid); + logicRegions.m_obs = logicClasses.m_observed; + logicRegions.m_react = logicClasses.m_reactive; if (v3Global.opt.stats()) { addSizeStat("size of region: Active Pre", logicRegions.m_pre); addSizeStat("size of region: Active", logicRegions.m_act); addSizeStat("size of region: NBA", logicRegions.m_nba); + addSizeStat("size of region: Observed", logicRegions.m_obs); + addSizeStat("size of region: Reactive", logicRegions.m_react); V3Stats::statsStage("sched-partition"); } @@ -1183,6 +1187,8 @@ void schedule(AstNetlist* netlistp) { addSizeStat("size of replicated logic: Input", logicReplicas.m_ico); addSizeStat("size of replicated logic: Active", logicReplicas.m_act); addSizeStat("size of replicated logic: NBA", logicReplicas.m_nba); + addSizeStat("size of replicated logic: Observed", logicReplicas.m_obs); + addSizeStat("size of replicated logic: Reactive", logicReplicas.m_react); V3Stats::statsStage("sched-replicate"); } @@ -1207,8 +1213,8 @@ void schedule(AstNetlist* netlistp) { const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre, // &logicRegions.m_act, // &logicRegions.m_nba, // - &logicClasses.m_observed, // - &logicClasses.m_reactive, // + &logicRegions.m_obs, // + &logicRegions.m_react, // &timingKit.m_lbs}); const TriggerKit& actTrig = createTriggers(netlistp, initp, senExprBuilder, senTreeps, "act", extraTriggers); @@ -1225,7 +1231,7 @@ void schedule(AstNetlist* netlistp) { AstVarScope* const preTrigVscp = scopeTopp->createTempLike("__VpreTriggered", actTrigVscp); const auto cloneMapWithNewTriggerReferences - = [=](std::unordered_map map, AstVarScope* vscp) { + = [=](const std::unordered_map& map, AstVarScope* vscp) { // Copy map auto newMap{map}; // Replace references in each mapped value with a reference to the given vscp @@ -1293,6 +1299,7 @@ void schedule(AstNetlist* netlistp) { // Orders a region's logic and creates the region eval function const auto order = [&](const std::string& name, const std::vector& logic) -> EvalKit { + UINFO(2, "Scheduling " << name << " #logic = " << logic.size() << endl); AstVarScope* const trigVscp = scopeTopp->createTempLike("__V" + name + "Triggered", actTrigVscp); const auto trigMap = cloneMapWithNewTriggerReferences(actTrigMap, trigVscp); @@ -1350,18 +1357,21 @@ void schedule(AstNetlist* netlistp) { // Orders a region's logic and creates the region eval function (only if there is any logic in // the region) - const auto orderIfNonEmpty = [&](const std::string& name, LogicByScope& lbs) -> EvalKit { - if (lbs.empty()) return {}; - const auto& kit = order(name, {&lbs}); + const auto orderIfNonEmpty + = [&](const std::string& name, const std::vector& logic) -> EvalKit { + if (logic[0]->empty()) + return {}; // if region is empty, replica is supposed to be empty as well + const auto& kit = order(name, logic); if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-" + name); return kit; }; // Step 11: Create the 'obs' region evaluation function - const EvalKit& obsKit = orderIfNonEmpty("obs", logicClasses.m_observed); + const EvalKit& obsKit = orderIfNonEmpty("obs", {&logicRegions.m_obs, &logicReplicas.m_obs}); // Step 12: Create the 're' region evaluation function - const EvalKit& reactKit = orderIfNonEmpty("react", logicClasses.m_reactive); + const EvalKit& reactKit + = orderIfNonEmpty("react", {&logicRegions.m_react, &logicReplicas.m_react}); // Step 13: Create the 'postponed' region evaluation function auto* const postponedFuncp = createPostponed(netlistp, logicClasses); diff --git a/src/V3Sched.h b/src/V3Sched.h index 27a4264d5..a8da240d9 100644 --- a/src/V3Sched.h +++ b/src/V3Sched.h @@ -98,6 +98,8 @@ struct LogicRegions final { LogicByScope m_pre; // AstAssignPre logic in 'act' region LogicByScope m_act; // 'act' region logic LogicByScope m_nba; // 'nba' region logic + LogicByScope m_obs; // AstAlwaysObserved logic in 'obs' region + LogicByScope m_react; // AstAlwaysReactive logic in 're' region LogicRegions() = default; VL_UNCOPYABLE(LogicRegions); @@ -111,6 +113,8 @@ struct LogicReplicas final { LogicByScope m_ico; // Logic replicated into the 'ico' (Input Combinational) region LogicByScope m_act; // Logic replicated into the 'act' region LogicByScope m_nba; // Logic replicated into the 'nba' region + LogicByScope m_obs; // Logic replicated into the 'obs' region + LogicByScope m_react; // Logic replicated into the 're' region LogicReplicas() = default; VL_UNCOPYABLE(LogicReplicas); diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index eb5194692..172dbc3ff 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -50,7 +50,9 @@ enum RegionFlags : uint8_t { NONE = 0x0, // INPUT = 0x1, // Variable/logic is driven from top level input ACTIVE = 0x2, // Variable/logic is driven from 'act' region logic - NBA = 0x4 // Variable/logic is driven from 'nba' region logic + NBA = 0x4, // Variable/logic is driven from 'nba' region logic + OBSERVED = 0x8, // Variable/logic is driven from 'obs' region logic + REACTIVE = 0x10, // Variable/logic is driven from 're' region logic }; //############################################################################## @@ -79,7 +81,34 @@ public: case INPUT | NBA: return "magenta"; case ACTIVE | NBA: return "cyan"; case INPUT | ACTIVE | NBA: return "gray80"; // don't want white on white background - default: v3fatal("There are only 3 region bits"); return ""; + + case REACTIVE: return "gray60"; + case REACTIVE | INPUT: return "lightcoral"; + case REACTIVE | ACTIVE: return "lightgreen"; + case REACTIVE | NBA: return "lightblue"; + case REACTIVE | INPUT | ACTIVE: return "lightyellow"; + case REACTIVE | INPUT | NBA: return "lightpink"; + case REACTIVE | ACTIVE | NBA: return "lightcyan"; + case REACTIVE | INPUT | ACTIVE | NBA: return "gray90"; + + case OBSERVED: return "gray20"; + case OBSERVED | INPUT: return "darkred"; + case OBSERVED | ACTIVE: return "darkgreen"; + case OBSERVED | NBA: return "darkblue"; + case OBSERVED | INPUT | ACTIVE: return "gold"; + case OBSERVED | INPUT | NBA: return "purple"; + case OBSERVED | ACTIVE | NBA: return "darkcyan"; + case OBSERVED | INPUT | ACTIVE | NBA: return "gray30"; + + case REACTIVE | OBSERVED: return "gray40"; + case REACTIVE | OBSERVED | INPUT: return "indianred"; + case REACTIVE | OBSERVED | ACTIVE: return "olive"; + case REACTIVE | OBSERVED | NBA: return "dodgerBlue"; + case REACTIVE | OBSERVED | INPUT | ACTIVE: return "khaki"; + case REACTIVE | OBSERVED | INPUT | NBA: return "plum"; + case REACTIVE | OBSERVED | ACTIVE | NBA: return "lightSeaGreen"; + case REACTIVE | OBSERVED | INPUT | ACTIVE | NBA: return "gray50"; + default: v3fatal("There are only 5 region bits"); return ""; } } // LCOV_EXCL_STOP @@ -213,6 +242,8 @@ std::unique_ptr buildGraph(const LogicRegions& logicRegions) { for (const auto& pair : logicRegions.m_pre) addLogic(ACTIVE, pair.first, pair.second); for (const auto& pair : logicRegions.m_act) addLogic(ACTIVE, pair.first, pair.second); for (const auto& pair : logicRegions.m_nba) addLogic(NBA, pair.first, pair.second); + for (const auto& pair : logicRegions.m_obs) addLogic(OBSERVED, pair.first, pair.second); + for (const auto& pair : logicRegions.m_react) addLogic(REACTIVE, pair.first, pair.second); return graphp; } @@ -251,6 +282,8 @@ LogicReplicas replicate(Graph* graphp) { if (targetRegions & INPUT) replicateTo(result.m_ico); if (targetRegions & ACTIVE) replicateTo(result.m_act); if (targetRegions & NBA) replicateTo(result.m_nba); + if (targetRegions & OBSERVED) replicateTo(result.m_obs); + if (targetRegions & REACTIVE) replicateTo(result.m_react); } } return result; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 2b1c78aad..76d42254d 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2792,6 +2792,7 @@ class WidthVisitor final : public VNVisitor { } void visit(AstMemberSel* nodep) override { UINFO(5, " MEMBERSEL " << nodep << endl); + if (nodep->didWidth()) return; if (debug() >= 9) nodep->dumpTree("- mbs-in: "); userIterateChildren(nodep, WidthVP{SELF, BOTH}.p()); if (debug() >= 9) nodep->dumpTree("- mbs-ic: "); @@ -2799,19 +2800,65 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(nodep->fromp()->dtypep(), nodep->fromp(), "Unlinked data type"); AstNodeDType* const fromDtp = nodep->fromp()->dtypep()->skipRefToEnump(); UINFO(9, " from dt " << fromDtp << endl); - if (AstNodeUOrStructDType* const adtypep = VN_CAST(fromDtp, NodeUOrStructDType)) { + const AstMemberSel* const fromSel = VN_CAST(nodep->fromp(), MemberSel); + if (AstClocking* const clockingp = fromSel && fromSel->varp() + ? VN_CAST(fromSel->varp()->firstAbovep(), Clocking) + : nullptr) { + // In: MEMBERSEL{MEMBERSEL{vifaceref, "cb", cb_event}, "clockvar", null} + // Out: MEMBERSEL{vifaceref, "clockvar", clockvar} + UINFO(9, " from clocking " << clockingp << endl); + if (AstVar* const varp = memberSelClocking(nodep, clockingp)) { + if (!varp->didWidth()) userIterate(varp, nullptr); + AstMemberSel* fromp = VN_AS(nodep->fromp(), MemberSel); + fromp->replaceWith(fromp->fromp()->unlinkFrBack()); + VL_DO_DANGLING(fromp->deleteTree(), fromp); + nodep->dtypep(varp->dtypep()); + nodep->varp(varp); + if (nodep->access().isWriteOrRW()) V3LinkLValue::linkLValueSet(nodep); + if (AstIfaceRefDType* const adtypep + = VN_CAST(nodep->fromp()->dtypep(), IfaceRefDType)) { + nodep->varp()->sensIfacep(adtypep->ifacep()); + } + UINFO(9, " done clocking msel " << nodep << endl); + nodep->didWidth(true); // Must not visit again: will confuse scopes + return; + } + } else if (AstNodeUOrStructDType* const adtypep = VN_CAST(fromDtp, NodeUOrStructDType)) { if (memberSelStruct(nodep, adtypep)) return; } else if (AstClassRefDType* const adtypep = VN_CAST(fromDtp, ClassRefDType)) { if (memberSelClass(nodep, adtypep)) return; } else if (AstIfaceRefDType* const adtypep = VN_CAST(fromDtp, IfaceRefDType)) { - if (AstNode* const foundp = memberSelIface(nodep, adtypep)) { + if (AstNode* foundp = memberSelIface(nodep, adtypep)) { + if (AstClocking* const clockingp = VN_CAST(foundp, Clocking)) { + foundp = clockingp->eventp(); + if (!foundp) { + AstVar* const eventp = new AstVar{ + clockingp->fileline(), VVarType::MODULETEMP, clockingp->name(), + clockingp->findBasicDType(VBasicDTypeKwd::EVENT)}; + eventp->lifetime(VLifetime::STATIC); + clockingp->eventp(eventp); + // Trigger the clocking event in Observed (IEEE 1800-2023 14.13) + clockingp->addNextHere(new AstAlwaysObserved{ + clockingp->fileline(), + new AstSenTree{clockingp->fileline(), + clockingp->sensesp()->cloneTree(false)}, + new AstFireEvent{ + clockingp->fileline(), + new AstVarRef{clockingp->fileline(), eventp, VAccess::WRITE}, + false}}); + v3Global.setHasEvents(); + foundp = eventp; + } + } if (AstVar* const varp = VN_CAST(foundp, Var)) { + if (!varp->didWidth()) userIterate(varp, nullptr); nodep->dtypep(foundp->dtypep()); nodep->varp(varp); AstIface* const ifacep = adtypep->ifacep(); varp->sensIfacep(ifacep); nodep->fromp()->foreach( [ifacep](AstVarRef* const refp) { refp->varp()->sensIfacep(ifacep); }); + nodep->didWidth(true); return; } UINFO(1, "found object " << foundp << endl); @@ -2865,6 +2912,7 @@ class WidthVisitor final : public VNVisitor { } nodep->dtypep(foundp->dtypep()); nodep->varp(varp); + nodep->didWidth(true); return true; } if (AstEnumItemRef* const adfoundp = VN_CAST(foundp, EnumItemRef)) { @@ -2930,6 +2978,21 @@ class WidthVisitor final : public VNVisitor { << (suggest.empty() ? "" : nodep->fileline()->warnMore() + suggest)); return nullptr; // Caller handles error } + AstVar* memberSelClocking(AstMemberSel* nodep, AstClocking* clockingp) { + // Returns node if ok + VSpellCheck speller; + for (AstClockingItem* itemp = clockingp->itemsp(); itemp; + itemp = VN_AS(itemp->nextp(), ClockingItem)) { + if (itemp->varp()->name() == nodep->name()) return itemp->varp(); + speller.pushCandidate(itemp->varp()->prettyName()); + } + const string suggest = speller.bestCandidateMsg(nodep->prettyName()); + nodep->v3error( + "Member " << nodep->prettyNameQ() << " not found in clocking block " + << clockingp->prettyNameQ() << "\n" + << (suggest.empty() ? "" : nodep->fileline()->warnMore() + suggest)); + return nullptr; // Caller handles error + } bool memberSelStruct(AstMemberSel* nodep, AstNodeUOrStructDType* adtypep) { // Returns true if ok if (AstMemberDType* const memberp diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 672307a06..1017ea146 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -81,6 +81,7 @@ #include "V3ProtectLib.h" #include "V3Randomize.h" #include "V3Reloop.h" +#include "V3Sampled.h" #include "V3Sched.h" #include "V3Scope.h" #include "V3Scoreboard.h" @@ -437,6 +438,9 @@ static void process() { // down to a module and now be identical V3ActiveTop::activeTopAll(v3Global.rootp()); + // Remove SAMPLED + if (v3Global.hasSampled()) V3Sampled::sampledAll(v3Global.rootp()); + if (v3Global.opt.stats()) V3Stats::statsStageAll(v3Global.rootp(), "PreOrder"); // Schedule the logic diff --git a/test_regress/t/t_clocking_virtual.pl b/test_regress/t/t_clocking_virtual.pl new file mode 100755 index 000000000..9d615c1c3 --- /dev/null +++ b/test_regress/t/t_clocking_virtual.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--exe --main --timing"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_clocking_virtual.v b/test_regress/t/t_clocking_virtual.v new file mode 100644 index 000000000..dc6ad78ab --- /dev/null +++ b/test_regress/t/t_clocking_virtual.v @@ -0,0 +1,59 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +interface Iface; + logic clk = 1'b0, inp = 1'b0, io = 1'b0, out = 1'b0, out2 = 1'b0; + clocking cb @(posedge clk); + input #7 inp; + output out; + inout io; + endclocking + always @(posedge clk) inp <= 1'b1; + always #5 clk <= ~clk; + assign out2 = out; +endinterface + +module main; + initial begin + #6; + t.mod1.cb.io <= 1'b1; + t.mod1.cb.out <= 1'b1; + if (t.mod0.io != 1'b0) $stop; + if (t.mod1.cb.io != 1'b0) $stop; + if (t.mod1.cb.inp != 1'b0) $stop; + @(posedge t.mod0.io) + if ($time != 15) $stop; + if (t.mod0.io != 1'b1) $stop; + if (t.mod1.cb.io != 1'b0) $stop; + #1 + if (t.mod0.cb.io != 1'b1) $stop; + if (t.mod1.cb.io != 1'b1) $stop; + if (t.mod1.cb.inp != 1'b1) $stop; + #8; + t.mod0.inp = 1'b0; + if (t.mod0.cb.inp != 1'b1) $stop; + @(t.mod1.cb) + if ($time != 25) $stop; + if (t.mod0.cb.inp != 1'b1) $stop; + t.mod0.inp = 1'b0; + @(t.mod0.cb) + if ($time != 35) $stop; + if (t.mod0.cb.inp != 1'b0) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + initial begin + @(posedge t.mod0.out) + if ($time != 15) $stop; + if (t.mod1.out2 != 1'b1) $stop; + end +endmodule + +module t; + main main1(); + Iface mod0(); + virtual Iface mod1 = mod0; +endmodule