diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 654419d86..4f367a972 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -282,6 +282,7 @@ set(COMMON_SOURCES V3SchedPartition.cpp V3SchedReplicate.cpp V3SchedTiming.cpp + V3SchedVirtIface.cpp V3Scope.cpp V3Scoreboard.cpp V3Slice.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 8c79c0066..10f30b8ad 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -274,6 +274,7 @@ RAW_OBJS_PCH_ASTNOMT = \ V3SchedPartition.o \ V3SchedReplicate.o \ V3SchedTiming.o \ + V3SchedVirtIface.o \ V3Scope.o \ V3Scoreboard.o \ V3Slice.o \ diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 07a00b96c..f161e5e68 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -818,6 +818,7 @@ class AstIfaceRefDType final : public AstNodeDType { // @astgen ptr := m_ifacep : Optional[AstIface] // Interface; cellp() should override // @astgen ptr := m_cellp : Optional[AstCell] // When exact parent cell known; not a guess // @astgen ptr := m_modportp : Optional[AstModport] // nullptr = unlinked or no modport + 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 @@ -855,6 +856,11 @@ public: 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; + if (flag) v3Global.setHasVirtIfaces(); + } + 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 b555c0fad..2b84d226a 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1713,6 +1713,8 @@ class AstVar final : public AstNode { // MTASKSTATE variables // @astgen op3 := valuep : Optional[AstNode] // May be a DType for type parameter defaults // @astgen op4 := attrsp : List[AstNode] // Attributes during early parse + // @astgen ptr := m_sensIfacep : Optional[AstIface] // Interface type to which reads from this + // var are sensitive string m_name; // Name of variable string m_origName; // Original name before dot addition @@ -1738,7 +1740,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 @@ -1780,7 +1781,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; @@ -1909,6 +1909,7 @@ public: } 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; } @@ -1919,7 +1920,6 @@ public: 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) { @@ -2012,7 +2012,6 @@ public: 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; @@ -2040,6 +2039,7 @@ public: 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/V3Const.cpp b/src/V3Const.cpp index 37fe52981..7f91aff2a 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2694,7 +2694,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 fa9065bd9..bf662b213 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 b53ee24c9..b9457d688 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -163,7 +163,7 @@ public: 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 036a5515e..4b414bc0f 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 @@ public: 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 f06960972..80a894f36 100644 --- a/src/V3Life.cpp +++ b/src/V3Life.cpp @@ -137,7 +137,7 @@ public: 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 @@ public: 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 754654a45..028b14a69 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 c43af13b4..6ab8c0ce3 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -508,16 +508,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); } @@ -576,6 +576,17 @@ public: const string& description(size_t index) const { return m_descriptions[index]; } }; +//============================================================================ +// Helper that creates virtual interface trigger resets + +void addVirtIfaceTriggerAssignments(const VirtIfaceTriggers& virtIfaceTriggers, + size_t vifTriggerIndex, const TriggerKit& actTrig) { + for (const auto& p : virtIfaceTriggers) { + actTrig.addExtraTriggerAssignment(p.second, vifTriggerIndex); + ++vifTriggerIndex; + } +} + //============================================================================ // Create a TRIGGERVEC and the related TriggerKit for the given AstSenTree vector @@ -808,7 +819,8 @@ void createSettle(AstNetlist* netlistp, AstCFunc* const initFuncp, SenExprBuilde // Order the replicated combinational logic to create the 'ico' region AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, - SenExprBuilder& senExprBuilder, LogicByScope& logic) { + SenExprBuilder& senExprBuilder, LogicByScope& logic, + const VirtIfaceTriggers& virtIfaceTriggers) { // Nothing to do if no combinational logic is sensitive to top level inputs if (logic.empty()) return nullptr; @@ -834,6 +846,10 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, const size_t dpiExportTriggerIndex = dpiExportTriggerVscp ? extraTriggers.allocate("DPI export trigger") : std::numeric_limits::max(); + const size_t firstVifTriggerIndex = extraTriggers.size(); + for (const auto& p : virtIfaceTriggers) { + extraTriggers.allocate("virtual interface: " + p.first->name()); + } // Gather the relevant sensitivity expressions and create the trigger kit const auto& senTreeps = getSenTreesUsedBy({&logic}); @@ -841,8 +857,9 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, = createTriggers(netlistp, initFuncp, senExprBuilder, senTreeps, "ico", extraTriggers); if (dpiExportTriggerVscp) { - trig.addDpiExportTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex); + trig.addExtraTriggerAssignment(dpiExportTriggerVscp, dpiExportTriggerIndex); } + addVirtIfaceTriggerAssignments(virtIfaceTriggers, firstVifTriggerIndex, trig); // Remap sensitivities remapSensitivities(logic, trig.m_map); @@ -859,6 +876,8 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, AstSenTree* const dpiExportTriggered = dpiExportTriggerVscp ? createTriggerSenTree(netlistp, trig.m_vscp, dpiExportTriggerIndex) : nullptr; + const auto& vifTriggered + = virtIfaceTriggers.makeIfaceToSensMap(netlistp, firstVifTriggerIndex, trig.m_vscp); // Create and Order the body function AstCFunc* const icoFuncp @@ -869,6 +888,10 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, out.push_back(inputChanged); } if (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); + } }); splitCheck(icoFuncp); @@ -1070,6 +1093,21 @@ void createEval(AstNetlist* netlistp, // } // namespace +//============================================================================ +// Helper that builds virtual interface trigger sentrees + +VirtIfaceTriggers::IfaceSensMap +VirtIfaceTriggers::makeIfaceToSensMap(AstNetlist* const netlistp, size_t vifTriggerIndex, + AstVarScope* trigVscp) const { + std::map ifaceToSensMap; + for (const auto& p : *this) { + ifaceToSensMap.emplace( + std::make_pair(p.first, createTriggerSenTree(netlistp, trigVscp, vifTriggerIndex))); + ++vifTriggerIndex; + } + return ifaceToSensMap; +} + //============================================================================ // Top level entry-point to scheduling @@ -1080,7 +1118,10 @@ void schedule(AstNetlist* netlistp) { V3Stats::addStat("Scheduling, " + name, size); }; - // Step 0. Prepare timing-related logic and external domains + // Step 0. Prepare external domains for timing and virtual interfaces + // Create extra triggers for virtual interfaces + const auto& virtIfaceTriggers = makeVirtIfaceTriggers(netlistp); + // Prepare timing-related logic and external domains TimingKit timingKit = prepareTiming(netlistp); // Step 1. Gather and classify all logic in the design @@ -1145,8 +1186,8 @@ void schedule(AstNetlist* netlistp) { } // Step 7: Create input combinational logic loop - AstNode* const icoLoopp - = createInputCombLoop(netlistp, initp, senExprBuilder, logicReplicas.m_ico); + AstNode* const icoLoopp = createInputCombLoop(netlistp, initp, senExprBuilder, + logicReplicas.m_ico, virtIfaceTriggers); if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-ico"); // Step 8: Create the pre/act/nba triggers @@ -1157,6 +1198,10 @@ void schedule(AstNetlist* netlistp) { const size_t dpiExportTriggerIndex = dpiExportTriggerVscp ? extraTriggers.allocate("DPI export trigger") : std::numeric_limits::max(); + const size_t firstVifTriggerIndex = extraTriggers.size(); + for (const auto& p : virtIfaceTriggers) { + extraTriggers.allocate("virtual interface: " + p.first->name()); + } const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre, // &logicRegions.m_act, // @@ -1171,8 +1216,9 @@ 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); } + addVirtIfaceTriggerAssignments(virtIfaceTriggers, firstVifTriggerIndex, actTrig); AstVarScope* const actTrigVscp = actTrig.m_vscp; AstVarScope* const preTrigVscp = scopeTopp->createTempLike("__VpreTriggered", actTrigVscp); @@ -1224,12 +1270,19 @@ void schedule(AstNetlist* netlistp) { ? createTriggerSenTree(netlistp, actTrig.m_vscp, dpiExportTriggerIndex) : nullptr; + const auto& vifTriggeredAct + = virtIfaceTriggers.makeIfaceToSensMap(netlistp, firstVifTriggerIndex, actTrig.m_vscp); + 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"); @@ -1253,6 +1306,8 @@ void schedule(AstNetlist* netlistp) { = dpiExportTriggerVscp ? createTriggerSenTree(netlistp, trigVscp, dpiExportTriggerIndex) : nullptr; + const auto& vifTriggered + = virtIfaceTriggers.makeIfaceToSensMap(netlistp, firstVifTriggerIndex, trigVscp); const auto& timingDomains = timingKit.remapDomains(trigMap); AstCFunc* const funcp = V3Order::order( @@ -1261,6 +1316,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 diff --git a/src/V3Sched.h b/src/V3Sched.h index fca742de4..616b435ca 100644 --- a/src/V3Sched.h +++ b/src/V3Sched.h @@ -149,6 +149,27 @@ public: TimingKit& operator=(TimingKit&&) = default; }; +class VirtIfaceTriggers final { + using IfaceTrigger = std::pair; + using IfaceTriggerVec = std::vector; + using IfaceSensMap = std::map; + IfaceTriggerVec m_triggers; + +public: + void emplace_back(IfaceTrigger&& p) { m_triggers.emplace_back(std::move(p)); } + IfaceTriggerVec::const_iterator begin() const { return m_triggers.begin(); } + IfaceTriggerVec::const_iterator end() const { return m_triggers.end(); } + IfaceSensMap makeIfaceToSensMap(AstNetlist* netlistp, size_t vifTriggerIndex, + AstVarScope* trigVscp) const; + VL_UNCOPYABLE(VirtIfaceTriggers); + VirtIfaceTriggers() = default; + VirtIfaceTriggers(VirtIfaceTriggers&&) = default; + VirtIfaceTriggers& operator=(VirtIfaceTriggers&&) = default; +}; + +// Creates trigger vars for signals driven via virtual interfaces +VirtIfaceTriggers makeVirtIfaceTriggers(AstNetlist* nodep) VL_MT_DISABLED; + // Creates the timing kit and marks variables written by suspendables TimingKit prepareTiming(AstNetlist* const netlistp) VL_MT_DISABLED; diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 9ef1fcdb0..7d72976d6 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -121,7 +121,8 @@ public: : SchedReplicateVertex{graphp} , m_vscp{vscp} { // Top level inputs are - if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi()) { + if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi() + || varp()->sensIfacep()) { addDrivingRegions(INPUT); } // Currently we always execute suspendable processes at the beginning of diff --git a/src/V3SchedVirtIface.cpp b/src/V3SchedVirtIface.cpp new file mode 100644 index 000000000..61ecc3174 --- /dev/null +++ b/src/V3SchedVirtIface.cpp @@ -0,0 +1,234 @@ +// -*- 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 +// +//************************************************************************* +// V3SchedVirtIface'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, AssignPost: +// 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 "V3AstNodeExpr.h" +#include "V3Sched.h" +#include "V3UniqueNames.h" + +VL_DEFINE_DEBUG_FUNCTIONS; + +namespace V3Sched { + +namespace { + +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 + AstAssign* 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 + VirtIfaceTriggers m_triggers; // Interfaces and corresponding trigger vars + + // METHODS + // For each write across a virtual interface boundary + static void foreachWrittenVirtIface(AstNode* const nodep, const OnWriteToVirtIface& onWrite) { + nodep->foreach([&](AstVarRef* const refp) { + if (refp->access().isReadOnly()) return; + if (AstIfaceRefDType* const dtypep = VN_CAST(refp->varp()->dtypep(), IfaceRefDType)) { + if (dtypep->isVirtual() && VN_IS(refp->firstAbovep(), MemberSel)) { + onWrite(refp, dtypep->ifacep()); + } + } else if (AstIface* const ifacep = refp->varp()->sensIfacep()) { + onWrite(refp, ifacep); + } + }); + } + // Returns true if there is a write across a virtual interface boundary + static bool writesToVirtIface(const AstNode* const nodep) { + return nodep->exists([](const AstVarRef* const refp) { + if (!refp->access().isWriteOrRW()) return false; + AstIfaceRefDType* const dtypep = VN_CAST(refp->varp()->dtypep(), IfaceRefDType); + const bool writesToVirtIfaceMember + = (dtypep && dtypep->isVirtual() && VN_IS(refp->firstAbovep(), MemberSel)); + const bool writesToIfaceSensVar = refp->varp()->sensIfacep(); + return writesToVirtIfaceMember || writesToIfaceSensVar; + }); + } + // Error on write across a virtual interface boundary + static void unsupportedWriteToVirtIface(AstNode* nodep, const char* locationp) { + if (!nodep) return; + foreachWrittenVirtIface(nodep, [locationp](AstVarRef* const selp, AstIface*) { + selp->v3warn(E_UNSUPPORTED, + "Unsupported: write to virtual interface in " << locationp); + }); + } + // Create trigger var for the given interface if it doesn't exist; return a write ref to it + AstVarRef* createVirtIfaceTriggerRefp(FileLine* const flp, AstIface* ifacep) { + if (!ifacep->user1()) { + AstScope* const scopeTopp = m_netlistp->topScopep()->scopep(); + AstVarScope* const vscp = scopeTopp->createTemp(m_vifTriggerNames.get(ifacep), 1); + ifacep->user1p(vscp); + m_triggers.emplace_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); + m_trigAssignp = nullptr; + VL_RESTORER(m_trigAssignIfacep); + m_trigAssignIfacep = nullptr; + iterateChildren(nodep); + } + void visit(AstCFunc* nodep) override { + VL_RESTORER(m_trigAssignp); + m_trigAssignp = nullptr; + VL_RESTORER(m_trigAssignIfacep); + m_trigAssignIfacep = nullptr; + iterateChildren(nodep); + } + void visit(AstAssignW* nodep) override { + if (writesToVirtIface(nodep)) { + // Convert to always, as we have to assign the trigger var + nodep->convertToAlways(); + } + } + void visit(AstAssignPost* nodep) override { + if (writesToVirtIface(nodep)) { + // Convert to always, as we have to assign the trigger var + FileLine* const flp = nodep->fileline(); + AstAlwaysPost* const postp = new AstAlwaysPost{flp, nullptr, nullptr}; + nodep->replaceWith(postp); + postp->addStmtsp( + new AstAssign{flp, nodep->lhsp()->unlinkFrBack(), nodep->rhsp()->unlinkFrBack()}); + nodep->deleteTree(); + } + } + void visit(AstNodeIf* nodep) override { + unsupportedWriteToVirtIface(nodep->condp(), "if condition"); + { + VL_RESTORER(m_trigAssignp); + VL_RESTORER(m_trigAssignIfacep); + iterateAndNextNull(nodep->thensp()); + } + { + VL_RESTORER(m_trigAssignp); + VL_RESTORER(m_trigAssignIfacep); + iterateAndNextNull(nodep->elsesp()); + } + if (v3Global.usesTiming()) { + // Clear the trigger assignment, as there could have been timing controls in either + // branch + m_trigAssignp = nullptr; + m_trigAssignIfacep = nullptr; + } + } + void visit(AstWhile* nodep) override { + unsupportedWriteToVirtIface(nodep->precondsp(), "loop condition"); + unsupportedWriteToVirtIface(nodep->condp(), "loop condition"); + unsupportedWriteToVirtIface(nodep->incsp(), "loop increment statement"); + { + VL_RESTORER(m_trigAssignp); + VL_RESTORER(m_trigAssignIfacep); + iterateAndNextNull(nodep->stmtsp()); + } + if (v3Global.usesTiming()) { + // Clear the trigger assignment, as there could have been timing controls in the loop + m_trigAssignp = nullptr; + m_trigAssignIfacep = nullptr; + } + } + void visit(AstJumpBlock* nodep) override { + { + VL_RESTORER(m_trigAssignp); + VL_RESTORER(m_trigAssignIfacep); + iterateChildren(nodep); + } + if (v3Global.usesTiming()) { + // Clear the trigger assignment, as there could have been timing controls in the jump + // block + m_trigAssignp = nullptr; + m_trigAssignIfacep = nullptr; + } + } + void visit(AstNodeStmt* nodep) override { + if (v3Global.usesTiming() + && nodep->exists([](AstNode* nodep) { return nodep->isTimingControl(); })) { + m_trigAssignp = nullptr; // Could be after a delay - need new trigger assignment + m_trigAssignIfacep = nullptr; + // No restorer, as following statements should not reuse the old assignment + } + FileLine* const flp = nodep->fileline(); + foreachWrittenVirtIface(nodep, [&](AstVarRef*, AstIface* ifacep) { + if (ifacep != m_trigAssignIfacep) { + // Write to different interface type than before - need new trigger assignment + // No restorer, as following statements should not reuse the old assignment + m_trigAssignIfacep = ifacep; + m_trigAssignp = nullptr; + } + if (!m_trigAssignp) { + 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; + + // METHODS + VirtIfaceTriggers take_triggers() { return std::move(m_triggers); } +}; + +} //namespace + +VirtIfaceTriggers makeVirtIfaceTriggers(AstNetlist* nodep) { + UINFO(2, __FUNCTION__ << ": " << endl); + if (v3Global.hasVirtIfaces()) { + VirtIfaceVisitor visitor{nodep}; + V3Global::dumpCheckGlobalTree("sched_vif", 0, dumpTreeLevel() >= 6); + return visitor.take_triggers(); + } + return {}; +} + +} //namespace V3Sched diff --git a/src/V3Width.cpp b/src/V3Width.cpp index b5186cc4d..da7c69aea 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/verilog.y b/src/verilog.y index 82d80520f..5769ba7f0 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -2134,12 +2134,22 @@ 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); + $$ = ifrefp; } + | id/*interface*/ '.' id/*modport*/ + { AstIfaceRefDType* const ifrefp = new AstIfaceRefDType{$1, $3, "", *$1, *$3}; + ifrefp->isVirtual(true); + $$ = ifrefp; } | id/*interface*/ parameter_value_assignmentClass - { $$ = new AstIfaceRefDType{$1, nullptr, "", *$1, "", $2}; } + { AstIfaceRefDType* const ifrefp = new AstIfaceRefDType{$1, nullptr, "", *$1, "", $2}; + ifrefp->isVirtual(true); + $$ = 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); + $$ = ifrefp; } ; data_type_or_void: // ==IEEE: data_type_or_void diff --git a/test_regress/t/t_interface_virtual_controlflow.out b/test_regress/t/t_interface_virtual_controlflow.out new file mode 100644 index 000000000..303bceb13 --- /dev/null +++ b/test_regress/t/t_interface_virtual_controlflow.out @@ -0,0 +1,19 @@ +[0] vif1.data==0000 +[0] intf2.data==0000 +[0] vif4.data==0000 +[10] intf2.data==beef +[20] vif1.data==dead +[20] vif4.data==face +[30] intf2.data==beef +[40] vif1.data==dead +[40] vif4.data==face +[50] intf2.data==beef +[60] vif1.data==dead +[60] vif4.data==face +[70] intf2.data==beef +[80] intf2.data==beef +[80] vif4.data==cafe +[90] intf2.data==beef +[100] intf2.data==beef +[100] vif4.data==deaf +*-* All Finished *-* diff --git a/test_regress/t/t_interface_virtual_controlflow.pl b/test_regress/t/t_interface_virtual_controlflow.pl new file mode 100755 index 000000000..3b4aae630 --- /dev/null +++ b/test_regress/t/t_interface_virtual_controlflow.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 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( + verilator_flags2 => ["-fno-reorder"], + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual_controlflow.v b/test_regress/t/t_interface_virtual_controlflow.v new file mode 100644 index 000000000..4e5dd4cbd --- /dev/null +++ b/test_regress/t/t_interface_virtual_controlflow.v @@ -0,0 +1,67 @@ +// 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(), intf4(); + virtual Bus1 vif1 = intf1; + virtual Bus2 vif2 = intf2; + virtual Bus3 vif3 = intf3, vif4 = intf4; + + // Finish on negedge so that $finish is last + always @(negedge clk) + if (cyc >= 10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + function void assign_to_intf3(); + if ($c("1")) return; + intf3.data = 'hcafe; + endfunction + + always @(posedge clk) begin + logic foo = 1; + cyc <= cyc + 1; + if (cyc == 1 || cyc == 3 || cyc == 5) intf1.data = 'hdead; + else vif2.data = 'hbeef; + if (cyc == 1 || cyc == 3 || cyc == 5) begin + if (cyc >= 3) $c("// dummy statement"); + else intf3.data = 'hfafa; + intf4.data = 'hface; + end + if (cyc == 7) begin + while ($c("0")) begin + foo = 0; + intf3.data = 'hbebe; + end + intf4.data = 'hcafe; + end + if (cyc == 9) begin + assign_to_intf3; + intf4.data = 'hdeaf; + end + end + + always_comb $write("[%0t] vif1.data==%h\n", $time, vif1.data); + always_comb $write("[%0t] intf2.data==%h\n", $time, intf2.data); + always_comb $write("[%0t] vif4.data==%h\n", $time, vif4.data); +endmodule diff --git a/test_regress/t/t_interface_virtual_sched_act.out b/test_regress/t/t_interface_virtual_sched_act.out new file mode 100644 index 000000000..10090eb57 --- /dev/null +++ b/test_regress/t/t_interface_virtual_sched_act.out @@ -0,0 +1,12 @@ +[0] data==0000 +[0] data==0000 +[10] data==0000 +[20] data==dead +[20] data==beef +[20] data==beef +[30] data==beef +[40] data==face +[40] data==cafe +[40] data==cafe +[50] data==cafe +*-* All Finished *-* diff --git a/test_regress/t/t_interface_virtual_sched_act.pl b/test_regress/t/t_interface_virtual_sched_act.pl new file mode 100755 index 000000000..6247bd126 --- /dev/null +++ b/test_regress/t/t_interface_virtual_sched_act.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_sched_act.v b/test_regress/t/t_interface_virtual_sched_act.v new file mode 100644 index 000000000..258ac1620 --- /dev/null +++ b/test_regress/t/t_interface_virtual_sched_act.v @@ -0,0 +1,40 @@ +// 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 Bus; + logic [15:0] data; +endinterface + +module t ( + clk +); + input clk; + integer cyc = 0; + Bus intf(); + virtual Bus vif = intf; + logic [15:0] data; + + always @(posedge clk) begin + cyc <= cyc + 1; + end + + // Finish on negedge so that $finish is last + always @(negedge clk) + if (cyc >= 5) begin + $write("*-* All Finished *-*\n"); + $finish; + end + + always @(posedge clk or data) begin + if (cyc == 1) intf.data <= 'hdead; + else if (cyc == 2) intf.data <= 'hbeef; + else if (cyc == 3) intf.data <= 'hface; + else if (cyc == 4) intf.data <= 'hcafe; + end + + assign data = vif.data; + always_comb $write("[%0t] data==%h\n", $time, data); +endmodule diff --git a/test_regress/t/t_interface_virtual_sched_ico.cpp b/test_regress/t/t_interface_virtual_sched_ico.cpp new file mode 100644 index 000000000..aac1bebaa --- /dev/null +++ b/test_regress/t/t_interface_virtual_sched_ico.cpp @@ -0,0 +1,41 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// +// Copyright 2023 by Geza Lore. 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 +// +//************************************************************************* + +#include "verilated.h" + +#include "Vt_interface_virtual_sched_ico.h" +#include "Vt_interface_virtual_sched_ico__Syms.h" + +#include + +int main(int argc, char** argv) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->debug(0); + contextp->commandArgs(argc, argv); + srand48(5); + + const std::unique_ptr topp{new VM_PREFIX}; + topp->inc = 1; + topp->clk = false; + topp->eval(); + + while (!contextp->gotFinish() && contextp->time() < 100000) { + contextp->timeInc(5); + if (topp->clk) topp->inc += 1; + topp->clk = !topp->clk; + topp->eval(); + } + + if (!contextp->gotFinish()) { + vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish"); + } + return 0; +} diff --git a/test_regress/t/t_interface_virtual_sched_ico.out b/test_regress/t/t_interface_virtual_sched_ico.out new file mode 100644 index 000000000..6f7519233 --- /dev/null +++ b/test_regress/t/t_interface_virtual_sched_ico.out @@ -0,0 +1,25 @@ +[0] intf1.inc==0 +[0] vif2.inc==0 +[0] intf1.inc==1 +[0] vif2.inc==1 +[0] intf1.inc==1 +[0] vif2.inc==1 +[5] intf1.inc==1 +[5] vif2.inc==1 +[10] intf1.inc==2 +[10] vif2.inc==2 +[15] intf1.inc==2 +[15] vif2.inc==2 +[20] intf1.inc==3 +[20] vif2.inc==3 +[25] intf1.inc==3 +[25] vif2.inc==3 +[30] intf1.inc==4 +[30] vif2.inc==4 +[35] intf1.inc==4 +[35] vif2.inc==4 +[40] intf1.inc==5 +[40] vif2.inc==5 +[45] intf1.inc==5 +[45] vif2.inc==5 +*-* All Finished *-* diff --git a/test_regress/t/t_interface_virtual_sched_ico.pl b/test_regress/t/t_interface_virtual_sched_ico.pl new file mode 100755 index 000000000..1f4f8b7ca --- /dev/null +++ b/test_regress/t/t_interface_virtual_sched_ico.pl @@ -0,0 +1,24 @@ +#!/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(vlt_all => 1); + +compile( + make_main => 0, + v_flags2 => ["--exe", "$Self->{t_dir}/$Self->{name}.cpp"], + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual_sched_ico.v b/test_regress/t/t_interface_virtual_sched_ico.v new file mode 100644 index 000000000..f3a85a298 --- /dev/null +++ b/test_regress/t/t_interface_virtual_sched_ico.v @@ -0,0 +1,39 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2023 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +interface If; + logic [31:0] inc; +endinterface + +module top ( + clk, + inc +); + + input clk; + input [31:0] inc; + int cyc = 0; + + If intf1(); + If intf2(); + virtual If vif1 = intf1; + virtual If vif2 = intf2; + + assign vif1.inc = inc; + assign intf2.inc = inc; + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc >= 4) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_comb $write("[%0t] intf1.inc==%0h\n", $time, intf1.inc); + always_comb $write("[%0t] vif2.inc==%0h\n", $time, vif2.inc); + +endmodule diff --git a/test_regress/t/t_interface_virtual_sched_nba.out b/test_regress/t/t_interface_virtual_sched_nba.out new file mode 100644 index 000000000..abde98039 --- /dev/null +++ b/test_regress/t/t_interface_virtual_sched_nba.out @@ -0,0 +1,21 @@ +[0] intf1.data==0000 +[0] intf2.data==0000 +[0] vif3.data==0000 +[0] intf2.data==0000 +[10] intf2.data==0000 +[10] vif3.data==0000 +[20] intf1.data==dead +[20] intf2.data==0000 +[20] vif3.data==0000 +[30] intf2.data==dead +[30] vif3.data==0000 +[40] intf1.data==beef +[40] intf2.data==dead +[40] vif3.data==0000 +[50] intf2.data==beef +[50] vif3.data==0000 +[60] intf2.data==beef +[60] vif3.data==face +[70] intf2.data==beef +[70] vif3.data==cafe +*-* All Finished *-* diff --git a/test_regress/t/t_interface_virtual_sched_nba.pl b/test_regress/t/t_interface_virtual_sched_nba.pl new file mode 100755 index 000000000..6247bd126 --- /dev/null +++ b/test_regress/t/t_interface_virtual_sched_nba.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_sched_nba.v b/test_regress/t/t_interface_virtual_sched_nba.v new file mode 100644 index 000000000..dc610aa50 --- /dev/null +++ b/test_regress/t/t_interface_virtual_sched_nba.v @@ -0,0 +1,60 @@ +// 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) + vif1.data = 'hdead; + else if (cyc == 2) + data = vif1.data; + else if (cyc == 3) + vif1.data = 'hbeef; + else if (cyc == 4) + data = vif1.data; + else if (cyc == 5) + intf3.data <= 'hface; + else if (cyc == 6) + intf3.data <= 'hcafe; + 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_timing.out b/test_regress/t/t_interface_virtual_timing.out new file mode 100644 index 000000000..2ca5ac2af --- /dev/null +++ b/test_regress/t/t_interface_virtual_timing.out @@ -0,0 +1,29 @@ +vif1.data==dead +intf2.data==0000 +vif1.data==dead +intf2.data==0000 +intf2.data==beef +vif1.data==dead +intf2.data==beef +intf2.data==beef +vif1.data==cafe +intf2.data==beef +intf2.data==face +vif1.data==cafe +intf2.data==face +intf2.data==face +vif1.data==feed +intf2.data==face +intf2.data==deed +vif1.data==feed +intf2.data==deed +intf2.data==deed +vif1.data==deaf +intf2.data==deed +intf2.data==fafa +vif1.data==deaf +intf2.data==fafa +intf2.data==fafa +vif1.data==bebe +intf2.data==fafa +*-* All Finished *-* diff --git a/test_regress/t/t_interface_virtual_timing.pl b/test_regress/t/t_interface_virtual_timing.pl new file mode 100755 index 000000000..0da9620d1 --- /dev/null +++ b/test_regress/t/t_interface_virtual_timing.pl @@ -0,0 +1,24 @@ +#!/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( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_interface_virtual_timing.v b/test_regress/t/t_interface_virtual_timing.v new file mode 100644 index 000000000..258c7d8f6 --- /dev/null +++ b/test_regress/t/t_interface_virtual_timing.v @@ -0,0 +1,42 @@ +// 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 Bus; + logic [15:0] data; +endinterface + +module t; + Bus intf1(), intf2(); + virtual Bus vif1 = intf1, vif2 = intf2; + + task assign_to_vif2(); + if ($c("0")) return; + #1 vif2.data = 'hfafa; #1; + endtask + + initial forever begin + intf1.data = 'hdead; + if ($c("1")) begin + #1 vif2.data = 'hbeef; #1; + end + intf1.data = 'hcafe; + if ($c("0")); else begin + #1 vif2.data = 'hface; #1; + end + intf1.data = 'hfeed; + while ($time < 5) begin + #1 vif2.data = 'hdeed; #1; + end + intf1.data = 'hdeaf; + assign_to_vif2; + intf1.data = 'hbebe; + #1 $write("*-* All Finished *-*\n"); + $finish; + end + + always_comb if ($time < 9) $write("vif1.data==%h\n", vif1.data); + always_comb if ($time < 9) $write("intf2.data==%h\n", intf2.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 000000000..aec39ddb7 --- /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 if condition + 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 loop condition + 23 | while (write_data(vif.data)); + | ^~~ +%Error-UNSUPPORTED: t/t_interface_virtual_unsup.v:24:34: Unsupported: write to virtual interface in loop condition + 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 loop increment statement + 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 loop condition + 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 000000000..bd07fc421 --- /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 000000000..27870a35f --- /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