verilator/src/V3LinkInc.cpp

421 lines
18 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Replace increments/decrements with new variables
//
// 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
//
//*************************************************************************
// V3LinkInc's Transformations:
//
// prepost_expr_visit
// PREINC/PREDEC
// Create a temporary __VIncrementX variable, assign the value of
// the current variable value to it, substitute the current
// variable with the temporary one in the statement.
// Increment/decrement the original variable with by the given
// value.
// POSTINC/POSTDEC
// Increment/decrement the current variable by the given value.
// Create a temporary __VIncrementX variable, assign the value of
// of the current variable (after the operation) to it. Substitute
// The original variable with the temporary one in the statement.
//
// prepost_stmt_visit
// PREINC/PREDEC/POSTINC/POSTDEC
// Increment/decrement the current variable by the given value.
// The order (pre/post) doesn't matter outside statements thus
// the pre/post operations are treated equally and there is no
// need for a temporary variable.
//
// prepost_stmt_sel_visit
// For e.g. 'array[something_with_side_eff]++', common in UVM etc
// PREADD/PRESUB/POSTADD/POSTSUB
// Create temporary with array index.
// Increment/decrement using index of the temporary.
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3LinkInc.h"
#include "V3LinkLValue.h"
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
class LinkIncVisitor final : public VNVisitor {
// STATE
AstNodeFTask* m_ftaskp = nullptr; // Function or task we're inside
AstNodeModule* m_modp = nullptr; // Module we're inside
int m_modCompoundAssignmentsNum = 0; // Var name counter
AstNode* m_insStmtp = nullptr; // Where to insert statement
bool m_unsupportedHere = false; // Used to detect where it's not supported yet
// METHODS
void insertOnTop(AstNode* newp) {
// Add the thing directly under the current TFunc/Module
AstNode* stmtsp = nullptr;
if (m_ftaskp) {
stmtsp = m_ftaskp->stmtsp();
} else if (m_modp) {
stmtsp = m_modp->stmtsp();
}
UASSERT_OBJ(stmtsp, newp, "Variable not under FTASK/MODULE");
newp->addNext(stmtsp->unlinkFrBackWithNext());
if (m_ftaskp) {
m_ftaskp->addStmtsp(newp);
} else if (m_modp) {
m_modp->addStmtsp(newp);
}
}
void insertBeforeStmt(AstNode* nodep, AstNode* newp) {
// Return node that must be visited, if any
UINFOTREE(9, newp, "", "newstmt");
UASSERT_OBJ(m_insStmtp, nodep, "Expression not underneath a statement");
m_insStmtp->addHereThisAsNext(newp);
}
// VISITORS
void visit(AstNodeModule* nodep) override {
if (nodep->dead()) return;
VL_RESTORER(m_modp);
VL_RESTORER(m_modCompoundAssignmentsNum);
m_modp = nodep;
m_modCompoundAssignmentsNum = 0;
iterateChildren(nodep);
}
void visit(AstNodeFTask* nodep) override {
VL_RESTORER(m_ftaskp);
m_ftaskp = nodep;
iterateChildren(nodep);
}
void visit(AstNodeCoverOrAssert* nodep) override {
VL_RESTORER(m_insStmtp);
m_insStmtp = nodep;
iterateAndNextNull(nodep->propp());
m_insStmtp = nullptr;
// Note: no iterating over sentreep here as they will be ignored anyway
if (AstAssert* const assertp = VN_CAST(nodep, Assert)) {
iterateAndNextNull(assertp->failsp());
} else if (AstAssertIntrinsic* const intrinsicp = VN_CAST(nodep, AssertIntrinsic)) {
iterateAndNextNull(intrinsicp->failsp());
}
iterateAndNextNull(nodep->passsp());
}
void visit(AstLoop* nodep) override {
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
m_insStmtp = nullptr; // First thing should be new statement
iterateAndNextNull(nodep->stmtsp());
m_insStmtp = nullptr; // Next thing should be new statement
}
void visit(AstNodeForeach* nodep) override {
// Special, as statements need to be put in different places
// Body insert just before themselves
m_insStmtp = nullptr; // First thing should be new statement
iterateChildren(nodep);
// Done the loop
m_insStmtp = nullptr; // Next thing should be new statement
}
void visit(AstJumpBlock* nodep) override {
// Special, as statements need to be put in different places
// Body insert just before themselves
m_insStmtp = nullptr; // First thing should be new statement
iterateChildren(nodep);
// Done the loop
m_insStmtp = nullptr; // Next thing should be new statement
}
void visit(AstNodeIf* nodep) override {
m_insStmtp = nodep;
iterateAndNextNull(nodep->condp());
m_insStmtp = nullptr;
iterateAndNextNull(nodep->thensp());
iterateAndNextNull(nodep->elsesp());
m_insStmtp = nullptr;
}
void visit(AstCaseItem* nodep) override {
{
VL_RESTORER(m_unsupportedHere);
m_unsupportedHere = true;
iterateAndNextNull(nodep->condsp());
}
m_insStmtp = nullptr; // Next thing should be new statement
iterateAndNextNull(nodep->stmtsp());
}
void visit(AstDelay* nodep) override {
m_insStmtp = nodep;
iterateAndNextNull(nodep->lhsp());
m_insStmtp = nullptr;
iterateAndNextNull(nodep->stmtsp());
m_insStmtp = nullptr;
}
void visit(AstEventControl* nodep) override {
m_insStmtp = nullptr;
iterateAndNextNull(nodep->stmtsp());
m_insStmtp = nullptr;
}
void visit(AstWait* nodep) override {
m_insStmtp = nodep;
iterateAndNextNull(nodep->condp());
m_insStmtp = nullptr;
iterateAndNextNull(nodep->stmtsp());
m_insStmtp = nullptr;
}
void visit(AstBegin* nodep) override {
m_insStmtp = nullptr; // Pretend not a statement TODO: parse ++/-- as ExprStmt
iterateChildren(nodep);
m_insStmtp = nullptr; // Next thing should be new statement
}
void visit(AstStmtExpr* nodep) override {
AstNodeExpr* const exprp = nodep->exprp();
if (VN_IS(exprp, PostInc) || VN_IS(exprp, PostDec) || VN_IS(exprp, PreInc)
|| VN_IS(exprp, PreDec)) {
// Repalce this StmtExpr with the expression, visiting it will turn it into a NodeStmt
nodep->replaceWith(exprp->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(nodep), nodep);
m_insStmtp = nullptr;
iterate(exprp);
m_insStmtp = nullptr;
return;
}
visit(static_cast<AstNodeStmt*>(nodep));
}
void visit(AstNodeStmt* nodep) override {
m_insStmtp = nodep;
iterateChildren(nodep);
m_insStmtp = nullptr; // Next thing should be new statement
}
void unsupported_visit(AstNode* nodep) {
VL_RESTORER(m_unsupportedHere);
m_unsupportedHere = true;
UINFO(9, "Marking unsupported " << nodep);
iterateChildren(nodep);
}
void visit(AstLogAnd* nodep) override { unsupported_visit(nodep); }
void visit(AstLogOr* nodep) override { unsupported_visit(nodep); }
void visit(AstLogEq* nodep) override { unsupported_visit(nodep); }
void visit(AstLogIf* nodep) override { unsupported_visit(nodep); }
void visit(AstCond* nodep) override { unsupported_visit(nodep); }
void visit(AstPropSpec* nodep) override { unsupported_visit(nodep); }
void prepost_visit(AstNodeUniop* const nodep) {
// Check if we are underneath a statement
AstSelBit* const selbitp = VN_CAST(nodep->lhsp(), SelBit);
if (!m_insStmtp && selbitp && VN_IS(selbitp->fromp(), NodeVarRef)
&& !selbitp->bitp()->isPure()) {
prepost_stmt_sel_visit(nodep);
} else {
if (!m_insStmtp) {
prepost_stmt_visit(nodep);
} else {
prepost_expr_visit(nodep);
}
}
}
AstNodeExpr* getOperationp(AstNode* const nodep, AstNodeExpr* const lhsp,
AstNodeExpr* const rhsp) {
if (VN_IS(nodep, PreDec) || VN_IS(nodep, PostDec)) {
return new AstSub{nodep->fileline(), lhsp, rhsp};
}
if (VN_IS(nodep, PreInc) || VN_IS(nodep, PostInc)) {
return new AstAdd{nodep->fileline(), lhsp, rhsp};
}
if (AstAssignCompound* assignp = VN_CAST(nodep, AssignCompound)) {
switch (assignp->operation()) {
case AstAssignCompound::operation::Add:
return new AstAdd{nodep->fileline(), lhsp, rhsp};
case AstAssignCompound::operation::And:
return new AstAnd{nodep->fileline(), lhsp, rhsp};
case AstAssignCompound::operation::Div:
return new AstDiv{nodep->fileline(), lhsp, rhsp};
case AstAssignCompound::operation::ModDiv:
return new AstModDiv{nodep->fileline(), lhsp, rhsp};
case AstAssignCompound::operation::Mul:
return new AstMul{nodep->fileline(), lhsp, rhsp};
case AstAssignCompound::operation::Or: return new AstOr{nodep->fileline(), lhsp, rhsp};
case AstAssignCompound::operation::ShiftL:
return new AstShiftL{nodep->fileline(), lhsp, rhsp};
case AstAssignCompound::operation::ShiftR:
return new AstShiftR{nodep->fileline(), lhsp, rhsp};
case AstAssignCompound::operation::ShiftRS:
return new AstShiftRS{nodep->fileline(), lhsp, rhsp};
case AstAssignCompound::operation::Sub:
return new AstSub{nodep->fileline(), lhsp, rhsp};
case AstAssignCompound::operation::Xor:
return new AstXor{nodep->fileline(), lhsp, rhsp};
}
}
nodep->v3fatalSrc("Unhandled compound assignment operation");
}
void prepost_stmt_sel_visit(AstNodeUniop* const nodep) {
// Special case array[something]++, see comments at file top
// UINFOTREE(9, nodep, "", "pp-stmt-sel-in");
iterateChildren(nodep);
FileLine* fl = nodep->fileline();
V3Number numOne{fl, 32, 1, false};
AstNodeExpr* const exprp = new AstConst{nodep->fileline(), numOne};
prepost_stmt_sel_visit(nodep, nodep->lhsp(), exprp);
}
void prepost_stmt_sel_visit(AstAssignCompound* const nodep) {
// Special case array[something] += expr, see comments at file top
// UINFOTREE(9, nodep, "", "pp-stmt-sel-in");
AstNodeExpr* const exprp = nodep->rhsp()->unlinkFrBack();
prepost_stmt_sel_visit(nodep, nodep->lhsp(), exprp);
}
void prepost_stmt_sel_visit(AstNode* const nodep, AstNodeExpr* const lhsp,
AstNodeExpr* const exprp) {
AstSelBit* const rdSelbitp = VN_AS(lhsp, SelBit);
AstNodeVarRef* const rdFromp = VN_AS(rdSelbitp->fromp()->cloneTreePure(true), NodeVarRef);
rdFromp->access(VAccess::READ);
AstNodeExpr* const rdBitp = rdSelbitp->bitp()->unlinkFrBack();
AstSelBit* const wrSelbitp = VN_CAST(lhsp, SelBit);
AstNodeExpr* const wrFromp = wrSelbitp->fromp()->unlinkFrBack();
V3LinkLValue::linkLValueSet(wrFromp);
// Prepare a temporary variable
FileLine* const fl = nodep->fileline();
const string name = "__VtempIndex"s + cvtToStr(++m_modCompoundAssignmentsNum);
AstVar* const varp = new AstVar{
fl, VVarType::BLOCKTEMP, name, VFlagChildDType{},
new AstRefDType{fl, AstRefDType::FlagTypeOfExpr{}, rdBitp->cloneTree(true)}};
if (m_ftaskp) varp->funcLocal(true);
// Declare the variable
insertOnTop(varp);
// Define what operation will we be doing
AstAssign* const varAssignp
= new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, rdBitp};
AstNode* const newp = varAssignp;
AstNodeExpr* const valuep
= new AstSelBit{fl, rdFromp, new AstVarRef{fl, varp, VAccess::READ}};
AstNodeExpr* const storeTop
= new AstSelBit{fl, wrFromp, new AstVarRef{fl, varp, VAccess::READ}};
AstAssign* assignp
= new AstAssign{nodep->fileline(), storeTop, getOperationp(nodep, valuep, exprp)};
newp->addNext(assignp);
// if (debug() >= 9) newp->dumpTreeAndNext("-pp-stmt-sel-new: ");
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void prepost_stmt_visit(AstNodeUniop* const nodep) {
iterateChildren(nodep);
AstNodeExpr* const storeTop = nodep->lhsp()->cloneTreePure(true);
AstNodeExpr* const valuep = nodep->lhsp()->unlinkFrBack();
FileLine* const fl = nodep->fileline();
V3Number numOne{fl, 32, 1, false};
AstNodeExpr* const exprp = new AstConst{nodep->fileline(), numOne};
prepost_stmt_visit(nodep, exprp, storeTop, valuep);
}
void prepost_stmt_visit(AstAssignCompound* const nodep) {
AstNodeExpr* const exprp = nodep->rhsp()->unlinkFrBack();
AstNodeExpr* const storeTop = nodep->lhsp()->cloneTreePure(true);
AstNodeExpr* const valuep = nodep->lhsp()->unlinkFrBack();
prepost_stmt_visit(nodep, exprp, storeTop, valuep);
}
void prepost_stmt_visit(AstNode* const nodep, AstNodeExpr* const exprp,
AstNodeExpr* const storeTop, AstNodeExpr* const valuep) {
V3LinkLValue::linkLValueSet(valuep, false);
AstAssign* const assignp
= new AstAssign{nodep->fileline(), storeTop, getOperationp(nodep, valuep, exprp)};
nodep->replaceWith(assignp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void prepost_expr_visit(AstNodeUniop* const nodep) {
iterateChildren(nodep);
if (m_unsupportedHere) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Pre/post increment/decrement operator"
" within a logical expression (&&, ||, ?:, etc.)");
return;
}
AstNodeExpr* const readp = nodep->lhsp();
AstNodeExpr* const writep = nodep->lhsp()->cloneTreePure(true);
V3LinkLValue::linkLValueSet(readp, false);
FileLine* const fl = nodep->fileline();
V3Number numOne{fl, 32, 1, false};
AstNodeExpr* const newconstp = new AstConst{nodep->fileline(), numOne};
// Prepare a temporary variable
const string name = "__Vincrement"s + cvtToStr(++m_modCompoundAssignmentsNum);
AstVar* const varp = new AstVar{
fl, VVarType::BLOCKTEMP, name, VFlagChildDType{},
new AstRefDType{fl, AstRefDType::FlagTypeOfExpr{}, readp->cloneTreePure(true)}};
varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
if (m_ftaskp) varp->funcLocal(true);
// Declare the variable
insertOnTop(varp);
// Define what operation will we be doing
AstNodeExpr* const operp = getOperationp(nodep, readp->cloneTreePure(true), newconstp);
if (VN_IS(nodep, PreInc) || VN_IS(nodep, PreDec)) {
// PreInc/PreDec operations
// Immediately after declaration - increment it by one
AstAssign* const assignp
= new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, operp};
// Immediately after incrementing - assign it to the original variable
assignp->addNext(new AstAssign{fl, writep, new AstVarRef{fl, varp, VAccess::READ}});
insertBeforeStmt(nodep, assignp);
} else {
// PostInc/PostDec operations
// Assign the original variable to the temporary one
AstAssign* const assignp = new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE},
readp->cloneTreePure(true)};
// Increment the original variable by one
assignp->addNext(new AstAssign{fl, writep, operp});
insertBeforeStmt(nodep, assignp);
}
// Replace the node with the temporary
nodep->replaceWith(new AstVarRef{readp->fileline(), varp, VAccess::READ});
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstPreInc* nodep) override { prepost_visit(nodep); }
void visit(AstPostInc* nodep) override { prepost_visit(nodep); }
void visit(AstPreDec* nodep) override { prepost_visit(nodep); }
void visit(AstPostDec* nodep) override { prepost_visit(nodep); }
void visit(AstAssignCompound* nodep) override {
visit(static_cast<AstNodeStmt*>(nodep));
AstSelBit* const selbitp = VN_CAST(nodep->lhsp(), SelBit);
if (!m_insStmtp && selbitp && VN_IS(selbitp->fromp(), NodeVarRef)
&& !selbitp->bitp()->isPure()) {
prepost_stmt_sel_visit(nodep);
} else {
prepost_stmt_visit(nodep);
}
}
void visit(AstGenFor* nodep) override { iterateChildren(nodep); }
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit LinkIncVisitor(AstNetlist* nodep) { iterate(nodep); }
~LinkIncVisitor() override = default;
};
//######################################################################
// Task class functions
void V3LinkInc::linkIncrements(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{ LinkIncVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("linkinc", 0, dumpTreeEitherLevel() >= 3);
}