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
|
|
|
|
|
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
|
|
|
|
|
AstAssign* m_trigAssignp = nullptr; // Previous/current trigger assignment
|
|
|
|
|
AstIface* m_trigAssignIfacep = nullptr; // Interface type whose trigger is assigned
|
|
|
|
|
// by m_trigAssignp
|
2025-08-21 10:43:37 +02:00
|
|
|
AstVar* m_trigAssignMemberVarp = nullptr; // Member pointer whose trigger is assigned
|
2023-12-05 04:11:07 +01:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-07-12 03:04:51 +02:00
|
|
|
// For each write across a virtual interface boundary (member-level tracking)
|
|
|
|
|
static void foreachWrittenVirtIfaceMember(
|
|
|
|
|
AstNode* const nodep, const std::function<void(AstVarRef*, AstIface*, AstVar*)>& onWrite) {
|
|
|
|
|
nodep->foreach([&](AstVarRef* const refp) {
|
|
|
|
|
if (refp->access().isReadOnly()) return;
|
|
|
|
|
if (AstIfaceRefDType* const dtypep = VN_CAST(refp->varp()->dtypep(), IfaceRefDType)) {
|
|
|
|
|
if (dtypep->isVirtual()) {
|
|
|
|
|
if (AstMemberSel* const memberSelp = VN_CAST(refp->firstAbovep(), MemberSel)) {
|
|
|
|
|
// Extract the member varp from the MemberSel node
|
|
|
|
|
AstVar* memberVarp = memberSelp->varp();
|
|
|
|
|
onWrite(refp, dtypep->ifacep(), memberVarp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (AstIface* const ifacep = refp->varp()->sensIfacep()) {
|
|
|
|
|
AstVar* memberVarp = refp->varp();
|
|
|
|
|
onWrite(refp, ifacep, memberVarp);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-12-05 04:11:07 +01:00
|
|
|
// 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
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// 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};
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
|
|
|
|
VL_RESTORER(m_trigAssignp);
|
|
|
|
|
m_trigAssignp = nullptr;
|
|
|
|
|
VL_RESTORER(m_trigAssignIfacep);
|
|
|
|
|
m_trigAssignIfacep = nullptr;
|
2025-07-12 03:04:51 +02:00
|
|
|
VL_RESTORER(m_trigAssignMemberVarp);
|
|
|
|
|
m_trigAssignMemberVarp = nullptr;
|
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);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstCFunc* nodep) override {
|
|
|
|
|
VL_RESTORER(m_trigAssignp);
|
|
|
|
|
m_trigAssignp = nullptr;
|
|
|
|
|
VL_RESTORER(m_trigAssignIfacep);
|
|
|
|
|
m_trigAssignIfacep = nullptr;
|
2025-07-12 03:04:51 +02:00
|
|
|
VL_RESTORER(m_trigAssignMemberVarp);
|
|
|
|
|
m_trigAssignMemberVarp = nullptr;
|
2023-12-05 04:11:07 +01:00
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
|
|
|
|
void visit(AstNodeIf* nodep) override {
|
|
|
|
|
unsupportedWriteToVirtIface(nodep->condp(), "if condition");
|
|
|
|
|
{
|
|
|
|
|
VL_RESTORER(m_trigAssignp);
|
|
|
|
|
VL_RESTORER(m_trigAssignIfacep);
|
2025-07-12 03:04:51 +02:00
|
|
|
VL_RESTORER(m_trigAssignMemberVarp);
|
2023-12-05 04:11:07 +01:00
|
|
|
iterateAndNextNull(nodep->thensp());
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
VL_RESTORER(m_trigAssignp);
|
|
|
|
|
VL_RESTORER(m_trigAssignIfacep);
|
2025-07-12 03:04:51 +02:00
|
|
|
VL_RESTORER(m_trigAssignMemberVarp);
|
2023-12-05 04:11:07 +01:00
|
|
|
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;
|
2025-07-12 03:04:51 +02:00
|
|
|
m_trigAssignMemberVarp = nullptr;
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-29 16:25:25 +02:00
|
|
|
void visit(AstLoop* nodep) override {
|
|
|
|
|
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
|
2023-12-05 04:11:07 +01:00
|
|
|
{
|
|
|
|
|
VL_RESTORER(m_trigAssignp);
|
|
|
|
|
VL_RESTORER(m_trigAssignIfacep);
|
2025-07-12 03:04:51 +02:00
|
|
|
VL_RESTORER(m_trigAssignMemberVarp);
|
2023-12-05 04:11:07 +01:00
|
|
|
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;
|
2025-07-12 03:04:51 +02:00
|
|
|
m_trigAssignMemberVarp = nullptr;
|
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(AstJumpBlock* nodep) override {
|
|
|
|
|
{
|
|
|
|
|
VL_RESTORER(m_trigAssignp);
|
|
|
|
|
VL_RESTORER(m_trigAssignIfacep);
|
2025-07-12 03:04:51 +02:00
|
|
|
VL_RESTORER(m_trigAssignMemberVarp);
|
2023-12-05 04:11:07 +01:00
|
|
|
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;
|
2025-07-12 03:04:51 +02:00
|
|
|
m_trigAssignMemberVarp = nullptr;
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void visit(AstNodeStmt* nodep) override {
|
|
|
|
|
if (v3Global.usesTiming()
|
|
|
|
|
&& nodep->exists([](AstNode* nodep) { return nodep->isTimingControl(); })) {
|
2025-07-12 03:04:51 +02:00
|
|
|
m_trigAssignp = nullptr;
|
2023-12-05 04:11:07 +01:00
|
|
|
m_trigAssignIfacep = nullptr;
|
2025-07-12 03:04:51 +02:00
|
|
|
m_trigAssignMemberVarp = nullptr;
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
|
|
|
|
FileLine* const flp = nodep->fileline();
|
2025-07-12 03:04:51 +02:00
|
|
|
|
|
|
|
|
foreachWrittenVirtIfaceMember(nodep, [&](AstVarRef*, AstIface* ifacep,
|
|
|
|
|
AstVar* memberVarp) {
|
|
|
|
|
if (ifacep != m_trigAssignIfacep || memberVarp != m_trigAssignMemberVarp) {
|
|
|
|
|
// Write to different interface member than before - need new trigger assignment
|
2023-12-05 04:11:07 +01:00
|
|
|
m_trigAssignIfacep = ifacep;
|
2025-07-12 03:04:51 +02:00
|
|
|
m_trigAssignMemberVarp = memberVarp;
|
2023-12-05 04:11:07 +01:00
|
|
|
m_trigAssignp = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (!m_trigAssignp) {
|
2025-07-12 03:04:51 +02:00
|
|
|
m_trigAssignp
|
|
|
|
|
= new AstAssign{flp, createVirtIfaceMemberTriggerRefp(flp, ifacep, memberVarp),
|
|
|
|
|
new AstConst{flp, AstConst::BitTrue{}}};
|
2023-12-05 04:11:07 +01:00
|
|
|
nodep->addNextHere(m_trigAssignp);
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-07-12 03:04:51 +02:00
|
|
|
// Fallback to whole-interface tracking if no member-specific assignments found
|
|
|
|
|
if (!m_trigAssignp) {
|
|
|
|
|
foreachWrittenVirtIface(nodep, [&](AstVarRef*, AstIface* ifacep) {
|
|
|
|
|
if (ifacep != m_trigAssignIfacep) {
|
|
|
|
|
m_trigAssignIfacep = ifacep;
|
|
|
|
|
m_trigAssignMemberVarp = nullptr;
|
|
|
|
|
m_trigAssignp = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (!m_trigAssignp) {
|
|
|
|
|
m_trigAssignp = new AstAssign{flp, createVirtIfaceTriggerRefp(flp, ifacep),
|
|
|
|
|
new AstConst{flp, AstConst::BitTrue{}}};
|
|
|
|
|
nodep->addNextHere(m_trigAssignp);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-12-05 04:11:07 +01:00
|
|
|
}
|
|
|
|
|
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) {
|
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
|