// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Create triggers necessary for scheduling across // virtual interfaces // // Code available from: https://verilator.org // //************************************************************************* // // This program is free software; you can redistribute it and/or modify it // under the terms of either the GNU Lesser General Public License Version 3 // or the Perl Artistic License Version 2.0. // SPDX-FileCopyrightText: 2003-2026 Wilson Snyder // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* // V3SchedVirtIface's Transformations: // // Identify interface members written through virtual interface variables (VIF writes). // For each such member, collect all VarScopes across all instantiations of that // interface type. Each VarScope gets its own value-change trigger in the scheduler. // // Also disables lifetime optimization for variables in AlwaysPost blocks that // write through virtual interfaces. // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3AstNodeExpr.h" #include "V3Sched.h" VL_DEFINE_DEBUG_FUNCTIONS; namespace V3Sched { namespace { class VirtIfaceVisitor final : public VNVisitor { private: // STATE using IfaceMember = std::pair; using IfaceCallable = std::pair; // Set of (iface, member) pairs written through VIF -- defines which members need triggers std::set m_vifWrittenMembers; // All candidate VarScopes of interface members (keyed by interface type + member name) std::map> m_candidateVscps; std::set> m_seenCandidateVscps; // VarScope index and callable worklist for VIF method-body writes. std::map> m_vscpsByVar; std::vector m_reachableIfaceCallables; std::set m_seenReachableIfaceCallables; VirtIfaceTriggers m_triggers; // METHODS // Returns true if statement writes across a virtual interface boundary static bool writesToVirtIface(const AstNode* const nodep) { return nodep->exists([](const AstMemberSel* const memberSelp) { if (!memberSelp->access().isWriteOrRW()) return false; AstIfaceRefDType* const dtypep = VN_CAST(memberSelp->fromp()->dtypep()->skipRefp(), IfaceRefDType); return dtypep && dtypep->isVirtual(); }); } // VISITORS void visit(AstMemberSel* nodep) override { // Detect writes through VIF: the MemberSel's fromp resolves to a virtual interface type. // Handles both direct VIF access (vif.member) and class chain (obj.vif.member). if (nodep->access().isWriteOrRW()) { AstIfaceRefDType* const dtypep = VN_CAST(nodep->fromp()->dtypep()->skipRefp(), IfaceRefDType); if (dtypep && dtypep->isVirtual()) { m_vifWrittenMembers.emplace(dtypep->ifacep(), nodep->varp()->name()); } } iterateChildren(nodep); } void visit(AstCMethodCall* nodep) override { if (const AstIfaceRefDType* const dtypep = VN_CAST(nodep->fromp()->dtypep()->skipRefp(), IfaceRefDType)) { if (VL_UNCOVERABLE(!dtypep->isVirtual())) { // Concrete interface method calls are lowered before this pass. } else { addReachableIfaceCallable(dtypep->ifaceViaCellp(), nodep->funcp()); } } iterateChildren(nodep); } void visit(AstVarScope* nodep) override { // Collect candidate VarScopes. sensIfacep() is set on interface members // accessed via any MemberSel (virtual or non-virtual). AstVar* const varp = nodep->varp(); if (varp->isTemp()) return; m_vscpsByVar[varp].push_back(nodep); if (const AstIface* const ifacep = varp->sensIfacep()) addCandidateVscp(ifacep, nodep); } void visit(AstNodeProcedure* nodep) override { // Disable lifetime optimization for variables in AlwaysPost blocks // that write to virtual interface members, as VIF reads may observe them if (VN_IS(nodep, AlwaysPost) && writesToVirtIface(nodep)) { nodep->foreach([](AstVarRef* refp) { refp->varScopep()->optimizeLifePost(false); }); } iterateChildren(nodep); } void visit(AstNode* nodep) override { iterateChildren(nodep); } void addCandidateVscp(const AstIface* const ifacep, AstVarScope* const vscp) { const IfaceMember member{ifacep, vscp->varp()->name()}; if (m_seenCandidateVscps.emplace(member, vscp).second) m_candidateVscps[member].push_back(vscp); } void addReachableIfaceCallable(AstIface* const ifacep, AstCFunc* const funcp) { const IfaceCallable callable{ifacep, funcp}; if (m_seenReachableIfaceCallables.emplace(callable).second) { m_reachableIfaceCallables.push_back(callable); } } // Build final trigger list by intersecting VIF writes with candidate VarScopes void buildTriggers() { for (const auto& written : m_vifWrittenMembers) { const auto it = m_candidateVscps.find(written); if (it == m_candidateVscps.end()) continue; for (AstVarScope* const vscp : it->second) { const AstIface* const ifacep = written.first; m_triggers.addTrigger(ifacep, vscp->varp(), vscp); } } } public: // CONSTRUCTORS explicit VirtIfaceVisitor(AstNetlist* nodep) { iterate(nodep); for (size_t i = 0; i < m_reachableIfaceCallables.size(); ++i) { const IfaceCallable callable = m_reachableIfaceCallables[i]; callable.second->foreach([this, callable](AstNodeExpr* const nodep) { if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) { // Only persistent interface storage is observable through a VIF read. UASSERT_OBJ(refp->varScopep(), refp, "No var scope"); AstVar* const varp = refp->varp(); if (!refp->access().isWriteOrRW() || varp->isFuncLocal() || varp->isTemp() || varp->isEvent() || !VN_IS(refp->varScopep()->scopep()->modp(), Iface)) { return; } varp->sensIfacep(callable.first); m_vifWrittenMembers.emplace(callable.first, varp->name()); const auto it = m_vscpsByVar.find(varp); UASSERT_OBJ(it != m_vscpsByVar.end(), varp, "No VarScope for interface member"); for (AstVarScope* const vscp : it->second) addCandidateVscp(callable.first, vscp); } else if (AstNodeCCall* const callp = VN_CAST(nodep, NodeCCall)) { addReachableIfaceCallable(callable.first, callp->funcp()); } }); } buildTriggers(); } ~VirtIfaceVisitor() override = default; // METHODS VirtIfaceTriggers take_triggers() { return std::move(m_triggers); } }; } //namespace VirtIfaceTriggers makeVirtIfaceTriggers(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); VirtIfaceTriggers triggers{}; if (v3Global.hasVirtIfaces()) { triggers = VirtIfaceVisitor{nodep}.take_triggers(); // Dump after destructor so VNDeleter runs V3Global::dumpCheckGlobalTree("sched_vif", 0, dumpTreeEitherLevel() >= 6); } return triggers; } } //namespace V3Sched