183 lines
7.8 KiB
C++
183 lines
7.8 KiB
C++
// -*- 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<const AstIface*, std::string>;
|
|
using IfaceCallable = std::pair<AstIface*, AstCFunc*>;
|
|
|
|
// Set of (iface, member) pairs written through VIF -- defines which members need triggers
|
|
std::set<IfaceMember> m_vifWrittenMembers;
|
|
// All candidate VarScopes of interface members (keyed by interface type + member name)
|
|
std::map<IfaceMember, std::vector<AstVarScope*>> m_candidateVscps;
|
|
std::set<std::pair<IfaceMember, AstVarScope*>> m_seenCandidateVscps;
|
|
// VarScope index and callable worklist for VIF method-body writes.
|
|
std::map<const AstVar*, std::vector<AstVarScope*>> m_vscpsByVar;
|
|
std::vector<IfaceCallable> m_reachableIfaceCallables;
|
|
std::set<IfaceCallable> 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
|