// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Covert forceable signals, process force/release // // Code available from: https://verilator.org // //************************************************************************* // // 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-FileCopyrightText: 2003-2026 Wilson Snyder // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 // //************************************************************************* // V3Force's Transformations: // // For each forceable net with name "": // add 3 extra signals: // - __VforceRd: a net with same type as signal // - __VforceEn: a var with same type as signal, which is the bitwise force enable // - __VforceVal: a var with same type as signal, which is the forced value // add an initial statement: // initial __VforceEn = 0; // add a continuous assignment: // assign __VforceRd = __VforceEn ? __VforceVal : ; // replace all READ references to with a read reference to _VforceRd // // Replace each AstAssignForce with 3 assignments: // - __VforceEn = 1 // - __VforceVal = // - __VforceRd = // // Replace each AstRelease with 1 or 2 assignments: // - __VforceEn = 0 // - __VforceRd = // iff lhs is a net // // After each WRITE of forced LHS // reevaluate __VforceRd to support immediate force/release // // After each WRITE of forced RHS // reevaluate __VforceVal to support VarRef rollback after release //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Force.h" #include "V3AstUserAllocator.h" #include "V3UniqueNames.h" VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### // Convert force/release statements and signals marked 'forceable' class ForceState final { constexpr static int ELEMENTS_MAX = 1000; // TYPES struct ForceComponentsVar final { AstVar* const m_rdVarp; // New variable to replace read references with AstVar* const m_valVarp; // Forced value AstVar* const m_enVarp; // Force enabled signal explicit ForceComponentsVar(AstVar* varp) : m_rdVarp{new AstVar{varp->fileline(), VVarType::WIRE, varp->name() + "__VforceRd", varp->dtypep()}} , m_valVarp{new AstVar{varp->fileline(), VVarType::VAR, varp->name() + "__VforceVal", varp->dtypep()}} , m_enVarp{new AstVar{varp->fileline(), VVarType::VAR, varp->name() + "__VforceEn", getEnVarpDTypep(varp)}} { m_rdVarp->addNext(m_enVarp); m_rdVarp->addNext(m_valVarp); varp->addNextHere(m_rdVarp); } }; public: struct ForceComponentsVarScope final { AstVarScope* const m_rdVscp; // New variable to replace read references with AstVarScope* const m_valVscp; // Forced value AstVarScope* const m_enVscp; // Force enabled signal V3UniqueNames m_iterNames; // Names for loop iteration variables explicit ForceComponentsVarScope(AstVarScope* vscp, ForceComponentsVar& fcv) : m_rdVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_rdVarp}} , m_valVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_valVarp}} , m_enVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_enVarp}} , m_iterNames{"__VForceIter"} { m_rdVscp->addNext(m_enVscp); m_rdVscp->addNext(m_valVscp); vscp->addNextHere(m_rdVscp); FileLine* const flp = vscp->fileline(); // Add initialization of the enable signal AstActive* const activeInitp = new AstActive{ flp, "force-init", new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Static{}}}}; activeInitp->senTreeStorep(activeInitp->sentreep()); vscp->scopep()->addBlocksp(activeInitp); // Create statements that update __Rd variable. // These nodes will be copied and used also for __En initialization AstVarRef* const rdRefp = new AstVarRef{flp, m_rdVscp, VAccess::WRITE}; std::vector assigns; AstNodeStmt* const rdUpdateStmtsp = getForcedUpdateStmtsRecursep(rdRefp, vscp, rdRefp, assigns); // To use these statements for __En initialization, replace references to __Rd with // ones to __En and replace assignments RHS with 0 AstNodeStmt* const enInitStmtsp = rdUpdateStmtsp->cloneTree(true); for (size_t i = 0; i < assigns.size(); i++) { // Save copies, because clonep() works only after the last cloneTree assigns[i] = assigns[i]->clonep(); } for (AstAssign* const assignp : assigns) { AstVarRef* const lhsVarRefp = VN_AS(AstNodeVarRef::varRefLValueRecurse(assignp->lhsp()), VarRef); lhsVarRefp->replaceWith(new AstVarRef{flp, m_enVscp, VAccess::WRITE}); lhsVarRefp->deleteTree(); assignp->rhsp()->unlinkFrBack()->deleteTree(); V3Number zero{m_enVscp, assignp->lhsp()->dtypep()->width()}; assignp->rhsp(new AstConst{flp, zero}); } activeInitp->addStmtsp(new AstInitial{flp, enInitStmtsp}); { // Add the combinational override // Explicitly list dependencies for update. // Note: rdVscp is also needed to retrigger assignment for the first time. AstSenItem* const itemsp = new AstSenItem{ flp, VEdgeType::ET_CHANGED, new AstVarRef{flp, m_rdVscp, VAccess::READ}}; itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, new AstVarRef{flp, m_valVscp, VAccess::READ}}); itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, new AstVarRef{flp, m_enVscp, VAccess::READ}}); AstVarRef* const origp = new AstVarRef{flp, vscp, VAccess::READ}; ForceState::markNonReplaceable(origp); itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, origp}); AstActive* const activep = new AstActive{flp, "force-update", new AstSenTree{flp, itemsp}}; activep->senTreeStorep(activep->sentreep()); activep->addStmtsp( new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, rdUpdateStmtsp}); vscp->scopep()->addBlocksp(activep); } } AstNodeStmt* getForcedUpdateStmtsRecursep(AstNodeExpr* const lhsp, AstVarScope* const vscp, AstVarRef* const lhsVarRefp, std::vector& assigns) { // Create stataments that update values of __Rd variable. // lhsp is either a reference to that variable or ArraySel or MemberSel on it. // lhsVarRefp is a reference to that variable in lhsp subtree. // assigns is a vector to which all assignments to __Rd are added. FileLine* const flp = lhsp->fileline(); const AstNodeDType* const lhsDtypep = lhsp->dtypep()->skipRefp(); if (lhsDtypep->isIntegralOrPacked() || VN_IS(lhsDtypep, BasicDType)) { AstAssign* const assignp = new AstAssign{flp, lhsp, forcedUpdate(vscp, lhsp, lhsVarRefp)}; assigns.push_back(assignp); return assignp; } else if (const AstStructDType* const structDtypep = VN_CAST(lhsDtypep, StructDType)) { AstNodeStmt* stmtsp = nullptr; bool firstIter = true; for (AstMemberDType* mdtp = structDtypep->membersp(); mdtp; mdtp = VN_AS(mdtp->nextp(), MemberDType)) { AstNodeExpr* const lhsCopyp = firstIter ? lhsp : lhsp->cloneTreePure(false); AstVarRef* const lhsVarRefCopyp = firstIter ? lhsVarRefp : lhsVarRefp->clonep(); AstStructSel* const structSelp = new AstStructSel{flp, lhsCopyp, mdtp->name()}; structSelp->dtypep(mdtp); AstNodeStmt* const memberStmtp = getForcedUpdateStmtsRecursep(structSelp, vscp, lhsVarRefCopyp, assigns); stmtsp = firstIter ? memberStmtp : stmtsp->addNext(memberStmtp); firstIter = false; } return stmtsp; } else if (const AstUnpackArrayDType* const arrayDtypep = VN_CAST(lhsDtypep, UnpackArrayDType)) { AstVar* const loopVarp = new AstVar{flp, VVarType::MODULETEMP, m_iterNames.get(m_rdVscp->varp()->name()), VFlagBitPacked{}, 32}; m_rdVscp->varp()->addNext(loopVarp); AstVarScope* const loopVarScopep = new AstVarScope{flp, m_rdVscp->scopep(), loopVarp}; m_rdVscp->addNext(loopVarScopep); AstVarRef* const readRefp = new AstVarRef{flp, loopVarScopep, VAccess::READ}; AstNodeStmt* const currInitp = new AstAssign{ flp, new AstVarRef{flp, loopVarScopep, VAccess::WRITE}, new AstConst{flp, 0}}; AstLoop* const currWhilep = new AstLoop{flp}; currInitp->addNextHere(currWhilep); AstLoopTest* const loopTestp = new AstLoopTest{ flp, currWhilep, new AstNeq{ flp, readRefp, new AstConst{flp, static_cast(arrayDtypep->elementsConst())}}}; currWhilep->addStmtsp(loopTestp); AstArraySel* const lhsSelp = new AstArraySel{flp, lhsp, readRefp->cloneTree(false)}; AstNodeStmt* const loopBodyp = getForcedUpdateStmtsRecursep(lhsSelp, vscp, lhsVarRefp, assigns); currWhilep->addStmtsp(loopBodyp); AstAssign* const currIncrp = new AstAssign{ flp, new AstVarRef{flp, loopVarScopep, VAccess::WRITE}, new AstAdd{flp, readRefp->cloneTree(false), new AstConst{flp, 1}}}; currWhilep->addStmtsp(currIncrp); return currInitp; } else { lhsDtypep->v3fatalSrc("Unhandled type"); } } static AstNodeExpr* wrapIntoExprp(AstVarRef* const refp, AstNodeExpr* const exprp, AstVarRef* const varRefToReplacep) { // Return a copy of exprp in which varRefToReplacep is replaced with refp if (exprp == varRefToReplacep) { return refp; } else { AstNodeExpr* const copiedExprp = exprp->cloneTreePure(false); AstNode* const oldRefp = varRefToReplacep->clonep(); varRefToReplacep->clonep()->replaceWith(refp); oldRefp->deleteTree(); return copiedExprp; } } AstNodeExpr* forcedUpdate(AstVarScope* const vscp, AstNodeExpr* exprp = nullptr, AstVarRef* const varRefToReplacep = nullptr) const { FileLine* const flp = vscp->fileline(); AstVarRef* origRefp = new AstVarRef{flp, vscp, VAccess::READ}; ForceState::markNonReplaceable(origRefp); AstNodeExpr* const origExprp = wrapIntoExprp(origRefp, exprp, varRefToReplacep); AstNodeExpr* const enExprp = wrapIntoExprp(new AstVarRef{flp, m_enVscp, VAccess::READ}, exprp, varRefToReplacep); AstNodeExpr* const valExprp = wrapIntoExprp( new AstVarRef{flp, m_valVscp, VAccess::READ}, exprp, varRefToReplacep); if (ForceState::isRangedDType(vscp)) { return new AstOr{ flp, new AstAnd{flp, enExprp, valExprp}, new AstAnd{flp, new AstNot{flp, enExprp->cloneTreePure(false)}, origExprp}}; } return new AstCond{flp, enExprp, valExprp, origExprp}; } }; private: // NODE STATE // AstVar::user1p -> ForceComponentsVar* instance (via m_forceComponentsVar) // AstVarScope::user1p -> ForceComponentsVarScope* instance (via m_forceComponentsVarScope) // AstVarRef::user2 -> Flag indicating not to replace reference // AstAssign::user2 -> Flag indicating that assignment was created for AstRelease // AstVarScope::user3p -> AstAssign*, the assignment __VforceVal = const VNUser1InUse m_user1InUse; const VNUser2InUse m_user2InUse; const VNUser3InUse m_user3InUse; AstUser1Allocator m_forceComponentsVar; AstUser1Allocator m_forceComponentsVarScope; std::unordered_map, std::vector>> m_valVscps; // `valVscp` force components of a forced RHS static size_t checkIfDTypeSupportedRecurse(const AstNodeDType* const dtypep, const AstVar* const varp) { // Checks if force stmt is supported on all subtypes // and returns number of unpacked elements const AstNodeDType* const dtp = dtypep->skipRefp(); if (const AstUnpackArrayDType* const udtp = VN_CAST(dtp, UnpackArrayDType)) { const size_t elemsInSubDType = checkIfDTypeSupportedRecurse(udtp->subDTypep(), varp); return udtp->elementsConst() * elemsInSubDType; } else if (const AstNodeUOrStructDType* const sdtp = VN_CAST(dtp, NodeUOrStructDType)) { size_t elemCount = 0; for (const AstMemberDType* mdtp = sdtp->membersp(); mdtp; mdtp = VN_AS(mdtp->nextp(), MemberDType)) { elemCount += checkIfDTypeSupportedRecurse(mdtp->subDTypep(), varp); } return elemCount; } else if (const AstBasicDType* const bdtp = VN_CAST(dtp, BasicDType)) { if (bdtp->isString() || bdtp->isEvent() || bdtp->keyword() == VBasicDTypeKwd::CHANDLE || bdtp->keyword() == VBasicDTypeKwd::TIME) { varp->v3warn(E_UNSUPPORTED, "Forcing variable of unsupported type: " << varp->dtypep()->prettyTypeName()); } return 1; } else if (!dtp->isIntegralOrPacked()) { varp->v3warn(E_UNSUPPORTED, "Forcing variable of unsupported type: " << varp->dtypep()->prettyTypeName()); return 1; } else { // All packed types are supported return 1; } } static AstNodeDType* getEnVarpDTypep(const AstVar* const varp) { AstNodeDType* const origDTypep = varp->dtypep()->skipRefp(); const size_t unpackElemNum = checkIfDTypeSupportedRecurse(origDTypep, varp); if (unpackElemNum > ELEMENTS_MAX) { varp->v3warn(E_UNSUPPORTED, "Unsupported: Force of variable with " ">= " << ELEMENTS_MAX << " unpacked elements"); } if (VN_IS(origDTypep, UnpackArrayDType)) { return origDTypep; } else if (VN_IS(origDTypep, BasicDType)) { return isRangedDType(varp) ? origDTypep : varp->findBitDType(); } else if (VN_IS(origDTypep, PackArrayDType)) { return origDTypep; } else if (VN_IS(origDTypep, NodeUOrStructDType)) { return origDTypep; } else { varp->v3fatalSrc("Unsupported: Force of variable of unhandled data type"); return origDTypep; } } public: // CONSTRUCTORS ForceState() = default; VL_UNCOPYABLE(ForceState); // STATIC METHODS static bool isRangedDType(const AstNode* const nodep) { // If ranged we need a multibit enable to support bit-by-bit part-select forces, // otherwise forcing a real or other opaque dtype and need a single bit enable. const AstBasicDType* const basicp = nodep->dtypep()->skipRefp()->basicp(); return basicp && basicp->isRanged(); } static bool isNotReplaceable(const AstVarRef* const nodep) { return nodep->user2(); } static void markNonReplaceable(AstVarRef* const nodep) { nodep->user2SetOnce(); } // Get all ValVscps for a VarScope const std::vector* getValVscps(AstVarRef* const refp) const { auto it = m_valVscps.find(refp->varScopep()); if (it != m_valVscps.end()) return &(it->second.second); return nullptr; } // Add a ValVscp for a VarScope void addValVscp(AstVarRef* const refp, AstVarScope* const valVscp) { if (m_valVscps[refp->varScopep()].first.find(valVscp) != m_valVscps[refp->varScopep()].first.end()) return; m_valVscps[refp->varScopep()].first.emplace(valVscp); m_valVscps[refp->varScopep()].second.push_back(valVscp); } // METHODS const ForceComponentsVarScope& getForceComponents(AstVarScope* vscp) { AstVar* const varp = vscp->varp(); return m_forceComponentsVarScope(vscp, vscp, m_forceComponentsVar(varp, varp)); } ForceComponentsVarScope* tryGetForceComponents(AstVarRef* nodep) const { return m_forceComponentsVarScope.tryGet(nodep->varScopep()); } void setValVscpAssign(AstVarScope* valVscp, AstAssign* rhsExpr) { valVscp->user3p(rhsExpr); } AstAssign* getValVscpAssign(AstVarScope* valVscp) const { return VN_CAST(valVscp->user3p(), Assign); } }; class ForceConvertVisitor final : public VNVisitor { // STATE ForceState& m_state; // STATIC METHODS // Replace each AstNodeVarRef in the given 'nodep' that writes a variable by transforming the // referenced AstVarScope with the given function. static void transformWritenVarScopes(AstNode* nodep, std::function f) { UASSERT_OBJ(nodep->backp(), nodep, "Must have backp, otherwise will be lost if replaced"); nodep->foreach([&f](AstNodeVarRef* refp) { if (refp->access() != VAccess::WRITE) return; // TODO: this is not strictly speaking safe for some complicated lvalues, eg.: // 'force foo[a(cnt)] = 1;', where 'cnt' is an out parameter, but it will // do for now... refp->replaceWith( new AstVarRef{refp->fileline(), f(refp->varScopep()), VAccess::WRITE}); VL_DO_DANGLING(refp->deleteTree(), refp); }); } // VISITORS void visit(AstNode* nodep) override { iterateChildren(nodep); } void visit(AstAssignForce* nodep) override { // The AstAssignForce node will be removed for sure FileLine* const flp = nodep->fileline(); AstNodeExpr* const lhsp = nodep->lhsp(); // The LValue we are forcing AstNodeExpr* const rhsp = nodep->rhsp(); // The value we are forcing it to VNRelinker relinker; nodep->unlinkFrBack(&relinker); VL_DO_DANGLING(pushDeletep(nodep), nodep); // Set corresponding enable signals to ones V3Number ones{lhsp, ForceState::isRangedDType(lhsp) ? lhsp->width() : 1}; ones.setAllBits1(); AstAssign* const setEnp = new AstAssign{flp, lhsp->cloneTreePure(false), new AstConst{rhsp->fileline(), ones}}; transformWritenVarScopes(setEnp->lhsp(), [this](AstVarScope* vscp) { return m_state.getForceComponents(vscp).m_enVscp; }); // Set corresponding value signals to the forced value AstAssign* const setValp = new AstAssign{flp, lhsp->cloneTreePure(false), rhsp->cloneTreePure(false)}; transformWritenVarScopes(setValp->lhsp(), [this, rhsp, setValp](AstVarScope* vscp) { AstVarScope* const valVscp = m_state.getForceComponents(vscp).m_valVscp; m_state.setValVscpAssign(valVscp, setValp); rhsp->foreach([valVscp, this](AstVarRef* refp) { m_state.addValVscp(refp, valVscp); }); return valVscp; }); // Set corresponding read signal directly as well, in case something in the same // process reads it later AstAssign* const setRdp = new AstAssign{flp, lhsp->unlinkFrBack(), rhsp->unlinkFrBack()}; transformWritenVarScopes(setRdp->lhsp(), [this](AstVarScope* vscp) { return m_state.getForceComponents(vscp).m_rdVscp; }); setEnp->addNext(setValp); setEnp->addNext(setRdp); relinker.relink(setEnp); } void visit(AstRelease* nodep) override { FileLine* const flp = nodep->fileline(); AstNodeExpr* const lhsp = nodep->lhsp(); // The LValue we are releasing // The AstRelease node will be removed for sure VNRelinker relinker; nodep->unlinkFrBack(&relinker); VL_DO_DANGLING(pushDeletep(nodep), nodep); // Set corresponding enable signals to zero V3Number zero{lhsp, ForceState::isRangedDType(lhsp) ? lhsp->width() : 1}; zero.setAllBits0(); AstAssign* const resetEnp = new AstAssign{flp, lhsp->cloneTreePure(false), new AstConst{lhsp->fileline(), zero}}; transformWritenVarScopes(resetEnp->lhsp(), [this](AstVarScope* vscp) { return m_state.getForceComponents(vscp).m_enVscp; }); // IEEE 1800-2023 10.6.2: When released, then if the variable is not driven by a continuous // assignment and does not currently have an active procedural continuous assignment, the // variable shall not immediately change value and shall maintain its current value until // the next procedural assignment to the variable is executed. Releasing a variable that is // driven by a continuous assignment or currently has an active assign procedural // continuous assignment shall reestablish that assignment and schedule a reevaluation in // the continuous assignment's scheduling region. AstAssign* const resetRdp = new AstAssign{flp, lhsp->cloneTreePure(false), lhsp->unlinkFrBack()}; resetRdp->user2(true); // Replace write refs on the LHS resetRdp->lhsp()->foreach([this](AstVarRef* refp) { if (refp->access() != VAccess::WRITE) return; AstVarScope* const vscp = refp->varScopep(); if (vscp->varp()->isContinuously()) { AstVarRef* const newpRefp = new AstVarRef{ refp->fileline(), m_state.getForceComponents(vscp).m_rdVscp, VAccess::WRITE}; refp->replaceWith(newpRefp); VL_DO_DANGLING(refp->deleteTree(), refp); } }); // Replace write refs on RHS if (VN_IS(resetRdp->rhsp(), ArraySel) || VN_IS(resetRdp->rhsp(), StructSel)) { AstVarRef* const refp = VN_AS(AstNodeVarRef::varRefLValueRecurse(resetRdp->rhsp()), VarRef); AstVarScope* const vscp = refp->varScopep(); AstNodeExpr* const origRhsp = resetRdp->rhsp(); origRhsp->replaceWith( m_state.getForceComponents(vscp).forcedUpdate(vscp, origRhsp, refp)); VL_DO_DANGLING(origRhsp->deleteTree(), origRhsp); } else { resetRdp->rhsp()->foreach([this](AstVarRef* refp) { if (refp->access() != VAccess::WRITE) return; AstVarScope* const vscp = refp->varScopep(); if (vscp->varp()->isContinuously()) { refp->access(VAccess::READ); ForceState::markNonReplaceable(refp); } else { refp->replaceWith(m_state.getForceComponents(vscp).forcedUpdate(vscp)); VL_DO_DANGLING(refp->deleteTree(), refp); } }); } resetRdp->addNext(resetEnp); relinker.relink(resetRdp); } void visit(AstVarScope* nodep) override { // If this signal is marked externally forceable, create the public force signals if (nodep->varp()->isForceable()) { if (VN_IS(nodep->varp()->dtypeSkipRefp(), UnpackArrayDType)) { nodep->varp()->v3warn( E_UNSUPPORTED, "Unsupported: Forcing unpacked arrays: " << nodep->varp()->name()); // (#4735) return; } const AstBasicDType* const bdtypep = nodep->varp()->basicp(); const bool strtype = bdtypep && bdtypep->keyword() == VBasicDTypeKwd::STRING; if (strtype) { nodep->varp()->v3error( "Forcing strings is not permitted: " << nodep->varp()->name()); } const ForceState::ForceComponentsVarScope& fc = m_state.getForceComponents(nodep); fc.m_enVscp->varp()->sigUserRWPublic(true); fc.m_valVscp->varp()->sigUserRWPublic(true); } } public: // CONSTRUCTOR // cppcheck-suppress constParameterCallback ForceConvertVisitor(AstNetlist* nodep, ForceState& state) : m_state{state} { // Transform all force and release statements iterateAndNextNull(nodep->modulesp()); } }; class ForceReplaceVisitor final : public VNVisitor { // STATE const ForceState& m_state; AstNodeStmt* m_stmtp = nullptr; bool m_inLogic = false; bool m_releaseRhs = false; // Inside RHS of assignment created for release statement // METHODS void iterateLogic(AstNode* logicp) { VL_RESTORER(m_inLogic); m_inLogic = true; iterateChildren(logicp); } // VISITORS void visit(AstNodeStmt* nodep) override { VL_RESTORER(m_stmtp); m_stmtp = nodep; iterateChildren(nodep); } void visit(AstAssign* nodep) override { VL_RESTORER(m_stmtp); VL_RESTORER(m_releaseRhs); m_stmtp = nodep; iterate(nodep->lhsp()); m_releaseRhs = nodep->user2(); iterate(nodep->rhsp()); } void visit(AstCFunc* nodep) override { iterateLogic(nodep); } void visit(AstCoverToggle* nodep) override { iterateLogic(nodep); } void visit(AstNodeProcedure* nodep) override { iterateLogic(nodep); } void visit(AstAlways* nodep) override { // TODO: this is the old behavioud prior to moving AssignW under Always. // Review if this is appropriate or if we are missing something... if (nodep->keyword() == VAlwaysKwd::CONT_ASSIGN) { iterateChildren(nodep); return; } iterateLogic(nodep); } void visit(AstSenItem* nodep) override { iterateLogic(nodep); } void visit(AstVarRef* nodep) override { if (ForceState::isNotReplaceable(nodep)) return; switch (nodep->access()) { case VAccess::READ: { // Replace VarRef from forced LHS with rdVscp. if (ForceState::ForceComponentsVarScope* const fcp = m_state.tryGetForceComponents(nodep)) { nodep->varp(fcp->m_rdVscp->varp()); nodep->varScopep(fcp->m_rdVscp); } break; } case VAccess::WRITE: { if (!m_inLogic) return; // Emit rdVscp update after each write to any VarRef on forced LHS. if (ForceState::ForceComponentsVarScope* const fcp = m_state.tryGetForceComponents(nodep)) { FileLine* const flp = nodep->fileline(); AstVarRef* const lhsRefp = new AstVarRef{flp, fcp->m_rdVscp, VAccess::WRITE}; AstNodeExpr* lhsp; AstNodeExpr* rhsp; if (nodep->dtypep()->skipRefp()->isIntegralOrPacked()) { rhsp = fcp->forcedUpdate(nodep->varScopep()); lhsp = lhsRefp; } else { AstNodeExpr* wholeExprp = nodep; while (VN_IS(wholeExprp->backp(), NodeExpr)) { wholeExprp = VN_AS(wholeExprp->backp(), NodeExpr); // wholeExprp should never be ExprStmt, because: // * if nodep is inside stmtsp() of one, we should sooner get NodeStmt node // * nodep should never be in resultp(), because it is a WRITE reference // and resultp() should be an rvalue UASSERT_OBJ(!VN_IS(wholeExprp, ExprStmt), nodep, "Unexpected AstExprStmt"); } lhsp = ForceState::ForceComponentsVarScope::wrapIntoExprp(lhsRefp, wholeExprp, nodep); rhsp = fcp->forcedUpdate(nodep->varScopep(), wholeExprp, nodep); } m_stmtp->addNextHere(new AstAssign{flp, lhsp, rhsp}); } // Emit valVscp update after each write to any VarRef on forced RHS. if (!m_state.getValVscps(nodep)) break; for (AstVarScope* const valVscp : *m_state.getValVscps(nodep)) { FileLine* const flp = nodep->fileline(); AstAssign* assignp = m_state.getValVscpAssign(valVscp); UASSERT_OBJ(assignp, flp, "Missing stored assignment for forced valVscp"); assignp = assignp->cloneTreePure(false); assignp->rhsp()->foreach( [](AstVarRef* refp) { ForceState::markNonReplaceable(refp); }); m_stmtp->addNextHere(assignp); } break; } default: if (!m_inLogic) return; if (m_state.tryGetForceComponents(nodep) || m_state.getValVscps(nodep)) { nodep->v3warn( E_UNSUPPORTED, "Unsupported: Signals used via read-write reference cannot be forced"); } break; } } void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTOR explicit ForceReplaceVisitor(AstNetlist* nodep, const ForceState& state) : m_state{state} { iterateChildren(nodep); } }; //###################################################################### // void V3Force::forceAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ":"); if (!v3Global.hasForceableSignals()) return; { ForceState state; { ForceConvertVisitor{nodep, state}; } { ForceReplaceVisitor{nodep, state}; } V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3); } }