forked from github/verilator
275 lines
11 KiB
C++
275 lines
11 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Recreate loops to help pack caches
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Copyright 2003-2023 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
|
|
//
|
|
//*************************************************************************
|
|
// V3Reloop's Transformations:
|
|
//
|
|
// Each CFunc:
|
|
// Look for a series of assignments that would look better in a loop:
|
|
//
|
|
// ASSIGN(ARRAYREF(var, #), ARRAYREF(var, #+C))
|
|
// ASSIGN(ARRAYREF(var, #+1), ARRAYREF(var, #+1+C))
|
|
// ->
|
|
// Create __Vilp local variable
|
|
// FOR(__Vilp = low; __Vilp <= high; ++__Vlip)
|
|
// ASSIGN(ARRAYREF(var, __Vilp), ARRAYREF(var, __Vilp + C))
|
|
//
|
|
// Likewise vector assign to the same constant converted to a loop.
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "config_build.h"
|
|
#include "verilatedos.h"
|
|
|
|
#include "V3Reloop.h"
|
|
|
|
#include "V3Ast.h"
|
|
#include "V3Global.h"
|
|
#include "V3Stats.h"
|
|
|
|
#include <algorithm>
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
//######################################################################
|
|
|
|
class ReloopVisitor final : public VNVisitor {
|
|
private:
|
|
// NODE STATE
|
|
// AstCFunc::user1p -> Var* for temp var, 0=not set yet
|
|
const VNUser1InUse m_inuser1;
|
|
|
|
// STATE
|
|
VDouble0 m_statReloops; // Statistic tracking
|
|
VDouble0 m_statReItems; // Statistic tracking
|
|
AstCFunc* m_cfuncp = nullptr; // Current block
|
|
|
|
std::vector<AstNodeAssign*> m_mgAssignps; // List of assignments merging
|
|
AstCFunc* m_mgCfuncp = nullptr; // Parent C function
|
|
const AstNode* m_mgNextp = nullptr; // Next node
|
|
const AstNodeSel* m_mgSelLp = nullptr; // Parent select, nullptr = idle
|
|
const AstNodeSel* m_mgSelRp = nullptr; // Parent select, nullptr = constant
|
|
const AstNodeVarRef* m_mgVarrefLp = nullptr; // Parent varref
|
|
const AstNodeVarRef* m_mgVarrefRp = nullptr; // Parent varref, nullptr = constant
|
|
int64_t m_mgOffset = 0; // Index offset
|
|
const AstConst* m_mgConstRp = nullptr; // Parent RHS constant, nullptr = sel
|
|
uint32_t m_mgIndexLo = 0; // Merge range
|
|
uint32_t m_mgIndexHi = 0; // Merge range
|
|
|
|
// METHODS
|
|
|
|
static AstVar* findCreateVarTemp(FileLine* fl, AstCFunc* cfuncp) {
|
|
AstVar* varp = VN_AS(cfuncp->user1p(), Var);
|
|
if (!varp) {
|
|
const string newvarname{"__Vilp"};
|
|
varp = new AstVar{fl, VVarType::STMTTEMP, newvarname, VFlagLogicPacked{}, 32};
|
|
UASSERT_OBJ(cfuncp, fl, "Assignment not under a function");
|
|
cfuncp->addInitsp(varp);
|
|
cfuncp->user1p(varp);
|
|
}
|
|
return varp;
|
|
}
|
|
void mergeEnd() {
|
|
if (!m_mgAssignps.empty()) {
|
|
const uint32_t items = m_mgIndexHi - m_mgIndexLo + 1;
|
|
UINFO(9, "End merge iter=" << items << " " << m_mgIndexHi << ":" << m_mgIndexLo << " "
|
|
<< m_mgOffset << " " << m_mgAssignps[0] << endl);
|
|
if (items >= static_cast<uint32_t>(v3Global.opt.reloopLimit())) {
|
|
UINFO(6, "Reloop merging items=" << items << " " << m_mgIndexHi << ":"
|
|
<< m_mgIndexLo << " " << m_mgOffset << " "
|
|
<< m_mgAssignps[0] << endl);
|
|
++m_statReloops;
|
|
m_statReItems += items;
|
|
|
|
// Transform first assign into for loop body
|
|
AstNodeAssign* const bodyp = m_mgAssignps.front();
|
|
UASSERT_OBJ(bodyp->lhsp() == m_mgSelLp, bodyp, "Corrupt queue/state");
|
|
FileLine* const fl = bodyp->fileline();
|
|
AstVar* const itp = findCreateVarTemp(fl, m_mgCfuncp);
|
|
|
|
if (m_mgOffset > 0) {
|
|
UASSERT_OBJ(m_mgIndexLo >= m_mgOffset, bodyp,
|
|
"Reloop iteration starts at negative index");
|
|
m_mgIndexLo -= m_mgOffset;
|
|
m_mgIndexHi -= m_mgOffset;
|
|
}
|
|
|
|
AstNode* const initp = new AstAssign{fl, new AstVarRef{fl, itp, VAccess::WRITE},
|
|
new AstConst{fl, m_mgIndexLo}};
|
|
AstNodeExpr* const condp = new AstLte{fl, new AstVarRef{fl, itp, VAccess::READ},
|
|
new AstConst{fl, m_mgIndexHi}};
|
|
AstNode* const incp = new AstAssign{
|
|
fl, new AstVarRef{fl, itp, VAccess::WRITE},
|
|
new AstAdd{fl, new AstConst{fl, 1}, new AstVarRef{fl, itp, VAccess::READ}}};
|
|
AstWhile* const whilep = new AstWhile{fl, condp, nullptr, incp};
|
|
initp->addNext(whilep);
|
|
bodyp->replaceWith(initp);
|
|
whilep->addStmtsp(bodyp);
|
|
|
|
// Replace constant index with new loop index
|
|
AstNodeExpr* const offsetp
|
|
= m_mgOffset == 0 ? nullptr : new AstConst(fl, std::abs(m_mgOffset));
|
|
AstNodeExpr* const lbitp = m_mgSelLp->bitp();
|
|
AstNodeExpr* const lvrefp = new AstVarRef{fl, itp, VAccess::READ};
|
|
lbitp->replaceWith(m_mgOffset > 0 ? new AstAdd{fl, lvrefp, offsetp} : lvrefp);
|
|
VL_DO_DANGLING(lbitp->deleteTree(), lbitp);
|
|
if (m_mgSelRp) { // else constant and no replace
|
|
AstNodeExpr* const rbitp = m_mgSelRp->bitp();
|
|
AstNodeExpr* const rvrefp = new AstVarRef{fl, itp, VAccess::READ};
|
|
rbitp->replaceWith(m_mgOffset < 0 ? new AstAdd{fl, rvrefp, offsetp} : rvrefp);
|
|
VL_DO_DANGLING(rbitp->deleteTree(), lbitp);
|
|
}
|
|
if (debug() >= 9) initp->dumpTree("- new: ");
|
|
if (debug() >= 9) whilep->dumpTree("- new: ");
|
|
|
|
// Remove remaining assigns
|
|
for (AstNodeAssign* assp : m_mgAssignps) {
|
|
if (assp != bodyp) {
|
|
VL_DO_DANGLING(assp->unlinkFrBack()->deleteTree(), assp);
|
|
}
|
|
}
|
|
}
|
|
// Setup for next merge
|
|
m_mgAssignps.clear();
|
|
m_mgSelLp = nullptr;
|
|
m_mgSelRp = nullptr;
|
|
m_mgVarrefLp = nullptr;
|
|
m_mgVarrefRp = nullptr;
|
|
m_mgOffset = 0;
|
|
m_mgConstRp = nullptr;
|
|
}
|
|
}
|
|
|
|
// VISITORS
|
|
void visit(AstCFunc* nodep) override {
|
|
VL_RESTORER(m_cfuncp);
|
|
{
|
|
m_cfuncp = nodep;
|
|
iterateChildren(nodep);
|
|
mergeEnd(); // Finish last pending merge, if any
|
|
}
|
|
}
|
|
void visit(AstNodeAssign* nodep) override {
|
|
if (!m_cfuncp) return;
|
|
|
|
// Left select WordSel or ArraySel
|
|
AstNodeSel* const lselp = VN_CAST(nodep->lhsp(), NodeSel);
|
|
if (!lselp) { // Not ever merged
|
|
mergeEnd();
|
|
return;
|
|
}
|
|
// Of a constant index
|
|
const AstConst* const lbitp = VN_CAST(lselp->bitp(), Const);
|
|
if (!lbitp) {
|
|
mergeEnd();
|
|
return;
|
|
}
|
|
if (lbitp->width() > 32) { // Assoc arrays can do this
|
|
mergeEnd();
|
|
return;
|
|
}
|
|
const uint32_t lindex = lbitp->toUInt();
|
|
// Of variable
|
|
const AstNodeVarRef* const lvarrefp = VN_CAST(lselp->fromp(), NodeVarRef);
|
|
if (!lvarrefp) {
|
|
mergeEnd();
|
|
return;
|
|
}
|
|
|
|
// RHS is a constant or a select
|
|
const AstConst* const rconstp = VN_CAST(nodep->rhsp(), Const);
|
|
const AstNodeSel* const rselp = VN_CAST(nodep->rhsp(), NodeSel);
|
|
const AstNodeVarRef* rvarrefp = nullptr;
|
|
uint32_t rindex = lindex;
|
|
if (rconstp) { // Ok
|
|
} else if (rselp) {
|
|
const AstConst* const rbitp = VN_CAST(rselp->bitp(), Const);
|
|
rvarrefp = VN_CAST(rselp->fromp(), NodeVarRef);
|
|
if (!rbitp || !rvarrefp || lvarrefp->varp() == rvarrefp->varp()) {
|
|
mergeEnd();
|
|
return;
|
|
}
|
|
rindex = rbitp->toUInt();
|
|
} else {
|
|
mergeEnd();
|
|
return;
|
|
}
|
|
|
|
if (m_mgSelLp) { // Old merge
|
|
if (m_mgCfuncp == m_cfuncp // In same function
|
|
&& m_mgNextp == nodep // Consecutive node
|
|
&& m_mgVarrefLp->same(lvarrefp) // Same array on left hand side
|
|
&& (m_mgConstRp // On the right hand side either ...
|
|
? (rconstp && m_mgConstRp->same(rconstp)) // ... same constant
|
|
: (rselp && m_mgVarrefRp->same(rvarrefp))) // ... or same array
|
|
&& (lindex == m_mgIndexLo - 1 || lindex == m_mgIndexHi + 1) // Left index +/- 1
|
|
&& (m_mgConstRp || lindex == rindex + m_mgOffset) // Same right index offset
|
|
) {
|
|
// Sequentially next to last assign; continue merge
|
|
if (lindex == m_mgIndexLo - 1) {
|
|
m_mgIndexLo = lindex;
|
|
} else if (lindex == m_mgIndexHi + 1) {
|
|
m_mgIndexHi = lindex;
|
|
}
|
|
UINFO(9, "Continue merge i=" << lindex << " " << m_mgIndexHi << ":" << m_mgIndexLo
|
|
<< " " << nodep << endl);
|
|
m_mgAssignps.push_back(nodep);
|
|
m_mgNextp = nodep->nextp();
|
|
return;
|
|
} else {
|
|
UINFO(9, "End merge i="
|
|
<< lindex << " " << m_mgIndexHi << ":" << m_mgIndexLo << " " << nodep
|
|
<< endl); // This assign doesn't merge with previous assign,
|
|
// but should start a new merge
|
|
mergeEnd();
|
|
}
|
|
}
|
|
|
|
// Merge start
|
|
m_mgAssignps.push_back(nodep);
|
|
m_mgCfuncp = m_cfuncp;
|
|
m_mgNextp = nodep->nextp();
|
|
m_mgSelLp = lselp;
|
|
m_mgSelRp = rselp;
|
|
m_mgVarrefLp = lvarrefp;
|
|
m_mgVarrefRp = rvarrefp;
|
|
m_mgOffset = static_cast<int64_t>(lindex) - static_cast<int64_t>(rindex);
|
|
m_mgConstRp = rconstp;
|
|
m_mgIndexLo = lindex;
|
|
m_mgIndexHi = lindex;
|
|
UINFO(9, "Start merge i=" << lindex << " o=" << m_mgOffset << nodep << endl);
|
|
}
|
|
//--------------------
|
|
void visit(AstVar*) override {} // Accelerate
|
|
void visit(AstNodeExpr*) override {} // Accelerate
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
explicit ReloopVisitor(AstNetlist* nodep) { iterate(nodep); }
|
|
~ReloopVisitor() override {
|
|
V3Stats::addStat("Optimizations, Reloops", m_statReloops);
|
|
V3Stats::addStat("Optimizations, Reloop iterations", m_statReItems);
|
|
}
|
|
};
|
|
|
|
//######################################################################
|
|
// Reloop class functions
|
|
|
|
void V3Reloop::reloopAll(AstNetlist* nodep) {
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
|
{ ReloopVisitor{nodep}; } // Destruct before checking
|
|
V3Global::dumpCheckGlobalTree("reloop", 0, dumpTree() >= 6);
|
|
}
|