2022-01-01 18:24:19 +01:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
|
//*************************************************************************
|
2021-12-17 18:56:33 +01:00
|
|
|
// DESCRIPTION: Verilator: Covert forceable signals, process force/release
|
2022-01-01 18:24:19 +01:00
|
|
|
//
|
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2026-01-27 02:24:34 +01:00
|
|
|
// 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
|
2022-01-01 18:24:19 +01:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
2021-12-17 18:56:33 +01:00
|
|
|
// V3Force's Transformations:
|
2022-01-01 18:24:19 +01:00
|
|
|
//
|
2021-12-17 18:56:33 +01:00
|
|
|
// For each forceable net with name "<name>":
|
|
|
|
|
// add 3 extra signals:
|
|
|
|
|
// - <name>__VforceRd: a net with same type as signal
|
|
|
|
|
// - <name>__VforceEn: a var with same type as signal, which is the bitwise force enable
|
2025-03-04 16:12:02 +01:00
|
|
|
// - <name>__VforceVal: a var with same type as signal, which is the forced value
|
2021-12-17 18:56:33 +01:00
|
|
|
// add an initial statement:
|
|
|
|
|
// initial <name>__VforceEn = 0;
|
|
|
|
|
// add a continuous assignment:
|
2025-03-04 16:12:02 +01:00
|
|
|
// assign <name>__VforceRd = <name>__VforceEn ? <name>__VforceVal : <name>;
|
2021-12-17 18:56:33 +01:00
|
|
|
// replace all READ references to <name> with a read reference to <name>_VforceRd
|
2022-01-01 18:24:19 +01:00
|
|
|
//
|
2021-12-17 18:56:33 +01:00
|
|
|
// Replace each AstAssignForce with 3 assignments:
|
|
|
|
|
// - <lhs>__VforceEn = 1
|
2025-03-04 16:12:02 +01:00
|
|
|
// - <lhs>__VforceVal = <rhs>
|
2021-12-17 18:56:33 +01:00
|
|
|
// - <lhs>__VforceRd = <rhs>
|
2022-01-01 18:24:19 +01:00
|
|
|
//
|
2021-12-17 18:56:33 +01:00
|
|
|
// Replace each AstRelease with 1 or 2 assignments:
|
|
|
|
|
// - <lhs>__VforceEn = 0
|
|
|
|
|
// - <lhs>__VforceRd = <lhs> // iff lhs is a net
|
2022-01-01 18:24:19 +01:00
|
|
|
//
|
2025-03-04 16:12:02 +01:00
|
|
|
// After each WRITE of forced LHS
|
|
|
|
|
// reevaluate <lhs>__VforceRd to support immediate force/release
|
|
|
|
|
//
|
|
|
|
|
// After each WRITE of forced RHS
|
|
|
|
|
// reevaluate <lhs>__VforceVal to support VarRef rollback after release
|
2022-01-01 18:24:19 +01:00
|
|
|
//*************************************************************************
|
|
|
|
|
|
2023-10-18 12:37:46 +02:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
|
|
2022-01-01 18:24:19 +01:00
|
|
|
#include "V3Force.h"
|
|
|
|
|
|
2021-12-17 18:56:33 +01:00
|
|
|
#include "V3AstUserAllocator.h"
|
2026-02-17 01:05:07 +01:00
|
|
|
#include "V3UniqueNames.h"
|
2022-01-01 18:24:19 +01:00
|
|
|
|
2022-09-18 21:53:42 +02:00
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
2022-01-01 18:24:19 +01:00
|
|
|
//######################################################################
|
2021-12-17 18:56:33 +01:00
|
|
|
// Convert force/release statements and signals marked 'forceable'
|
2022-01-01 18:24:19 +01:00
|
|
|
|
2025-03-04 16:12:02 +01:00
|
|
|
class ForceState final {
|
2025-12-16 08:35:59 +01:00
|
|
|
constexpr static int ELEMENTS_MAX = 1000;
|
2022-01-01 18:24:19 +01:00
|
|
|
// TYPES
|
2024-01-20 21:06:46 +01:00
|
|
|
struct ForceComponentsVar final {
|
2021-12-17 18:56:33 +01:00
|
|
|
AstVar* const m_rdVarp; // New variable to replace read references with
|
|
|
|
|
AstVar* const m_valVarp; // Forced value
|
2023-02-05 02:37:36 +01:00
|
|
|
AstVar* const m_enVarp; // Force enabled signal
|
2021-12-17 18:56:33 +01:00
|
|
|
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",
|
2023-02-05 02:37:36 +01:00
|
|
|
varp->dtypep()}}
|
2025-12-16 08:35:59 +01:00
|
|
|
, m_enVarp{new AstVar{varp->fileline(), VVarType::VAR, varp->name() + "__VforceEn",
|
|
|
|
|
getEnVarpDTypep(varp)}} {
|
2021-12-17 18:56:33 +01:00
|
|
|
m_rdVarp->addNext(m_enVarp);
|
|
|
|
|
m_rdVarp->addNext(m_valVarp);
|
|
|
|
|
varp->addNextHere(m_rdVarp);
|
2022-01-01 18:24:19 +01:00
|
|
|
}
|
2021-12-17 18:56:33 +01:00
|
|
|
};
|
2022-01-01 18:24:19 +01:00
|
|
|
|
2025-03-04 16:12:02 +01:00
|
|
|
public:
|
2024-01-20 21:06:46 +01:00
|
|
|
struct ForceComponentsVarScope final {
|
2021-12-17 18:56:33 +01:00
|
|
|
AstVarScope* const m_rdVscp; // New variable to replace read references with
|
|
|
|
|
AstVarScope* const m_valVscp; // Forced value
|
2025-03-04 16:12:02 +01:00
|
|
|
AstVarScope* const m_enVscp; // Force enabled signal
|
2026-02-17 01:05:07 +01:00
|
|
|
V3UniqueNames m_iterNames; // Names for loop iteration variables
|
2021-12-17 18:56:33 +01:00
|
|
|
explicit ForceComponentsVarScope(AstVarScope* vscp, ForceComponentsVar& fcv)
|
|
|
|
|
: m_rdVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_rdVarp}}
|
2025-03-04 16:12:02 +01:00
|
|
|
, m_valVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_valVarp}}
|
2026-02-17 01:05:07 +01:00
|
|
|
, m_enVscp{new AstVarScope{vscp->fileline(), vscp->scopep(), fcv.m_enVarp}}
|
|
|
|
|
, m_iterNames{"__VForceIter"} {
|
2021-12-17 18:56:33 +01:00
|
|
|
m_rdVscp->addNext(m_enVscp);
|
|
|
|
|
m_rdVscp->addNext(m_valVscp);
|
|
|
|
|
vscp->addNextHere(m_rdVscp);
|
2022-01-01 18:24:19 +01:00
|
|
|
|
2021-12-17 18:56:33 +01:00
|
|
|
FileLine* const flp = vscp->fileline();
|
2022-01-01 18:24:19 +01:00
|
|
|
|
2025-12-16 08:35:59 +01:00
|
|
|
// 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);
|
|
|
|
|
|
2026-02-17 01:05:07 +01:00
|
|
|
// 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<AstAssign*> 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();
|
2025-12-16 08:35:59 +01:00
|
|
|
}
|
2026-02-17 01:05:07 +01:00
|
|
|
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});
|
2021-12-17 18:56:33 +01:00
|
|
|
}
|
2026-02-17 01:05:07 +01:00
|
|
|
activeInitp->addStmtsp(new AstInitial{flp, enInitStmtsp});
|
2021-12-17 18:56:33 +01:00
|
|
|
{ // Add the combinational override
|
2025-03-04 16:12:02 +01:00
|
|
|
// 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);
|
2025-09-09 23:39:44 +02:00
|
|
|
itemsp->addNext(new AstSenItem{flp, VEdgeType::ET_CHANGED, origp});
|
2021-12-17 18:56:33 +01:00
|
|
|
AstActive* const activep
|
2025-03-04 16:12:02 +01:00
|
|
|
= new AstActive{flp, "force-update", new AstSenTree{flp, itemsp}};
|
2025-08-18 01:14:34 +02:00
|
|
|
activep->senTreeStorep(activep->sentreep());
|
2025-12-16 08:35:59 +01:00
|
|
|
|
2026-02-17 01:05:07 +01:00
|
|
|
activep->addStmtsp(
|
|
|
|
|
new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, rdUpdateStmtsp});
|
2022-09-15 20:43:56 +02:00
|
|
|
vscp->scopep()->addBlocksp(activep);
|
2022-01-01 18:24:19 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-17 01:05:07 +01:00
|
|
|
AstNodeStmt* getForcedUpdateStmtsRecursep(AstNodeExpr* const lhsp, AstVarScope* const vscp,
|
|
|
|
|
AstVarRef* const lhsVarRefp,
|
|
|
|
|
std::vector<AstAssign*>& 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<uint32_t>(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;
|
2025-12-16 08:35:59 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-17 01:05:07 +01:00
|
|
|
AstNodeExpr* forcedUpdate(AstVarScope* const vscp, AstNodeExpr* exprp = nullptr,
|
|
|
|
|
AstVarRef* const varRefToReplacep = nullptr) const {
|
2025-03-04 16:12:02 +01:00
|
|
|
FileLine* const flp = vscp->fileline();
|
2025-12-16 08:35:59 +01:00
|
|
|
AstVarRef* origRefp = new AstVarRef{flp, vscp, VAccess::READ};
|
|
|
|
|
ForceState::markNonReplaceable(origRefp);
|
2026-02-17 01:05:07 +01:00
|
|
|
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);
|
2025-03-04 16:12:02 +01:00
|
|
|
if (ForceState::isRangedDType(vscp)) {
|
|
|
|
|
return new AstOr{
|
2026-02-17 01:05:07 +01:00
|
|
|
flp, new AstAnd{flp, enExprp, valExprp},
|
|
|
|
|
new AstAnd{flp, new AstNot{flp, enExprp->cloneTreePure(false)}, origExprp}};
|
2025-03-04 16:12:02 +01:00
|
|
|
}
|
2026-02-17 01:05:07 +01:00
|
|
|
return new AstCond{flp, enExprp, valExprp, origExprp};
|
2025-03-04 16:12:02 +01:00
|
|
|
}
|
2021-12-17 18:56:33 +01:00
|
|
|
};
|
|
|
|
|
|
2025-03-04 16:12:02 +01:00
|
|
|
private:
|
2021-12-17 18:56:33 +01:00
|
|
|
// NODE STATE
|
|
|
|
|
// AstVar::user1p -> ForceComponentsVar* instance (via m_forceComponentsVar)
|
|
|
|
|
// AstVarScope::user1p -> ForceComponentsVarScope* instance (via m_forceComponentsVarScope)
|
|
|
|
|
// AstVarRef::user2 -> Flag indicating not to replace reference
|
2025-12-16 08:35:59 +01:00
|
|
|
// AstAssign::user2 -> Flag indicating that assignment was created for AstRelease
|
2025-10-08 15:39:50 +02:00
|
|
|
// AstVarScope::user3p -> AstAssign*, the assignment <lhs>__VforceVal = <rhs>
|
2021-12-17 18:56:33 +01:00
|
|
|
const VNUser1InUse m_user1InUse;
|
|
|
|
|
const VNUser2InUse m_user2InUse;
|
2025-03-04 16:12:02 +01:00
|
|
|
const VNUser3InUse m_user3InUse;
|
2021-12-17 18:56:33 +01:00
|
|
|
AstUser1Allocator<AstVar, ForceComponentsVar> m_forceComponentsVar;
|
|
|
|
|
AstUser1Allocator<AstVarScope, ForceComponentsVarScope> m_forceComponentsVarScope;
|
2025-08-06 23:37:00 +02:00
|
|
|
std::unordered_map<const AstVarScope*,
|
|
|
|
|
std::pair<std::unordered_set<AstVarScope*>, std::vector<AstVarScope*>>>
|
|
|
|
|
m_valVscps;
|
|
|
|
|
// `valVscp` force components of a forced RHS
|
2021-12-17 18:56:33 +01:00
|
|
|
|
2026-02-17 01:05:07 +01:00
|
|
|
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);
|
2025-12-16 08:35:59 +01:00
|
|
|
}
|
2026-02-17 01:05:07 +01:00
|
|
|
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());
|
2025-12-16 08:35:59 +01:00
|
|
|
}
|
2026-02-17 01:05:07 +01:00
|
|
|
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)) {
|
2025-12-16 08:35:59 +01:00
|
|
|
return origDTypep;
|
|
|
|
|
} else if (VN_IS(origDTypep, BasicDType)) {
|
|
|
|
|
return isRangedDType(varp) ? origDTypep : varp->findBitDType();
|
|
|
|
|
} else if (VN_IS(origDTypep, PackArrayDType)) {
|
|
|
|
|
return origDTypep;
|
2026-02-17 01:05:07 +01:00
|
|
|
} else if (VN_IS(origDTypep, NodeUOrStructDType)) {
|
2025-12-16 08:35:59 +01:00
|
|
|
return origDTypep;
|
|
|
|
|
} else {
|
|
|
|
|
varp->v3fatalSrc("Unsupported: Force of variable of unhandled data type");
|
|
|
|
|
return origDTypep;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-04 16:12:02 +01:00
|
|
|
public:
|
|
|
|
|
// CONSTRUCTORS
|
|
|
|
|
ForceState() = default;
|
|
|
|
|
VL_UNCOPYABLE(ForceState);
|
|
|
|
|
|
|
|
|
|
// STATIC METHODS
|
2026-02-17 01:05:07 +01:00
|
|
|
static bool isRangedDType(const AstNode* const nodep) {
|
2023-02-05 02:37:36 +01:00
|
|
|
// 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();
|
|
|
|
|
}
|
2025-03-04 16:12:02 +01:00
|
|
|
static bool isNotReplaceable(const AstVarRef* const nodep) { return nodep->user2(); }
|
|
|
|
|
static void markNonReplaceable(AstVarRef* const nodep) { nodep->user2SetOnce(); }
|
2025-08-06 23:37:00 +02:00
|
|
|
|
|
|
|
|
// Get all ValVscps for a VarScope
|
|
|
|
|
const std::vector<AstVarScope*>* getValVscps(AstVarRef* const refp) const {
|
|
|
|
|
auto it = m_valVscps.find(refp->varScopep());
|
|
|
|
|
if (it != m_valVscps.end()) return &(it->second.second);
|
|
|
|
|
return nullptr;
|
2025-03-04 16:12:02 +01:00
|
|
|
}
|
2025-08-06 23:37:00 +02:00
|
|
|
|
|
|
|
|
// 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);
|
2025-03-04 16:12:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// METHODS
|
2021-12-17 18:56:33 +01:00
|
|
|
const ForceComponentsVarScope& getForceComponents(AstVarScope* vscp) {
|
|
|
|
|
AstVar* const varp = vscp->varp();
|
|
|
|
|
return m_forceComponentsVarScope(vscp, vscp, m_forceComponentsVar(varp, varp));
|
2022-01-01 18:24:19 +01:00
|
|
|
}
|
2025-03-04 16:12:02 +01:00
|
|
|
ForceComponentsVarScope* tryGetForceComponents(AstVarRef* nodep) const {
|
|
|
|
|
return m_forceComponentsVarScope.tryGet(nodep->varScopep());
|
|
|
|
|
}
|
2025-10-08 15:39:50 +02:00
|
|
|
void setValVscpAssign(AstVarScope* valVscp, AstAssign* rhsExpr) { valVscp->user3p(rhsExpr); }
|
|
|
|
|
AstAssign* getValVscpAssign(AstVarScope* valVscp) const {
|
|
|
|
|
return VN_CAST(valVscp->user3p(), Assign);
|
2025-07-11 03:12:44 +02:00
|
|
|
}
|
2025-03-04 16:12:02 +01:00
|
|
|
};
|
2022-01-01 18:24:19 +01:00
|
|
|
|
2025-03-04 16:12:02 +01:00
|
|
|
class ForceConvertVisitor final : public VNVisitor {
|
|
|
|
|
// STATE
|
|
|
|
|
ForceState& m_state;
|
|
|
|
|
|
|
|
|
|
// STATIC METHODS
|
2021-12-17 18:56:33 +01:00
|
|
|
// Replace each AstNodeVarRef in the given 'nodep' that writes a variable by transforming the
|
|
|
|
|
// referenced AstVarScope with the given function.
|
2025-03-04 16:12:02 +01:00
|
|
|
static void transformWritenVarScopes(AstNode* nodep,
|
|
|
|
|
std::function<AstVarScope*(AstVarScope*)> f) {
|
2021-12-17 18:56:33 +01:00
|
|
|
UASSERT_OBJ(nodep->backp(), nodep, "Must have backp, otherwise will be lost if replaced");
|
2022-10-20 14:48:44 +02:00
|
|
|
nodep->foreach([&f](AstNodeVarRef* refp) {
|
2021-12-17 18:56:33 +01:00
|
|
|
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});
|
2022-07-31 19:13:55 +02:00
|
|
|
VL_DO_DANGLING(refp->deleteTree(), refp);
|
2021-12-17 18:56:33 +01:00
|
|
|
});
|
2022-01-01 18:24:19 +01:00
|
|
|
}
|
|
|
|
|
|
2025-03-04 16:12:02 +01:00
|
|
|
// VISITORS
|
2021-12-17 18:56:33 +01:00
|
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
2022-01-01 18:24:19 +01:00
|
|
|
|
2021-12-17 18:56:33 +01:00
|
|
|
void visit(AstAssignForce* nodep) override {
|
|
|
|
|
// The AstAssignForce node will be removed for sure
|
|
|
|
|
FileLine* const flp = nodep->fileline();
|
2022-11-13 21:33:11 +01:00
|
|
|
AstNodeExpr* const lhsp = nodep->lhsp(); // The LValue we are forcing
|
|
|
|
|
AstNodeExpr* const rhsp = nodep->rhsp(); // The value we are forcing it to
|
2024-05-08 14:36:24 +02:00
|
|
|
VNRelinker relinker;
|
|
|
|
|
nodep->unlinkFrBack(&relinker);
|
|
|
|
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
2022-01-01 18:24:19 +01:00
|
|
|
|
2021-12-17 18:56:33 +01:00
|
|
|
// Set corresponding enable signals to ones
|
2025-03-04 16:12:02 +01:00
|
|
|
V3Number ones{lhsp, ForceState::isRangedDType(lhsp) ? lhsp->width() : 1};
|
2021-12-17 18:56:33 +01:00
|
|
|
ones.setAllBits1();
|
|
|
|
|
AstAssign* const setEnp
|
2023-09-17 04:50:54 +02:00
|
|
|
= new AstAssign{flp, lhsp->cloneTreePure(false), new AstConst{rhsp->fileline(), ones}};
|
2021-12-17 18:56:33 +01:00
|
|
|
transformWritenVarScopes(setEnp->lhsp(), [this](AstVarScope* vscp) {
|
2025-03-04 16:12:02 +01:00
|
|
|
return m_state.getForceComponents(vscp).m_enVscp;
|
2021-12-17 18:56:33 +01:00
|
|
|
});
|
|
|
|
|
// Set corresponding value signals to the forced value
|
|
|
|
|
AstAssign* const setValp
|
2023-09-17 04:50:54 +02:00
|
|
|
= new AstAssign{flp, lhsp->cloneTreePure(false), rhsp->cloneTreePure(false)};
|
2025-10-08 15:39:50 +02:00
|
|
|
transformWritenVarScopes(setValp->lhsp(), [this, rhsp, setValp](AstVarScope* vscp) {
|
2025-03-04 16:12:02 +01:00
|
|
|
AstVarScope* const valVscp = m_state.getForceComponents(vscp).m_valVscp;
|
2025-10-08 15:39:50 +02:00
|
|
|
m_state.setValVscpAssign(valVscp, setValp);
|
2025-08-06 23:37:00 +02:00
|
|
|
rhsp->foreach([valVscp, this](AstVarRef* refp) { m_state.addValVscp(refp, valVscp); });
|
2025-03-04 16:12:02 +01:00
|
|
|
return valVscp;
|
2021-12-17 18:56:33 +01:00
|
|
|
});
|
2025-03-04 16:12:02 +01:00
|
|
|
|
|
|
|
|
// Set corresponding read signal directly as well, in case something in the same
|
|
|
|
|
// process reads it later
|
2021-12-17 18:56:33 +01:00
|
|
|
AstAssign* const setRdp = new AstAssign{flp, lhsp->unlinkFrBack(), rhsp->unlinkFrBack()};
|
|
|
|
|
transformWritenVarScopes(setRdp->lhsp(), [this](AstVarScope* vscp) {
|
2025-03-04 16:12:02 +01:00
|
|
|
return m_state.getForceComponents(vscp).m_rdVscp;
|
2021-12-17 18:56:33 +01:00
|
|
|
});
|
2022-01-01 18:24:19 +01:00
|
|
|
|
2021-12-17 18:56:33 +01:00
|
|
|
setEnp->addNext(setValp);
|
|
|
|
|
setEnp->addNext(setRdp);
|
|
|
|
|
relinker.relink(setEnp);
|
2022-01-01 18:24:19 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-17 18:56:33 +01:00
|
|
|
void visit(AstRelease* nodep) override {
|
2024-05-08 14:36:24 +02:00
|
|
|
FileLine* const flp = nodep->fileline();
|
|
|
|
|
AstNodeExpr* const lhsp = nodep->lhsp(); // The LValue we are releasing
|
2021-12-17 18:56:33 +01:00
|
|
|
// The AstRelease node will be removed for sure
|
|
|
|
|
VNRelinker relinker;
|
|
|
|
|
nodep->unlinkFrBack(&relinker);
|
2024-05-08 14:36:24 +02:00
|
|
|
VL_DO_DANGLING(pushDeletep(nodep), nodep);
|
2022-01-01 18:24:19 +01:00
|
|
|
|
2021-12-17 18:56:33 +01:00
|
|
|
// Set corresponding enable signals to zero
|
2025-03-04 16:12:02 +01:00
|
|
|
V3Number zero{lhsp, ForceState::isRangedDType(lhsp) ? lhsp->width() : 1};
|
2021-12-17 18:56:33 +01:00
|
|
|
zero.setAllBits0();
|
|
|
|
|
AstAssign* const resetEnp
|
2023-09-17 04:50:54 +02:00
|
|
|
= new AstAssign{flp, lhsp->cloneTreePure(false), new AstConst{lhsp->fileline(), zero}};
|
2021-12-17 18:56:33 +01:00
|
|
|
transformWritenVarScopes(resetEnp->lhsp(), [this](AstVarScope* vscp) {
|
2025-03-04 16:12:02 +01:00
|
|
|
return m_state.getForceComponents(vscp).m_enVscp;
|
2021-12-17 18:56:33 +01:00
|
|
|
});
|
2025-07-11 03:12:44 +02:00
|
|
|
|
|
|
|
|
// 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.
|
2021-12-17 18:56:33 +01:00
|
|
|
AstAssign* const resetRdp
|
2025-06-28 21:45:45 +02:00
|
|
|
= new AstAssign{flp, lhsp->cloneTreePure(false), lhsp->unlinkFrBack()};
|
2025-12-16 08:35:59 +01:00
|
|
|
resetRdp->user2(true);
|
2021-12-17 18:56:33 +01:00
|
|
|
// Replace write refs on the LHS
|
2025-11-05 16:20:18 +01:00
|
|
|
resetRdp->lhsp()->foreach([this](AstVarRef* refp) {
|
2021-12-17 18:56:33 +01:00
|
|
|
if (refp->access() != VAccess::WRITE) return;
|
|
|
|
|
AstVarScope* const vscp = refp->varScopep();
|
2025-11-05 16:20:18 +01:00
|
|
|
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);
|
|
|
|
|
}
|
2021-12-17 18:56:33 +01:00
|
|
|
});
|
|
|
|
|
// Replace write refs on RHS
|
2026-02-17 01:05:07 +01:00
|
|
|
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);
|
2025-12-16 08:35:59 +01:00
|
|
|
} 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 {
|
2026-02-17 01:05:07 +01:00
|
|
|
refp->replaceWith(m_state.getForceComponents(vscp).forcedUpdate(vscp));
|
2025-12-16 08:35:59 +01:00
|
|
|
VL_DO_DANGLING(refp->deleteTree(), refp);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-01-01 18:24:19 +01:00
|
|
|
|
2023-05-12 12:57:12 +02:00
|
|
|
resetRdp->addNext(resetEnp);
|
|
|
|
|
relinker.relink(resetRdp);
|
2022-01-01 18:24:19 +01:00
|
|
|
}
|
|
|
|
|
|
2021-12-19 20:45:06 +01:00
|
|
|
void visit(AstVarScope* nodep) override {
|
|
|
|
|
// If this signal is marked externally forceable, create the public force signals
|
|
|
|
|
if (nodep->varp()->isForceable()) {
|
2026-01-10 09:48:46 +01:00
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-04 16:12:02 +01:00
|
|
|
const ForceState::ForceComponentsVarScope& fc = m_state.getForceComponents(nodep);
|
2023-11-05 17:58:22 +01:00
|
|
|
fc.m_enVscp->varp()->sigUserRWPublic(true);
|
|
|
|
|
fc.m_valVscp->varp()->sigUserRWPublic(true);
|
2021-12-19 20:45:06 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-04 16:12:02 +01:00
|
|
|
public:
|
2021-12-17 18:56:33 +01:00
|
|
|
// CONSTRUCTOR
|
2025-08-21 10:43:37 +02:00
|
|
|
// cppcheck-suppress constParameterCallback
|
|
|
|
|
ForceConvertVisitor(AstNetlist* nodep, ForceState& state)
|
2025-03-04 16:12:02 +01:00
|
|
|
: m_state{state} {
|
2021-12-17 18:56:33 +01:00
|
|
|
// Transform all force and release statements
|
|
|
|
|
iterateAndNextNull(nodep->modulesp());
|
2025-03-04 16:12:02 +01:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class ForceReplaceVisitor final : public VNVisitor {
|
|
|
|
|
// STATE
|
|
|
|
|
const ForceState& m_state;
|
|
|
|
|
AstNodeStmt* m_stmtp = nullptr;
|
|
|
|
|
bool m_inLogic = false;
|
2025-12-16 08:35:59 +01:00
|
|
|
bool m_releaseRhs = false; // Inside RHS of assignment created for release statement
|
2021-12-17 18:56:33 +01:00
|
|
|
|
2025-03-04 16:12:02 +01:00
|
|
|
// 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);
|
|
|
|
|
}
|
2025-12-16 08:35:59 +01:00
|
|
|
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());
|
|
|
|
|
}
|
2025-03-04 16:12:02 +01:00
|
|
|
void visit(AstCFunc* nodep) override { iterateLogic(nodep); }
|
|
|
|
|
void visit(AstCoverToggle* nodep) override { iterateLogic(nodep); }
|
|
|
|
|
void visit(AstNodeProcedure* nodep) override { iterateLogic(nodep); }
|
Internals: Make AstAssignW a procedural statement (#6280) (#6556)
Initial idea was to remodel AssignW as Assign under Alway. Trying that
uncovered some issues, the most difficult of them was that a delay
attached to a continuous assignment behaves differently from a delay
attached to a blocking assignment statement, so we need to keep the
knowledge of which flavour an assignment was until V3Timing.
So instead of removing AstAssignW, we always wrap it in an AstAlways,
with a special `keyword()` type. This makes it into a proper procedural
statement, which is almost equivalent to AstAssign, except for the case
when they contain a delay. We still gain the benefits of #6280 and can
simplify some code. Every AstNodeStmt should now be under an
AstNodeProcedure - which we should rename to AstProcess, or an
AstNodeFTask). As a result, V3Table can now handle AssignW for free.
Also uncovered and fixed a bug in handling intra-assignment delays if
a function is present on the RHS of an AssignW.
There is more work to be done towards #6280, and potentially simplifying
AssignW handing, but this is the minimal change required to tick it off
the TODO list for #6280.
2025-10-14 10:05:19 +02:00
|
|
|
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);
|
|
|
|
|
}
|
2025-03-04 16:12:02 +01:00
|
|
|
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);
|
2022-01-01 18:24:19 +01:00
|
|
|
}
|
2025-03-04 16:12:02 +01:00
|
|
|
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();
|
2026-02-17 01:05:07 +01:00
|
|
|
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);
|
|
|
|
|
}
|
2025-03-04 16:12:02 +01:00
|
|
|
m_stmtp->addNextHere(new AstAssign{flp, lhsp, rhsp});
|
|
|
|
|
}
|
|
|
|
|
// Emit valVscp update after each write to any VarRef on forced RHS.
|
2025-08-06 23:37:00 +02:00
|
|
|
if (!m_state.getValVscps(nodep)) break;
|
|
|
|
|
for (AstVarScope* const valVscp : *m_state.getValVscps(nodep)) {
|
2025-03-04 16:12:02 +01:00
|
|
|
FileLine* const flp = nodep->fileline();
|
2025-10-08 15:39:50 +02:00
|
|
|
AstAssign* assignp = m_state.getValVscpAssign(valVscp);
|
|
|
|
|
UASSERT_OBJ(assignp, flp, "Missing stored assignment for forced valVscp");
|
|
|
|
|
|
|
|
|
|
assignp = assignp->cloneTreePure(false);
|
2025-03-04 16:12:02 +01:00
|
|
|
|
2025-10-08 15:39:50 +02:00
|
|
|
assignp->rhsp()->foreach(
|
|
|
|
|
[](AstVarRef* refp) { ForceState::markNonReplaceable(refp); });
|
2025-03-04 16:12:02 +01:00
|
|
|
|
2025-10-08 15:39:50 +02:00
|
|
|
m_stmtp->addNextHere(assignp);
|
2025-03-04 16:12:02 +01:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
2025-07-31 12:48:37 +02:00
|
|
|
if (!m_inLogic) return;
|
2025-08-06 23:37:00 +02:00
|
|
|
if (m_state.tryGetForceComponents(nodep) || m_state.getValVscps(nodep)) {
|
2025-09-20 14:19:42 +02:00
|
|
|
nodep->v3warn(
|
|
|
|
|
E_UNSUPPORTED,
|
2025-07-31 12:48:37 +02:00
|
|
|
"Unsupported: Signals used via read-write reference cannot be forced");
|
|
|
|
|
}
|
2025-03-04 16:12:02 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2022-01-01 18:24:19 +01:00
|
|
|
}
|
2025-03-04 16:12:02 +01:00
|
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
2022-01-01 18:24:19 +01:00
|
|
|
|
|
|
|
|
public:
|
2025-03-04 16:12:02 +01:00
|
|
|
// CONSTRUCTOR
|
|
|
|
|
explicit ForceReplaceVisitor(AstNetlist* nodep, const ForceState& state)
|
|
|
|
|
: m_state{state} {
|
|
|
|
|
iterateChildren(nodep);
|
|
|
|
|
}
|
2022-01-01 18:24:19 +01:00
|
|
|
};
|
|
|
|
|
//######################################################################
|
2021-12-17 18:56:33 +01:00
|
|
|
//
|
2022-01-01 18:24:19 +01:00
|
|
|
|
|
|
|
|
void V3Force::forceAll(AstNetlist* nodep) {
|
2025-05-23 02:29:32 +02:00
|
|
|
UINFO(2, __FUNCTION__ << ":");
|
2021-12-17 18:56:33 +01:00
|
|
|
if (!v3Global.hasForceableSignals()) return;
|
2025-03-04 16:12:02 +01:00
|
|
|
{
|
|
|
|
|
ForceState state;
|
|
|
|
|
{ ForceConvertVisitor{nodep, state}; }
|
|
|
|
|
{ ForceReplaceVisitor{nodep, state}; }
|
|
|
|
|
V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3);
|
|
|
|
|
}
|
2022-01-01 18:24:19 +01:00
|
|
|
}
|