From 54fab1dbe9655489084a33f013821a87f66c3130 Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Tue, 7 Nov 2023 11:25:32 +0100 Subject: [PATCH] Add extra triggers for virtual interfaces Signed-off-by: Krzysztof Bieganski --- src/CMakeLists.txt | 2 + src/Makefile_obj.in | 1 + src/V3AstNodeDType.h | 3 + src/V3AstNodeOther.h | 10 +- src/V3AstNodes.cpp | 1 + src/V3Const.cpp | 2 +- src/V3Dead.cpp | 2 +- src/V3Gate.cpp | 2 +- src/V3Global.h | 3 + src/V3Life.cpp | 4 +- src/V3Localize.cpp | 2 +- src/V3Sched.cpp | 43 ++++- src/V3SchedReplicate.cpp | 1 + src/V3VirtIface.cpp | 177 +++++++++++++++++++ src/V3VirtIface.h | 35 ++++ src/V3Width.cpp | 4 +- src/Verilator.cpp | 6 + src/verilog.y | 22 ++- test_regress/t/t_interface_virtual_trig.out | 16 ++ test_regress/t/t_interface_virtual_trig.pl | 22 +++ test_regress/t/t_interface_virtual_trig.v | 59 +++++++ test_regress/t/t_interface_virtual_unsup.out | 17 ++ test_regress/t/t_interface_virtual_unsup.pl | 23 +++ test_regress/t/t_interface_virtual_unsup.v | 28 +++ 24 files changed, 463 insertions(+), 22 deletions(-) create mode 100644 src/V3VirtIface.cpp create mode 100644 src/V3VirtIface.h create mode 100644 test_regress/t/t_interface_virtual_trig.out create mode 100755 test_regress/t/t_interface_virtual_trig.pl create mode 100644 test_regress/t/t_interface_virtual_trig.v create mode 100644 test_regress/t/t_interface_virtual_unsup.out create mode 100755 test_regress/t/t_interface_virtual_unsup.pl create mode 100644 test_regress/t/t_interface_virtual_unsup.v diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fdf2150820e..b32d795453e 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 c4da4ad264b..fadc0257b81 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -819,6 +819,7 @@ class AstIfaceRefDType final : public AstNodeDType { // Reference to an interface, either for a port, or inside parent cell // @astgen op1 := paramsp : List[AstPin] private: + 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 @@ -861,6 +862,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 23aaadb2af7..7353ab1ce8d 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1198,6 +1198,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 + std::map + 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 @@ -1228,6 +1230,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; } + std::map& virtIfaceTriggerps() { return m_virtIfaceTriggerps; } AstVar* delaySchedulerp() const { return m_delaySchedulerp; } void delaySchedulerp(AstVar* const varScopep) { m_delaySchedulerp = varScopep; } AstVarScope* nbaEventp() const { return m_nbaEventp; } @@ -1680,6 +1683,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) @@ -1695,7 +1699,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 @@ -1737,7 +1740,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; @@ -1866,6 +1868,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; } @@ -1876,7 +1879,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) { @@ -1969,7 +1971,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; @@ -1997,6 +1998,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 76ea7d7f40d..8e1d8abb7c5 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1941,6 +1941,7 @@ const char* AstNetlist::broken() const { BROKEN_RTN(m_dollarUnitPkgp && !m_dollarUnitPkgp->brokeExists()); BROKEN_RTN(m_evalp && !m_evalp->brokeExists()); BROKEN_RTN(m_dpiExportTriggerp && !m_dpiExportTriggerp->brokeExists()); + //BROKEN_RTN(m_virtIfaceTriggerp && !m_virtIfaceTriggerp->brokeExists()); BROKEN_RTN(m_topScopep && !m_topScopep->brokeExists()); BROKEN_RTN(m_delaySchedulerp && !m_delaySchedulerp->brokeExists()); BROKEN_RTN(m_nbaEventp && !m_nbaEventp->brokeExists()); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index de342d9838c..21ad256f345 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2692,7 +2692,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 768887b5fc9..7cefea95908 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -357,7 +357,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 6560dc0544c..c003d674771 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -372,7 +372,7 @@ class GateVisitor final : public VNVisitor { UINFO(6, "New vertex " << varscp << endl); vertexp = new GateVarVertex{&m_graph, m_scopep, varscp}; varscp->user1p(vertexp); - if (varscp->varp()->isUsedVirtIface()) { + if (varscp->varp()->sensIfacep()) { // Can be used in a class method, which cannot be tracked statically vertexp->clearReducibleAndDedupable("VirtIface"); vertexp->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 8213656236a..6155e973deb 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 d26a8617585..4f8b6ea4d08 100644 --- a/src/V3Localize.cpp +++ b/src/V3Localize.cpp @@ -167,7 +167,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..22e87ecdeea 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 @@ -1150,6 +1150,7 @@ void schedule(AstNetlist* netlistp) { // Step 8: Create the pre/act/nba triggers AstVarScope* const dpiExportTriggerVscp = netlistp->dpiExportTriggerp(); + std::vector> virtIfaceTriggerIndices; // We may have an extra trigger for variable updated in DPI exports ExtraTriggers extraTriggers; @@ -1157,6 +1158,11 @@ void schedule(AstNetlist* netlistp) { ? extraTriggers.allocate("DPI export trigger") : std::numeric_limits::max(); + for (auto p : netlistp->virtIfaceTriggerps()) { + const size_t virtIfaceTriggerIndex = extraTriggers.allocate("Virtual interface trigger"); + virtIfaceTriggerIndices.push_back(std::make_pair(p.first, virtIfaceTriggerIndex)); + } + const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre, // &logicRegions.m_act, // &logicRegions.m_nba, // @@ -1170,7 +1176,10 @@ 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); + } + for (auto p : virtIfaceTriggerIndices) { + actTrig.addExtraTriggerAssignment(netlistp->virtIfaceTriggerps().at(p.first), p.second); } AstVarScope* const actTrigVscp = actTrig.m_vscp; @@ -1223,12 +1232,22 @@ void schedule(AstNetlist* netlistp) { ? createTriggerSenTree(netlistp, actTrig.m_vscp, dpiExportTriggerIndex) : nullptr; + std::map virtIfaceTriggeredAct; + for (const auto& p : virtIfaceTriggerIndices) { + virtIfaceTriggeredAct.insert( + std::make_pair(p.first, createTriggerSenTree(netlistp, actTrig.m_vscp, p.second))); + } + 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()) { + auto it = virtIfaceTriggeredAct.find(vscp->varp()->sensIfacep()); + if (it != virtIfaceTriggeredAct.end()) out.push_back(it->second); + } }); splitCheck(actFuncp); if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-act"); @@ -1252,6 +1271,11 @@ void schedule(AstNetlist* netlistp) { = dpiExportTriggerVscp ? createTriggerSenTree(netlistp, trigVscp, dpiExportTriggerIndex) : nullptr; + std::map virtIfaceTriggered; + for (const auto& p : virtIfaceTriggerIndices) { + virtIfaceTriggered.insert( + std::make_pair(p.first, createTriggerSenTree(netlistp, trigVscp, p.second))); + } const auto& timingDomains = timingKit.remapDomains(trigMap); AstCFunc* const funcp = V3Order::order( @@ -1260,6 +1284,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()) { + auto it = virtIfaceTriggeredAct.find(vscp->varp()->sensIfacep()); + if (it != virtIfaceTriggeredAct.end()) out.push_back(it->second); + } }); // Create the trigger dumping function, which is the same as act trigger @@ -1314,6 +1342,7 @@ void schedule(AstNetlist* netlistp) { splitCheck(initp); netlistp->dpiExportTriggerp(nullptr); + netlistp->virtIfaceTriggerps().clear(); V3Global::dumpCheckGlobalTree("sched", 0, dumpTreeLevel() >= 3); } diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 9ef1fcdb0e9..945c5058127 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -128,6 +128,7 @@ class SchedReplicateVarVertex final : public SchedReplicateVertex { // the act region, which means combinational logic driven from a suspendable // processes must be present in the 'act' region if (varp()->isWrittenBySuspendable()) addDrivingRegions(ACTIVE); + if (varp()->sensIfacep()) addDrivingRegions(ACTIVE); } AstVarScope* vscp() const { return m_vscp; } AstVar* varp() const { return m_vscp->varp(); } diff --git a/src/V3VirtIface.cpp b/src/V3VirtIface.cpp new file mode 100644 index 00000000000..d66f2d8394f --- /dev/null +++ b/src/V3VirtIface.cpp @@ -0,0 +1,177 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Create separate tasks for forked processes that +// can outlive their parents +// +// 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().insert(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 (!VN_IS(nodep, NodeAssign) // + || !VN_IS(nodep, StmtExpr) // + || nodep->isTimingControl()) { + m_trigAssignp = nullptr; + } + 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->addHereThisAsNext(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..9574796f76d --- /dev/null +++ b/src/V3VirtIface.h @@ -0,0 +1,35 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//************************************************************************* +// DESCRIPTION: Verilator: Create separate tasks for VirtIfaceed processes that +// can outlive their parents +// +// 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 a9922e57cb6..7758731ac2c 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2723,7 +2723,9 @@ 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); + VN_AS(nodep->fromp(), VarRef)->varp()->sensIfacep(ifacep); return; } UINFO(1, "found object " << foundp << endl); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 2d23c0d5d3c..d89b66c50ec 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 3a274eba359..c4c7958b1b4 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