diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 654419d8654..2ad6cc0d8ac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -168,6 +168,7 @@ set(HEADERS V3Unknown.h V3Unroll.h V3VariableOrder.h + V3VirtIface.h V3Waiver.h V3Width.h V3WidthCommit.h @@ -302,6 +303,7 @@ set(COMMON_SOURCES V3Undriven.cpp V3Unknown.cpp V3Unroll.cpp + V3VirtIface.cpp V3VariableOrder.cpp V3Waiver.cpp V3Width.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 8c79c0066da..e6ab62bae57 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -291,6 +291,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Undriven.o \ V3Unknown.o \ V3Unroll.o \ + V3VirtIface.o \ V3VariableOrder.o \ V3Width.o \ V3WidthCommit.o \ diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 32679819c12..d9272c6b655 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -834,6 +834,7 @@ class AstEnumDType final : public AstNodeDType { class AstIfaceRefDType final : public AstNodeDType { // Reference to an interface, either for a port, or inside parent cell // @astgen op1 := paramsp : List[AstPin] + bool m_virtual = false; // True if virtual interface FileLine* m_modportFileline; // Where modport token was string m_cellName; // "" = no cell, such as when connects to 'input' iface string m_ifaceName; // Interface name @@ -876,6 +877,8 @@ class AstIfaceRefDType final : public AstNodeDType { bool similarDType(const AstNodeDType* samep) const override { return this == samep; } int widthAlignBytes() const override { return 1; } int widthTotalBytes() const override { return 1; } + void isVirtual(bool flag) { m_virtual = flag; } + bool isVirtual() const { return m_virtual; } FileLine* modportFileline() const { return m_modportFileline; } string cellName() const { return m_cellName; } void cellName(const string& name) { m_cellName = name; } diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index ff37277bc85..88dd236db20 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1248,6 +1248,8 @@ class AstNetlist final : public AstNode { AstCFunc* m_evalp = nullptr; // The '_eval' function AstCFunc* m_evalNbap = nullptr; // The '_eval__nba' function AstVarScope* m_dpiExportTriggerp = nullptr; // The DPI export trigger variable + using IfaceTriggerVec = std::vector>; + IfaceTriggerVec m_virtIfaceTriggerps; // The virtual interface trigger variables AstVar* m_delaySchedulerp = nullptr; // The delay scheduler variable AstVarScope* m_nbaEventp = nullptr; // The NBA event variable AstVarScope* m_nbaEventTriggerp = nullptr; // If set to 1, the NBA event should get triggered @@ -1278,6 +1280,7 @@ class AstNetlist final : public AstNode { void evalNbap(AstCFunc* funcp) { m_evalNbap = funcp; } AstVarScope* dpiExportTriggerp() const { return m_dpiExportTriggerp; } void dpiExportTriggerp(AstVarScope* varScopep) { m_dpiExportTriggerp = varScopep; } + IfaceTriggerVec& virtIfaceTriggerps() { return m_virtIfaceTriggerps; } AstVar* delaySchedulerp() const { return m_delaySchedulerp; } void delaySchedulerp(AstVar* const varScopep) { m_delaySchedulerp = varScopep; } AstVarScope* nbaEventp() const { return m_nbaEventp; } @@ -1727,6 +1730,7 @@ class AstVar final : public AstNode { VLifetime m_lifetime; // Lifetime VVarAttrClocker m_attrClocker; MTaskIdSet m_mtaskIds; // MTaskID's that read or write this var + AstIface* m_sensIfacep = nullptr; // Interface type to which reads from this var are sensitive int m_pinNum = 0; // For XML, if non-zero the connection pin number bool m_ansi : 1; // Params or pins declared in the module header, rather than the body bool m_declTyped : 1; // Declared as type (for dedup check) @@ -1742,7 +1746,6 @@ class AstVar final : public AstNode { bool m_usedClock : 1; // Signal used as a clock bool m_usedParam : 1; // Parameter is referenced (on link; later signals not setup) bool m_usedLoopIdx : 1; // Variable subject of for unrolling - bool m_usedVirtIface : 1; // Signal used through a virtual interface bool m_funcLocal : 1; // Local variable for a function bool m_funcLocalSticky : 1; // As m_funcLocal but remains set if var is moved to a static bool m_funcReturn : 1; // Return variable for a function @@ -1784,7 +1787,6 @@ class AstVar final : public AstNode { m_usedClock = false; m_usedParam = false; m_usedLoopIdx = false; - m_usedVirtIface = false; m_sigPublic = false; m_sigModPublic = false; m_sigUserRdPublic = false; @@ -1913,6 +1915,7 @@ class AstVar final : public AstNode { } void ansi(bool flag) { m_ansi = flag; } void declTyped(bool flag) { m_declTyped = flag; } + void sensIfacep(AstIface* nodep) { m_sensIfacep = nodep; } void attrClocker(VVarAttrClocker flag) { m_attrClocker = flag; } void attrFileDescr(bool flag) { m_fileDescr = flag; } void attrScClocked(bool flag) { m_scClocked = flag; } @@ -1923,7 +1926,6 @@ class AstVar final : public AstNode { void usedClock(bool flag) { m_usedClock = flag; } void usedParam(bool flag) { m_usedParam = flag; } void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; } - void usedVirtIface(bool flag) { m_usedVirtIface = flag; } void sigPublic(bool flag) { m_sigPublic = flag; } void sigModPublic(bool flag) { m_sigModPublic = flag; } void sigUserRdPublic(bool flag) { @@ -2016,7 +2018,6 @@ class AstVar final : public AstNode { bool isUsedClock() const { return m_usedClock; } bool isUsedParam() const { return m_usedParam; } bool isUsedLoopIdx() const { return m_usedLoopIdx; } - bool isUsedVirtIface() const { return m_usedVirtIface; } bool isSc() const VL_MT_SAFE { return m_sc; } bool isScQuad() const; bool isScBv() const; @@ -2044,6 +2045,7 @@ class AstVar final : public AstNode { bool attrSFormat() const { return m_attrSFormat; } bool attrSplitVar() const { return m_attrSplitVar; } bool attrIsolateAssign() const { return m_attrIsolateAssign; } + AstIface* sensIfacep() const { return m_sensIfacep; } VVarAttrClocker attrClocker() const { return m_attrClocker; } string verilogKwd() const override; void lifetime(const VLifetime& flag) { m_lifetime = flag; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 5b717f51325..c705347671a 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1950,6 +1950,7 @@ const char* AstNetlist::broken() const { BROKEN_RTN(m_delaySchedulerp && !m_delaySchedulerp->brokeExists()); BROKEN_RTN(m_nbaEventp && !m_nbaEventp->brokeExists()); BROKEN_RTN(m_nbaEventTriggerp && !m_nbaEventTriggerp->brokeExists()); + for (const auto& p : m_virtIfaceTriggerps) BROKEN_RTN(!p.second->brokeExists()); return nullptr; } AstPackage* AstNetlist::dollarUnitPkgAddp() { diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 0fbbb8bb8e1..f4a2b15e5a0 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2691,7 +2691,7 @@ class ConstVisitor final : public VNVisitor { && m_doNConst && v3Global.opt.fConst() // Default value, not a "known" constant for this usage - && !nodep->varp()->isClassMember() && !nodep->varp()->isUsedVirtIface() + && !nodep->varp()->isClassMember() && !nodep->varp()->sensIfacep() && !(nodep->varp()->isFuncLocal() && nodep->varp()->isNonOutput()) && !nodep->varp()->noSubst() && !nodep->varp()->isSigPublic()) || nodep->varp()->isParam())) { diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index fa9065bd9cd..bf662b2131f 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -361,7 +361,7 @@ class DeadVisitor final : public VNVisitor { } bool mightElimVar(AstVar* nodep) const { if (nodep->isSigPublic()) return false; // Can't elim publics! - if (nodep->isIO() || nodep->isClassMember() || nodep->isUsedVirtIface()) return false; + if (nodep->isIO() || nodep->isClassMember() || nodep->sensIfacep()) return false; if (nodep->isTemp() && !nodep->isTrace()) return true; return m_elimUserVars; // Post-Trace can kill most anything } diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp index b53ee24c946..b9457d6884b 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -163,7 +163,7 @@ class GateGraph final : public V3Graph { UINFO(6, "New vertex " << vscp << endl); vVtxp = new GateVarVertex{this, vscp}; vscp->user1p(vVtxp); - if (vscp->varp()->isUsedVirtIface()) { + if (vscp->varp()->sensIfacep()) { // Can be used in a class method, which cannot be tracked statically vVtxp->clearReducibleAndDedupable("VirtIface"); vVtxp->setConsumed("VirtIface"); diff --git a/src/V3Global.h b/src/V3Global.h index 036a5515ef5..4b414bc0fa0 100644 --- a/src/V3Global.h +++ b/src/V3Global.h @@ -112,6 +112,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_hasVirtIfaces = false; // Design uses virtual interfaces bool m_usesProbDist = false; // Uses $dist_* bool m_usesStdPackage = false; // Design uses the std package bool m_usesTiming = false; // Design uses timing constructs @@ -162,6 +163,8 @@ class V3Global final { void setHasEvents() { m_hasEvents = true; } bool hasClasses() const { return m_hasClasses; } void setHasClasses() { m_hasClasses = true; } + bool hasVirtIfaces() const { return m_hasVirtIfaces; } + void setHasVirtIfaces() { m_hasVirtIfaces = true; } bool usesProbDist() const { return m_usesProbDist; } void setUsesProbDist() { m_usesProbDist = true; } bool usesStdPackage() const { return m_usesStdPackage; } diff --git a/src/V3Life.cpp b/src/V3Life.cpp index f0696097288..80a894f367a 100644 --- a/src/V3Life.cpp +++ b/src/V3Life.cpp @@ -137,7 +137,7 @@ class LifeBlock final { void checkRemoveAssign(const LifeMap::iterator& it) { const AstVar* const varp = it->first->varp(); LifeVarEntry* const entp = &(it->second); - if (!varp->isSigPublic() && !varp->isUsedVirtIface()) { + if (!varp->isSigPublic() && !varp->sensIfacep()) { // Rather than track what sigs AstUCFunc/AstUCStmt may change, // we just don't optimize any public sigs // Check the var entry, and remove if appropriate @@ -178,7 +178,7 @@ class LifeBlock final { const auto pair = m_map.emplace(nodep, LifeVarEntry::CONSUMED{}); if (!pair.second) { if (AstConst* const constp = pair.first->second.constNodep()) { - if (!varrefp->varp()->isSigPublic() && !varrefp->varp()->isUsedVirtIface()) { + if (!varrefp->varp()->isSigPublic() && !varrefp->varp()->sensIfacep()) { // Aha, variable is constant; substitute in. // We'll later constant propagate UINFO(4, " replaceconst: " << varrefp << endl); diff --git a/src/V3Localize.cpp b/src/V3Localize.cpp index 754654a45e9..028b14a6928 100644 --- a/src/V3Localize.cpp +++ b/src/V3Localize.cpp @@ -166,7 +166,7 @@ class LocalizeVisitor final : public VNVisitor { && !nodep->varp()->isFuncLocal() // Not already a function local (e.g.: argument) && !nodep->varp()->isStatic() // Not a static variable && !nodep->varp()->isClassMember() // Statically exists in design hierarchy - && !nodep->varp()->isUsedVirtIface() // Not used through a virtual interface + && !nodep->varp()->sensIfacep() // Not sensitive to an interface && !nodep->varp()->valuep() // Does not have an initializer ) { UINFO(4, "Consider for localization: " << nodep << endl); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 926379d97e2..0fe8b2980f9 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -507,16 +507,16 @@ struct TriggerKit { m_funcp->stmtsp()->addHereThisAsNext(callp->makeStmt()); } - // Utility to set then clear the dpiExportTrigger trigger - void addDpiExportTriggerAssignment(AstVarScope* dpiExportTriggerVscp, uint32_t index) const { - FileLine* const flp = dpiExportTriggerVscp->fileline(); + // Utility to set then clear an extra trigger + void addExtraTriggerAssignment(AstVarScope* extraTriggerVscp, uint32_t index) const { + FileLine* const flp = extraTriggerVscp->fileline(); AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE}; AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "set"}; callp->addPinsp(new AstConst{flp, index}); - callp->addPinsp(new AstVarRef{flp, dpiExportTriggerVscp, VAccess::READ}); + callp->addPinsp(new AstVarRef{flp, extraTriggerVscp, VAccess::READ}); callp->dtypeSetVoid(); AstNode* const stmtp = callp->makeStmt(); - stmtp->addNext(new AstAssign{flp, new AstVarRef{flp, dpiExportTriggerVscp, VAccess::WRITE}, + stmtp->addNext(new AstAssign{flp, new AstVarRef{flp, extraTriggerVscp, VAccess::WRITE}, new AstConst{flp, AstConst::BitFalse{}}}); m_funcp->stmtsp()->addHereThisAsNext(stmtp); } @@ -840,7 +840,7 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, = createTriggers(netlistp, initFuncp, senExprBuilder, senTreeps, "ico", extraTriggers); if (dpiExportTriggerVscp) { - trig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex); + trig.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex); } // Remap sensitivities @@ -1156,6 +1156,10 @@ void schedule(AstNetlist* netlistp) { const size_t dpiExportTriggerIndex = dpiExportTriggerVscp ? extraTriggers.allocate("DPI export trigger") : std::numeric_limits::max(); + const size_t firstVifTriggerIndex = dpiExportTriggerIndex + 1; + for (const auto& p : netlistp->virtIfaceTriggerps()) { + extraTriggers.allocate(p.first->name()); + } const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre, // &logicRegions.m_act, // @@ -1170,7 +1174,14 @@ void schedule(AstNetlist* netlistp) { if (timingKit.m_postUpdates) actTrig.m_funcp->addStmtsp(timingKit.m_postUpdates); if (dpiExportTriggerVscp) { - actTrig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex); + actTrig.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex); + } + { + size_t vifTriggerIndex = firstVifTriggerIndex; + for (const auto& p : netlistp->virtIfaceTriggerps()) { + actTrig.addExtraTriggerAssignment(p.second, vifTriggerIndex); + vifTriggerIndex++; + } } AstVarScope* const actTrigVscp = actTrig.m_vscp; @@ -1223,12 +1234,26 @@ void schedule(AstNetlist* netlistp) { ? createTriggerSenTree(netlistp, actTrig.m_vscp, dpiExportTriggerIndex) : nullptr; + std::map vifTriggeredAct; + { + size_t vifTriggerIndex = firstVifTriggerIndex; + for (const auto& p : netlistp->virtIfaceTriggerps()) { + vifTriggeredAct.insert(std::make_pair( + p.first, createTriggerSenTree(netlistp, actTrig.m_vscp, vifTriggerIndex))); + vifTriggerIndex++; + } + } + AstCFunc* const actFuncp = V3Order::order( netlistp, {&logicRegions.m_pre, &logicRegions.m_act, &logicReplicas.m_act}, trigToSenAct, "act", false, false, [&](const AstVarScope* vscp, std::vector& out) { auto it = actTimingDomains.find(vscp); if (it != actTimingDomains.end()) out = it->second; if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredAct); + if (vscp->varp()->sensIfacep()) { + const auto it = vifTriggeredAct.find(vscp->varp()->sensIfacep()); + if (it != vifTriggeredAct.end()) out.push_back(it->second); + } }); splitCheck(actFuncp); if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-act"); @@ -1252,6 +1277,15 @@ void schedule(AstNetlist* netlistp) { = dpiExportTriggerVscp ? createTriggerSenTree(netlistp, trigVscp, dpiExportTriggerIndex) : nullptr; + std::map vifTriggered; + { + size_t vifTriggerIndex = firstVifTriggerIndex; + for (const auto& p : netlistp->virtIfaceTriggerps()) { + vifTriggered.insert(std::make_pair( + p.first, createTriggerSenTree(netlistp, trigVscp, vifTriggerIndex))); + vifTriggerIndex++; + } + } const auto& timingDomains = timingKit.remapDomains(trigMap); AstCFunc* const funcp = V3Order::order( @@ -1260,6 +1294,10 @@ void schedule(AstNetlist* netlistp) { auto it = timingDomains.find(vscp); if (it != timingDomains.end()) out = it->second; if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered); + if (vscp->varp()->sensIfacep()) { + const auto it = vifTriggered.find(vscp->varp()->sensIfacep()); + if (it != vifTriggered.end()) out.push_back(it->second); + } }); // Create the trigger dumping function, which is the same as act trigger @@ -1314,6 +1352,7 @@ void schedule(AstNetlist* netlistp) { splitCheck(initp); netlistp->dpiExportTriggerp(nullptr); + netlistp->virtIfaceTriggerps().clear(); V3Global::dumpCheckGlobalTree("sched", 0, dumpTreeLevel() >= 3); } diff --git a/src/V3VirtIface.cpp b/src/V3VirtIface.cpp new file mode 100644 index 00000000000..a41990aa591 --- /dev/null +++ b/src/V3VirtIface.cpp @@ -0,0 +1,175 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Create triggers necessary for scheduling across +// virtual interfaces +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2023 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 +// +//************************************************************************* +// V3VirtIface's Transformations: +// +// Each interface type written to via virtual interface, or written to normally but read via +// virtual interface: +// Create a trigger var for it +// Each AssignW: +// If it writes to a virtual interface, or to a variable read via virtual interface: +// Convert to an always +// Each statement: +// If it writes to a virtual interface, or to a variable read via virtual interface: +// Set the corresponding trigger to 1 +// If the write is done by an AssignDly, the trigger is also set by AssignDly +// +//************************************************************************* + +#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT + +#include "V3VirtIface.h" + +#include "V3AstNodeExpr.h" +#include "V3UniqueNames.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +//###################################################################### +// + +class VirtIfaceVisitor final : public VNVisitor { +private: + // NODE STATE + // AstIface::user1() -> AstVarScope*. Trigger var for this interface + const VNUser1InUse m_user1InUse; + + // TYPES + using OnWriteToVirtIface = std::function; + + // STATE + AstNetlist* const m_netlistp; // Root node + AstNodeAssign* m_trigAssignp = nullptr; // Previous/current trigger assignment + AstIface* m_trigAssignIfacep = nullptr; // Interface type whose trigger is assigned + // by m_trigAssignp + V3UniqueNames m_vifTriggerNames{"__VvifTrigger"}; // Unique names for virt iface + // triggers + + // METHODS + static void foreachWrittenVirtIface(AstNode* const nodep, const OnWriteToVirtIface& onWrite) { + nodep->foreach([&](AstVarRef* const refp) { + if (refp->access().isReadOnly()) return; + if (AstIfaceRefDType* dtypep = VN_CAST(refp->varp()->dtypep(), IfaceRefDType)) { + if (dtypep->isVirtual() && VN_IS(refp->firstAbovep(), MemberSel)) { + onWrite(refp, dtypep->ifacep()); + } + } else if (AstIface* ifacep = refp->varp()->sensIfacep()) { + onWrite(refp, ifacep); + } + }); + } + static void unsupportedWriteToVirtIface(AstNode* nodep) { + if (!nodep) return; + foreachWrittenVirtIface(nodep, [](AstVarRef* const selp, AstIface*) { + selp->v3warn(E_UNSUPPORTED, + "Unsupported: write to virtual interface in this location"); + }); + } + AstVarRef* createVirtIfaceTriggerRefp(FileLine* const flp, AstIface* ifacep) { + if (!ifacep->user1()) { + AstScope* const scopeTopp = m_netlistp->topScopep()->scopep(); + auto* vscp = scopeTopp->createTemp(m_vifTriggerNames.get(ifacep), 1); + ifacep->user1p(vscp); + m_netlistp->virtIfaceTriggerps().push_back(std::make_pair(ifacep, vscp)); + } + return new AstVarRef{flp, VN_AS(ifacep->user1p(), VarScope), VAccess::WRITE}; + } + + // VISITORS + void visit(AstNodeProcedure* nodep) override { + VL_RESTORER(m_trigAssignp); + VL_RESTORER(m_trigAssignIfacep); + m_trigAssignp = nullptr; + m_trigAssignIfacep = nullptr; + iterateChildren(nodep); + } + void visit(AstCFunc* nodep) override { + VL_RESTORER(m_trigAssignp); + VL_RESTORER(m_trigAssignIfacep); + m_trigAssignp = nullptr; + m_trigAssignIfacep = nullptr; + iterateChildren(nodep); + } + void visit(AstAssignW* nodep) override { + if (nodep->exists([](AstVarRef* const refp) { + AstIfaceRefDType* dtypep = VN_CAST(refp->varp()->dtypep(), IfaceRefDType); + return refp->access().isWriteOrRW() + && ((dtypep && dtypep->isVirtual() && VN_IS(refp->firstAbovep(), MemberSel)) + || refp->varp()->sensIfacep()); + })) { + nodep->convertToAlways(); + } + } + void visit(AstNodeIf* nodep) override { + unsupportedWriteToVirtIface(nodep->condp()); + m_trigAssignp = nullptr; + iterateAndNextNull(nodep->thensp()); + m_trigAssignp = nullptr; + iterateAndNextNull(nodep->elsesp()); + } + void visit(AstWhile* nodep) override { + unsupportedWriteToVirtIface(nodep->precondsp()); + unsupportedWriteToVirtIface(nodep->condp()); + unsupportedWriteToVirtIface(nodep->incsp()); + m_trigAssignp = nullptr; + iterateAndNextNull(nodep->stmtsp()); + } + void visit(AstNodeStmt* nodep) override { + if (nodep->user1SetOnce()) return; // Process once + if (nodep->isTimingControl()) { + m_trigAssignp = nullptr; // Could be after a delay - need new trigger assignment + } + FileLine* const flp = nodep->fileline(); + foreachWrittenVirtIface(nodep, [&](AstVarRef*, AstIface* ifacep) { + if (ifacep != m_trigAssignIfacep) { + m_trigAssignIfacep = ifacep; + m_trigAssignp = nullptr; + } + if (VN_IS(nodep, AssignDly)) { + if (!VN_IS(m_trigAssignp, AssignDly)) { + m_trigAssignp = new AstAssignDly{flp, createVirtIfaceTriggerRefp(flp, ifacep), + new AstConst{flp, AstConst::BitTrue{}}}; + AstNode* addp = nodep; + if (VN_IS(nodep->backp(), Fork)) addp = nodep->backp(); + addp->addHereThisAsNext(m_trigAssignp); + } + } else if (!m_trigAssignp || VN_IS(m_trigAssignp, AssignDly)) { + m_trigAssignp = new AstAssign{flp, createVirtIfaceTriggerRefp(flp, ifacep), + new AstConst{flp, AstConst::BitTrue{}}}; + nodep->addNextHere(m_trigAssignp); + } + }); + } + void visit(AstNodeExpr*) override {} // Accelerate + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + // CONSTRUCTORS + explicit VirtIfaceVisitor(AstNetlist* nodep) + : m_netlistp{nodep} { + iterate(nodep); + } + ~VirtIfaceVisitor() override = default; +}; + +//###################################################################### +// VirtIface class functions + +void V3VirtIface::makeTriggers(AstNetlist* nodep) { + UINFO(2, __FUNCTION__ << ": " << endl); + { VirtIfaceVisitor{nodep}; } + V3Global::dumpCheckGlobalTree("vif", 0, dumpTreeLevel() >= 3); +} diff --git a/src/V3VirtIface.h b/src/V3VirtIface.h new file mode 100644 index 00000000000..b6e501282e8 --- /dev/null +++ b/src/V3VirtIface.h @@ -0,0 +1,35 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Create triggers necessary for scheduling across +// virtual interfaces +// +// Code available from: https://verilator.org +// +//************************************************************************* +// +// Copyright 2003-2023 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_V3VIRTIFACE_H_ +#define VERILATOR_V3VIRTIFACE_H_ + +#include "config_build.h" +#include "verilatedos.h" + +#include "V3ThreadSafety.h" + +class AstNetlist; + +//============================================================================ + +class V3VirtIface final { +public: + static void makeTriggers(AstNetlist* nodep) VL_MT_DISABLED; +}; + +#endif // Guard diff --git a/src/V3Width.cpp b/src/V3Width.cpp index b5186cc4d6b..da7c69aea4f 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2741,7 +2741,10 @@ class WidthVisitor final : public VNVisitor { if (AstVar* const varp = VN_CAST(foundp, Var)) { nodep->dtypep(foundp->dtypep()); nodep->varp(varp); - varp->usedVirtIface(true); + AstIface* const ifacep = adtypep->ifacep(); + varp->sensIfacep(ifacep); + nodep->fromp()->foreach( + [ifacep](AstVarRef* const refp) { refp->varp()->sensIfacep(ifacep); }); return; } UINFO(1, "found object " << foundp << endl); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 32f1108cba6..e40f2fda19e 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -102,6 +102,7 @@ #include "V3Unknown.h" #include "V3Unroll.h" #include "V3VariableOrder.h" +#include "V3VirtIface.h" #include "V3Waiver.h" #include "V3Width.h" #include "V3WidthCommit.h" @@ -397,6 +398,11 @@ static void process() { // Reorder assignments in pipelined blocks if (v3Global.opt.fReorder()) V3Split::splitReorderAll(v3Global.rootp()); + if (v3Global.hasVirtIfaces()) { + // Create extra triggers for virtual interfaces + V3VirtIface::makeTriggers(v3Global.rootp()); + } + if (v3Global.opt.timing().isSetTrue()) { // Convert AST for timing if requested // Needs to be after V3Gate, as that step modifies sentrees diff --git a/src/verilog.y b/src/verilog.y index 44fe500390e..a92e727f138 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -2134,12 +2134,26 @@ data_typeNoRef: // ==IEEE: data_type, excluding class_ty data_typeVirtual: // ==IEEE: data_type after yVIRTUAL [ yINTERFACE ] // // Parameters here are SV2009 - id/*interface*/ { $$ = new AstIfaceRefDType{$1, "", *$1}; } - | id/*interface*/ '.' id/*modport*/ { $$ = new AstIfaceRefDType{$1, $3, "", *$1, *$3}; } + id/*interface*/ + { AstIfaceRefDType* const ifrefp = new AstIfaceRefDType{$1, "", *$1}; + ifrefp->isVirtual(true); + v3Global.setHasVirtIfaces(); + $$ = ifrefp; } + | id/*interface*/ '.' id/*modport*/ + { AstIfaceRefDType* const ifrefp = new AstIfaceRefDType{$1, $3, "", *$1, *$3}; + ifrefp->isVirtual(true); + v3Global.setHasVirtIfaces(); + $$ = ifrefp; } | id/*interface*/ parameter_value_assignmentClass - { $$ = new AstIfaceRefDType{$1, nullptr, "", *$1, "", $2}; } + { AstIfaceRefDType* const ifrefp = new AstIfaceRefDType{$1, nullptr, "", *$1, "", $2}; + ifrefp->isVirtual(true); + v3Global.setHasVirtIfaces(); + $$ = ifrefp; } | id/*interface*/ parameter_value_assignmentClass '.' id/*modport*/ - { $$ = new AstIfaceRefDType{$1, $4, "", *$1, *$4, $2}; } + { AstIfaceRefDType* const ifrefp = new AstIfaceRefDType{$1, $4, "", *$1, *$4, $2}; + ifrefp->isVirtual(true); + v3Global.setHasVirtIfaces(); + $$ = ifrefp; } ; data_type_or_void: // ==IEEE: data_type_or_void diff --git a/test_regress/t/t_interface_virtual_trig.out b/test_regress/t/t_interface_virtual_trig.out new file mode 100644 index 00000000000..3b4726ae079 --- /dev/null +++ b/test_regress/t/t_interface_virtual_trig.out @@ -0,0 +1,16 @@ +[0] intf1.data==0000 +[0] intf2.data==0000 +[0] vif3.data==0000 +[0] intf2.data==0000 +[10] intf2.data==0000 +[20] intf1.data==dead +[20] intf2.data==0000 +[30] intf2.data==dead +[40] intf1.data==beef +[40] intf2.data==dead +[50] intf2.data==beef +[60] intf2.data==beef +[60] vif3.data==fafa +[70] intf2.data==beef +[70] vif3.data==bebe +*-* All Finished *-* diff --git a/test_regress/t/t_interface_virtual_trig.pl b/test_regress/t/t_interface_virtual_trig.pl new file mode 100755 index 00000000000..6247bd1265b --- /dev/null +++ b/test_regress/t/t_interface_virtual_trig.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 2020 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( + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual_trig.v b/test_regress/t/t_interface_virtual_trig.v new file mode 100644 index 00000000000..a26438fe0f3 --- /dev/null +++ b/test_regress/t/t_interface_virtual_trig.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, 2023 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +interface Bus1; + logic [15:0] data; +endinterface + +interface Bus2; + logic [15:0] data; +endinterface + +interface Bus3; + logic [15:0] data; +endinterface + +module t(clk); + input clk; + integer cyc = 0; + Bus1 intf1(); + Bus2 intf2(); + Bus3 intf3(); + virtual Bus1 vif1 = intf1; + virtual Bus2 vif2 = intf2; + virtual Bus3 vif3 = intf3; + + logic [15:0] data; + assign vif2.data = data; + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) begin + vif1.data <= 'hdead; + end else if (cyc == 2) begin + data <= vif1.data; + end else if (cyc == 3) begin + vif1.data <= 'hbeef; + end else if (cyc == 4) begin + data <= vif1.data; + end if (cyc == 5) begin + intf3.data = 'hfafa; + end else if (cyc == 6) begin + intf3.data = 'hbebe; + end + end + + // Finish on negedge so that $finish is last + always @(negedge clk) + if (cyc >= 7) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + always_comb $write("[%0t] intf1.data==%h\n", $time, intf1.data); + always_comb $write("[%0t] intf2.data==%h\n", $time, intf2.data); + always_comb $write("[%0t] vif3.data==%h\n", $time, vif3.data); +endmodule diff --git a/test_regress/t/t_interface_virtual_unsup.out b/test_regress/t/t_interface_virtual_unsup.out new file mode 100644 index 00000000000..adc000fbb76 --- /dev/null +++ b/test_regress/t/t_interface_virtual_unsup.out @@ -0,0 +1,17 @@ +%Error-UNSUPPORTED: t/t_interface_virtual_unsup.v:22:22: Unsupported: write to virtual interface in this location + 22 | if (write_data(vif.data)) $write("dummy op"); + | ^~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_interface_virtual_unsup.v:23:25: Unsupported: write to virtual interface in this location + 23 | while (write_data(vif.data)); + | ^~~ +%Error-UNSUPPORTED: t/t_interface_virtual_unsup.v:24:34: Unsupported: write to virtual interface in this location + 24 | for (int i = 0; write_data(vif.data); i += int'(write_data(vif.data))); + | ^~~ +%Error-UNSUPPORTED: t/t_interface_virtual_unsup.v:24:66: Unsupported: write to virtual interface in this location + 24 | for (int i = 0; write_data(vif.data); i += int'(write_data(vif.data))); + | ^~~ +%Error-UNSUPPORTED: t/t_interface_virtual_unsup.v:25:34: Unsupported: write to virtual interface in this location + 25 | for (int i = 0; write_data(vif.data++); i++); + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_interface_virtual_unsup.pl b/test_regress/t/t_interface_virtual_unsup.pl new file mode 100755 index 00000000000..bd07fc42123 --- /dev/null +++ b/test_regress/t/t_interface_virtual_unsup.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2023 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( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +execute( + check_finished => 1, + ) if !$Self->{vlt_all}; + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual_unsup.v b/test_regress/t/t_interface_virtual_unsup.v new file mode 100644 index 00000000000..27870a35f2f --- /dev/null +++ b/test_regress/t/t_interface_virtual_unsup.v @@ -0,0 +1,28 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2023 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +interface Bus; + logic [15:0] data; +endinterface + +module t; + Bus intf(); + virtual Bus vif = intf; + + function logic write_data(output logic[15:0] data); + data = 'hdead; + return 1; + endfunction + + // verilator lint_off INFINITELOOP + initial begin + if (write_data(vif.data)) $write("dummy op"); + while (write_data(vif.data)); + for (int i = 0; write_data(vif.data); i += int'(write_data(vif.data))); + for (int i = 0; write_data(vif.data++); i++); + end + +endmodule