// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Clocking POS/NEGEDGE insertion // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2022 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 // //************************************************************************* // V3Clock's Transformations: // // Top Scope: // Check created ACTIVEs // Compress adjacent ACTIVEs with same sensitivity list // Form master _eval function // Add around the SENTREE a (IF POSEDGE(..)) // Add a __Vlast_{clock} for the comparison // Set the __Vlast_{clock} at the end of the block // Replace UNTILSTABLEs with loops until specified signals become const. // Create global calling function for any per-scope functions. (For FINALs). // //************************************************************************* #include "config_build.h" #include "verilatedos.h" #include "V3Clock.h" #include "V3Ast.h" #include "V3Global.h" #include "V3Sched.h" #include VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### // Convert every WRITE AstVarRef to a READ ref class ConvertWriteRefsToRead final : public VNVisitor { private: // MEMBERS AstNode* m_result = nullptr; // CONSTRUCTORS explicit ConvertWriteRefsToRead(AstNode* nodep) { m_result = iterateSubtreeReturnEdits(nodep); } // VISITORS void visit(AstVarRef* nodep) override { UASSERT_OBJ(!nodep->access().isRW(), nodep, "Cannot handle a READWRITE reference"); if (nodep->access().isWriteOnly()) { nodep->replaceWith( new AstVarRef(nodep->fileline(), nodep->varScopep(), VAccess::READ)); } } void visit(AstNode* nodep) override { iterateChildren(nodep); } public: static AstNode* main(AstNode* nodep) { return ConvertWriteRefsToRead(nodep).m_result; } }; //###################################################################### // Clock state, as a visitor of each AstNode class ClockVisitor final : public VNVisitor { private: // STATE AstCFunc* m_evalp = nullptr; // The '_eval' function AstScope* m_scopep = nullptr; // Current scope AstSenTree* m_lastSenp = nullptr; // Last sensitivity match, so we can detect duplicates. AstIf* m_lastIfp = nullptr; // Last sensitivity if active to add more under bool m_inSampled = false; // True inside a sampled expression // METHODS AstNode* createSenseEquation(AstSenItem* nodesp) { AstNode* senEqnp = nullptr; for (AstSenItem* senp = nodesp; senp; senp = VN_AS(senp->nextp(), SenItem)) { UASSERT_OBJ(senp->edgeType() == VEdgeType::ET_TRUE, senp, "Should have been lowered"); AstNode* const senOnep = senp->sensp()->cloneTree(false); senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep; } return senEqnp; } AstVarScope* createSampledVar(AstVarScope* vscp) { if (vscp->user1p()) return VN_AS(vscp->user1p(), VarScope); const AstVar* const varp = vscp->varp(); const string newvarname = string("__Vsampled__") + vscp->scopep()->nameDotless() + "__" + varp->name(); FileLine* const flp = vscp->fileline(); AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()}; newvarp->noReset(true); // Reset by below assign m_scopep->modp()->addStmtsp(newvarp); AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; vscp->user1p(newvscp); m_scopep->addVarsp(newvscp); // At the top of _eval, assign them AstAssign* const finalp = new AstAssign{flp, new AstVarRef{flp, newvscp, VAccess::WRITE}, new AstVarRef{flp, vscp, VAccess::READ}}; m_evalp->addInitsp(finalp); UINFO(4, "New Sampled: " << newvscp << endl); return newvscp; } AstIf* makeActiveIf(AstSenTree* sensesp) { AstNode* const senEqnp = createSenseEquation(sensesp->sensesp()); UASSERT_OBJ(senEqnp, sensesp, "No sense equation, shouldn't be in sequent activation."); AstIf* const newifp = new AstIf(sensesp->fileline(), senEqnp); return newifp; } void clearLastSen() { m_lastSenp = nullptr; m_lastIfp = nullptr; } // VISITORS void visit(AstCoverToggle* nodep) override { // nodep->dumpTree(cout, "ct:"); // COVERTOGGLE(INC, ORIG, CHANGE) -> // IF(ORIG ^ CHANGE) { INC; CHANGE = ORIG; } AstNode* const incp = nodep->incp()->unlinkFrBack(); AstNode* const origp = nodep->origp()->unlinkFrBack(); AstNode* const changeWrp = nodep->changep()->unlinkFrBack(); AstNode* const changeRdp = ConvertWriteRefsToRead::main(changeWrp->cloneTree(false)); AstIf* const newp = new AstIf(nodep->fileline(), new AstXor(nodep->fileline(), origp, changeRdp), incp); // We could add another IF to detect posedges, and only increment if so. // It's another whole branch though versus a potential memory miss. // We'll go with the miss. newp->addThensp(new AstAssign(nodep->fileline(), changeWrp, origp->cloneTree(false))); nodep->replaceWith(newp); VL_DO_DANGLING(nodep->deleteTree(), nodep); } void visit(AstSenTree* nodep) override { nodep->unlinkFrBack(); pushDeletep(nodep); // Delete it later, AstActives still pointing to it } void visit(AstActive* nodep) override { UASSERT_OBJ(nodep->hasClocked(), nodep, "Should have been converted by V3Sched"); UASSERT_OBJ(nodep->stmtsp(), nodep, "Should not have been created if empty"); VNRelinker relinker; nodep->unlinkFrBack(&relinker); AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext(); // Create 'if' statement, if needed if (!m_lastSenp || !nodep->sensesp()->sameTree(m_lastSenp)) { clearLastSen(); m_lastSenp = nodep->sensesp(); // Make a new if statement m_lastIfp = makeActiveIf(m_lastSenp); relinker.relink(m_lastIfp); } // Move statements to if m_lastIfp->addThensp(stmtsp); // Dispose of the AstActive VL_DO_DANGLING(nodep->deleteTree(), nodep); } void visit(AstExecGraph* nodep) override { for (AstMTaskBody* mtaskBodyp = nodep->mTaskBodiesp(); mtaskBodyp; mtaskBodyp = VN_AS(mtaskBodyp->nextp(), MTaskBody)) { clearLastSen(); iterate(mtaskBodyp); } clearLastSen(); } //========== Create sampled values void visit(AstScope* nodep) override { VL_RESTORER(m_scopep); { m_scopep = nodep; iterateChildren(nodep); } } void visit(AstSampled* nodep) override { VL_RESTORER(m_inSampled); { m_inSampled = true; iterateChildren(nodep); nodep->replaceWith(nodep->exprp()->unlinkFrBack()); VL_DO_DANGLING(pushDeletep(nodep), nodep); } } void visit(AstVarRef* nodep) override { iterateChildren(nodep); if (m_inSampled && !nodep->user1SetOnce()) { UASSERT_OBJ(nodep->access().isReadOnly(), nodep, "Should have failed in V3Access"); AstVarScope* const varscp = nodep->varScopep(); AstVarScope* const lastscp = createSampledVar(varscp); AstNode* const newp = new AstVarRef{nodep->fileline(), lastscp, VAccess::READ}; newp->user1SetOnce(); // Don't sample this one nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } } //-------------------- void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS explicit ClockVisitor(AstNetlist* netlistp) { m_evalp = netlistp->evalp(); iterate(netlistp); } ~ClockVisitor() override = default; }; //###################################################################### // Clock class functions void V3Clock::clockAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); { ClockVisitor{nodep}; } // Destruct before checking V3Global::dumpCheckGlobalTree("clock", 0, dumpTree() >= 3); }