2023-12-05 04:11:07 +01:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// DESCRIPTION: Verilator: Create triggers necessary for scheduling across
|
|
|
|
|
// virtual interfaces
|
|
|
|
|
//
|
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2025-01-01 14:30:25 +01:00
|
|
|
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
|
2023-12-05 04:11:07 +01:00
|
|
|
// 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 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
|
2025-12-22 09:52:44 +01:00
|
|
|
// AstVar::user1() -> AstIface*. Interface under which variable is
|
2023-12-05 04:11:07 +01:00
|
|
|
const VNUser1InUse m_user1InUse;
|
|
|
|
|
|
|
|
|
|
// TYPES
|
|
|
|
|
using OnWriteToVirtIface = std::function<void(AstVarRef*, AstIface*)>;
|
2025-07-12 03:04:51 +02:00
|
|
|
using OnWriteToVirtIfaceMember
|
|
|
|
|
= std::function<void(AstVarRef*, AstIface*, const std::string&)>;
|
2023-12-05 04:11:07 +01:00
|
|
|
|
|
|
|
|
// STATE
|
|
|
|
|
AstNetlist* const m_netlistp; // Root node
|
|
|
|
|
V3UniqueNames m_vifTriggerNames{"__VvifTrigger"}; // Unique names for virt iface
|
|
|
|
|
// triggers
|
|
|
|
|
VirtIfaceTriggers m_triggers; // Interfaces and corresponding trigger vars
|
2025-11-26 13:12:20 +01:00
|
|
|
AstNodeStmt* m_curStmt = nullptr; // Current statement
|
2023-12-05 04:11:07 +01:00
|
|
|
|
|
|
|
|
// 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,
|
2025-10-14 02:30:47 +02:00
|
|
|
"Unsupported: Write to virtual interface in " << locationp);
|
2023-12-05 04:11:07 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-12 03:04:51 +02:00
|
|
|
// Create trigger reference for a specific interface member
|
|
|
|
|
AstVarRef* createVirtIfaceMemberTriggerRefp(FileLine* const flp, AstIface* ifacep,
|
|
|
|
|
const AstVar* memberVarp) {
|
|
|
|
|
// Check if we already have a trigger for this specific member
|
|
|
|
|
AstVarScope* existingTrigger = m_triggers.findMemberTrigger(ifacep, memberVarp);
|
|
|
|
|
if (!existingTrigger) {
|
|
|
|
|
AstScope* const scopeTopp = m_netlistp->topScopep()->scopep();
|
|
|
|
|
// Create a unique name for this member trigger
|
|
|
|
|
const std::string triggerName
|
|
|
|
|
= m_vifTriggerNames.get(ifacep) + "_Vtrigm_" + memberVarp->name();
|
|
|
|
|
AstVarScope* const vscp = scopeTopp->createTemp(triggerName, 1);
|
|
|
|
|
m_triggers.addMemberTrigger(ifacep, memberVarp, vscp);
|
|
|
|
|
existingTrigger = vscp;
|
|
|
|
|
}
|
|
|
|
|
return new AstVarRef{flp, existingTrigger, VAccess::WRITE};
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-05 04:11:07 +01:00
|
|
|
// VISITORS
|
|
|
|
|
void visit(AstNodeProcedure* nodep) override {
|
2025-08-19 10:27:59 +02:00
|
|
|
// Not sure if needed, but be paranoid to match previous behavior as didn't optimize
|
|
|
|
|
// before ..
|
|
|
|
|
if (VN_IS(nodep, AlwaysPost) && writesToVirtIface(nodep)) {
|
|
|
|
|
nodep->foreach([](AstVarRef* refp) { refp->varScopep()->optimizeLifePost(false); });
|
|
|
|
|
}
|
2023-12-05 04:11:07 +01:00
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
2025-12-17 12:51:01 +01:00
|
|
|
void visit(AstNodeIf* const nodep) override {
|
2023-12-05 04:11:07 +01:00
|
|
|
unsupportedWriteToVirtIface(nodep->condp(), "if condition");
|
2025-12-17 12:51:01 +01:00
|
|
|
iterateChildren(nodep);
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
2025-12-17 12:51:01 +01:00
|
|
|
void visit(AstLoop* const nodep) override {
|
2025-09-29 16:25:25 +02:00
|
|
|
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
|
2025-12-17 12:51:01 +01:00
|
|
|
iterateChildren(nodep);
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
2025-09-29 16:25:25 +02:00
|
|
|
void visit(AstLoopTest* nodep) override {
|
|
|
|
|
unsupportedWriteToVirtIface(nodep->condp(), "loop condition");
|
|
|
|
|
}
|
2023-12-05 04:11:07 +01:00
|
|
|
void visit(AstNodeStmt* nodep) override {
|
2025-11-26 13:12:20 +01:00
|
|
|
VL_RESTORER(m_curStmt);
|
|
|
|
|
m_curStmt = nodep;
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstVarRef* const nodep) override {
|
|
|
|
|
if (!m_curStmt) return;
|
|
|
|
|
if (nodep->access().isReadOnly()) return;
|
|
|
|
|
AstIface* ifacep = nullptr;
|
|
|
|
|
AstVar* memberVarp = nullptr;
|
|
|
|
|
if (AstIfaceRefDType* const dtypep = VN_CAST(nodep->varp()->dtypep(), IfaceRefDType)) {
|
|
|
|
|
if (dtypep->isVirtual()) {
|
|
|
|
|
if (AstMemberSel* const memberSelp = VN_CAST(nodep->firstAbovep(), MemberSel)) {
|
|
|
|
|
// Extract the member varp from the MemberSel node
|
|
|
|
|
memberVarp = memberSelp->varp();
|
|
|
|
|
ifacep = dtypep->ifacep();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if ((ifacep = nodep->varp()->sensIfacep())) {
|
|
|
|
|
memberVarp = nodep->varp();
|
|
|
|
|
} else if (VN_IS(nodep->backp(), AssignW)) {
|
|
|
|
|
memberVarp = nodep->varScopep()->varp();
|
2025-12-22 09:52:44 +01:00
|
|
|
ifacep = VN_CAST(memberVarp->backp(), Iface);
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
2025-07-12 03:04:51 +02:00
|
|
|
|
2025-11-26 13:12:20 +01:00
|
|
|
if (ifacep && memberVarp) {
|
2025-12-17 12:51:01 +01:00
|
|
|
FileLine* const flp = nodep->fileline();
|
|
|
|
|
m_curStmt->addNextHere(
|
|
|
|
|
new AstAssign{flp, createVirtIfaceMemberTriggerRefp(flp, ifacep, memberVarp),
|
|
|
|
|
new AstConst{flp, AstConst::BitTrue{}}});
|
2025-07-12 03:04:51 +02:00
|
|
|
}
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
|
|
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
// CONSTRUCTORS
|
|
|
|
|
explicit VirtIfaceVisitor(AstNetlist* nodep)
|
|
|
|
|
: m_netlistp{nodep} {
|
2025-12-22 09:52:44 +01:00
|
|
|
nodep->foreach([](AstIface* const ifacep) {
|
|
|
|
|
for (AstNode* nodep = ifacep->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
|
|
|
if (AstVar* const varp = VN_CAST(nodep, Var)) varp->user1p(ifacep);
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-12-05 04:11:07 +01:00
|
|
|
iterate(nodep);
|
|
|
|
|
}
|
|
|
|
|
~VirtIfaceVisitor() override = default;
|
|
|
|
|
|
|
|
|
|
// METHODS
|
|
|
|
|
VirtIfaceTriggers take_triggers() { return std::move(m_triggers); }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} //namespace
|
|
|
|
|
|
|
|
|
|
VirtIfaceTriggers makeVirtIfaceTriggers(AstNetlist* nodep) {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(2, __FUNCTION__ << ":");
|
2025-09-09 23:39:44 +02:00
|
|
|
VirtIfaceTriggers triggers{};
|
2023-12-05 04:11:07 +01:00
|
|
|
if (v3Global.hasVirtIfaces()) {
|
2025-09-09 23:39:44 +02:00
|
|
|
triggers = VirtIfaceVisitor{nodep}.take_triggers();
|
|
|
|
|
// Dump afer destructor so VNDeleter runs
|
2024-01-09 16:35:13 +01:00
|
|
|
V3Global::dumpCheckGlobalTree("sched_vif", 0, dumpTreeEitherLevel() >= 6);
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
2025-09-09 23:39:44 +02:00
|
|
|
return triggers;
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} //namespace V3Sched
|