// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Clocking POS/NEGEDGE insertion // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2024 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 "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Clock.h" #include "V3Const.h" VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### // Convert every WRITE AstVarRef to a READ ref class ConvertWriteRefsToRead final : public VNVisitor { // MEMBERS AstNodeExpr* m_result = nullptr; // CONSTRUCTORS explicit ConvertWriteRefsToRead(AstNodeExpr* nodep) { m_result = VN_AS(iterateSubtreeReturnEdits(nodep), NodeExpr); } // 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}); VL_DO_DANGLING(pushDeletep(nodep), nodep); } } void visit(AstNode* nodep) override { iterateChildren(nodep); } public: static AstNodeExpr* main(AstNodeExpr* nodep) { return ConvertWriteRefsToRead{nodep}.m_result; } }; //###################################################################### // Clock state, as a visitor of each AstNode class ClockVisitor final : public VNVisitor { // NODE STATE // STATE AstCFunc* m_evalp = nullptr; // The '_eval' function AstSenTree* m_lastSenp = nullptr; // Last sensitivity match, so we can detect duplicates. AstIf* m_lastIfp = nullptr; // Last sensitivity if active to add more under // METHODS AstNodeExpr* createSenseEquation(AstSenItem* nodesp) { AstNodeExpr* 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"); AstNodeExpr* const senOnep = senp->sensp()->cloneTree(false); senEqnp = senEqnp ? new AstOr{senp->fileline(), senEqnp, senOnep} : senOnep; } return senEqnp; } AstIf* makeActiveIf(AstSenTree* sensesp) { AstNodeExpr* 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("- ct: "); // COVERTOGGLE(INC, ORIG, CHANGE) -> // IF(ORIG ^ CHANGE) { INC; CHANGE = ORIG; } AstNode* const incp = nodep->incp()->unlinkFrBack(); AstNodeExpr* const origp = nodep->origp()->unlinkFrBack(); AstNodeExpr* const changeWrp = nodep->changep()->unlinkFrBack(); AstNodeExpr* const changeRdp = ConvertWriteRefsToRead::main(changeWrp->cloneTree(false)); AstNodeExpr* comparedp = nullptr; // Xor will optimize better than Eq, when CoverToggle has bit selects, // but can only use Xor with non-opaque types if (const AstBasicDType* const bdtypep = VN_CAST(origp->dtypep()->skipRefp(), BasicDType)) { if (!bdtypep->isOpaque()) comparedp = new AstXor{nodep->fileline(), origp, changeRdp}; } if (!comparedp) comparedp = AstEq::newTyped(nodep->fileline(), origp, changeRdp); AstIf* const newp = new AstIf{nodep->fileline(), comparedp, 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"); AstNode* const stmtsp = nodep->stmtsp()->unlinkFrBackWithNext(); // Create 'if' statement, if needed if (!m_lastSenp || !nodep->sensesp()->sameTree(m_lastSenp)) { VNRelinker relinker; nodep->unlinkFrBack(&relinker); clearLastSen(); m_lastSenp = nodep->sensesp(); // Make a new if statement m_lastIfp = makeActiveIf(m_lastSenp); relinker.relink(m_lastIfp); } else { nodep->unlinkFrBack(); } // 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(); } //========== Move sampled assignments void visit(AstVarScope* nodep) override { AstVar* varp = nodep->varp(); if (varp->valuep() && varp->name().substr(0, strlen("__Vsampled")) == "__Vsampled") { m_evalp->addInitsp(new AstAssign{ nodep->fileline(), new AstVarRef{nodep->fileline(), nodep, VAccess::WRITE}, VN_AS(varp->valuep()->unlinkFrBack(), NodeExpr)}); varp->direction(VDirection::NONE); // Restore defaults varp->primaryIO(false); } } //-------------------- void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS explicit ClockVisitor(AstNetlist* netlistp) { m_evalp = netlistp->evalp(); // Simplify all SenTrees for (AstSenTree* senTreep = netlistp->topScopep()->senTreesp(); senTreep; senTreep = VN_AS(senTreep->nextp(), SenTree)) { V3Const::constifyExpensiveEdit(senTreep); } 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, dumpTreeEitherLevel() >= 3); }