// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Create triggers necessary for scheduling across // virtual interfaces // // 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 // //************************************************************************* // 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 AssignW, AssignPost: // 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 "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; // 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 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); } }); } // 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, "Unsupported: write to virtual interface in " << locationp); }); } // 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}; } // VISITORS void visit(AstNodeProcedure* nodep) override { VL_RESTORER(m_trigAssignp); m_trigAssignp = nullptr; VL_RESTORER(m_trigAssignIfacep); m_trigAssignIfacep = nullptr; iterateChildren(nodep); } void visit(AstCFunc* nodep) override { VL_RESTORER(m_trigAssignp); m_trigAssignp = nullptr; VL_RESTORER(m_trigAssignIfacep); m_trigAssignIfacep = nullptr; iterateChildren(nodep); } void visit(AstAssignW* nodep) override { if (writesToVirtIface(nodep)) { // Convert to always, as we have to assign the trigger var nodep->convertToAlways(); } } void visit(AstAssignPost* nodep) override { if (writesToVirtIface(nodep)) { // Convert to always, as we have to assign the trigger var FileLine* const flp = nodep->fileline(); AstAlwaysPost* const postp = new AstAlwaysPost{flp, nullptr, nullptr}; nodep->replaceWith(postp); postp->addStmtsp( new AstAssign{flp, nodep->lhsp()->unlinkFrBack(), nodep->rhsp()->unlinkFrBack()}); nodep->deleteTree(); } } void visit(AstNodeIf* nodep) override { unsupportedWriteToVirtIface(nodep->condp(), "if condition"); { VL_RESTORER(m_trigAssignp); VL_RESTORER(m_trigAssignIfacep); iterateAndNextNull(nodep->thensp()); } { VL_RESTORER(m_trigAssignp); VL_RESTORER(m_trigAssignIfacep); 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; } } void visit(AstWhile* nodep) override { unsupportedWriteToVirtIface(nodep->precondsp(), "loop condition"); unsupportedWriteToVirtIface(nodep->condp(), "loop condition"); unsupportedWriteToVirtIface(nodep->incsp(), "loop increment statement"); { VL_RESTORER(m_trigAssignp); VL_RESTORER(m_trigAssignIfacep); 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; } } void visit(AstJumpBlock* nodep) override { { VL_RESTORER(m_trigAssignp); VL_RESTORER(m_trigAssignIfacep); 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; } } void visit(AstNodeStmt* nodep) override { if (v3Global.usesTiming() && nodep->exists([](AstNode* nodep) { return nodep->isTimingControl(); })) { m_trigAssignp = nullptr; // Could be after a delay - need new trigger assignment m_trigAssignIfacep = nullptr; // No restorer, as following statements should not reuse the old assignment } FileLine* const flp = nodep->fileline(); foreachWrittenVirtIface(nodep, [&](AstVarRef*, AstIface* ifacep) { if (ifacep != m_trigAssignIfacep) { // Write to different interface type than before - need new trigger assignment // No restorer, as following statements should not reuse the old assignment m_trigAssignIfacep = ifacep; m_trigAssignp = nullptr; } if (!m_trigAssignp) { m_trigAssignp = new AstAssign{flp, createVirtIfaceTriggerRefp(flp, ifacep), new AstConst{flp, AstConst::BitTrue{}}}; nodep->addNextHere(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; // METHODS VirtIfaceTriggers take_triggers() { return std::move(m_triggers); } }; } //namespace VirtIfaceTriggers makeVirtIfaceTriggers(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); if (v3Global.hasVirtIfaces()) { VirtIfaceVisitor visitor{nodep}; V3Global::dumpCheckGlobalTree("sched_vif", 0, dumpTreeLevel() >= 6); return visitor.take_triggers(); } return {}; } } //namespace V3Sched