forked from github/verilator
493 lines
17 KiB
C++
493 lines
17 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
||
//*************************************************************************
|
||
// DESCRIPTION: Verilator: Add temporaries, such as for unroll nodes
|
||
//
|
||
// Code available from: http://www.veripool.org/verilator
|
||
//
|
||
//*************************************************************************
|
||
//
|
||
// Copyright 2003-2018 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.
|
||
//
|
||
// Verilator is distributed in the hope that it will be useful,
|
||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
// GNU General Public License for more details.
|
||
//
|
||
//*************************************************************************
|
||
// V3Unroll's Transformations:
|
||
// Note is called twice. Once on modules for GenFor unrolling,
|
||
// Again after V3Scope for normal for loop unrolling.
|
||
//
|
||
// Each module:
|
||
// Look for "FOR" loops and unroll them if <= 32 loops.
|
||
// (Eventually, a better way would be to simulate the entire loop; ala V3Table.)
|
||
// Convert remaining FORs to WHILEs
|
||
//
|
||
//*************************************************************************
|
||
|
||
#include "config_build.h"
|
||
#include "verilatedos.h"
|
||
#include <cstdio>
|
||
#include <cstdarg>
|
||
#include <unistd.h>
|
||
#include <algorithm>
|
||
|
||
#include "V3Global.h"
|
||
#include "V3Unroll.h"
|
||
#include "V3Stats.h"
|
||
#include "V3Const.h"
|
||
#include "V3Ast.h"
|
||
#include "V3Simulate.h"
|
||
|
||
//######################################################################
|
||
// Unroll state, as a visitor of each AstNode
|
||
|
||
class UnrollVisitor : public AstNVisitor {
|
||
private:
|
||
// STATE
|
||
AstVar* m_forVarp; // Iterator variable
|
||
AstVarScope* m_forVscp; // Iterator variable scope (NULL for generate pass)
|
||
AstConst* m_varValuep; // Current value of loop
|
||
AstNode* m_ignoreIncp; // Increment node to ignore
|
||
bool m_varModeCheck; // Just checking RHS assignments
|
||
bool m_varModeReplace; // Replacing varrefs
|
||
bool m_varAssignHit; // Assign var hit
|
||
bool m_generate; // Expand single generate For loop
|
||
string m_beginName; // What name to give begin iterations
|
||
V3Double0 m_statLoops; // Statistic tracking
|
||
V3Double0 m_statIters; // Statistic tracking
|
||
|
||
// METHODS
|
||
static int debug() {
|
||
static int level = -1;
|
||
if (VL_UNLIKELY(level < 0)) level = v3Global.opt.debugSrcLevel(__FILE__);
|
||
return level;
|
||
}
|
||
|
||
// VISITORS
|
||
bool cantUnroll(AstNode* nodep, const char* reason) {
|
||
if (m_generate) {
|
||
nodep->v3error("Unsupported: Can't unroll generate for; "<<reason);
|
||
}
|
||
UINFO(3," Can't Unroll: "<<reason<<" :"<<nodep<<endl);
|
||
//if (debug()>=9) nodep->dumpTree(cout,"-cant-");
|
||
V3Stats::addStatSum(string("Unrolling gave up, ")+reason, 1);
|
||
return false;
|
||
}
|
||
|
||
int unrollCount() {
|
||
return m_generate ? v3Global.opt.unrollCount()*16
|
||
: v3Global.opt.unrollCount();
|
||
}
|
||
|
||
bool bodySizeOverRecurse(AstNode* nodep, int& bodySize, int bodyLimit) {
|
||
if (!nodep) return false;
|
||
bodySize++;
|
||
// Exit once exceeds limits, rather than always total
|
||
// so don't go O(n^2) when can't unroll
|
||
if (bodySize > bodyLimit) return true;
|
||
if (bodySizeOverRecurse(nodep->op1p(), bodySize, bodyLimit)) return true;
|
||
if (bodySizeOverRecurse(nodep->op2p(), bodySize, bodyLimit)) return true;
|
||
if (bodySizeOverRecurse(nodep->op3p(), bodySize, bodyLimit)) return true;
|
||
if (bodySizeOverRecurse(nodep->op4p(), bodySize, bodyLimit)) return true;
|
||
// Tail recurse.
|
||
return bodySizeOverRecurse(nodep->nextp(), bodySize, bodyLimit);
|
||
}
|
||
|
||
bool forUnrollCheck(AstNode* nodep,
|
||
AstNode* initp, // Maybe under nodep (no nextp), or standalone (ignore nextp)
|
||
AstNode* precondsp, AstNode* condp,
|
||
AstNode* incp, // Maybe under nodep or in bodysp
|
||
AstNode* bodysp) {
|
||
// To keep the IF levels low, we return as each test fails.
|
||
UINFO(4, " FOR Check "<<nodep<<endl);
|
||
if (initp) UINFO(6, " Init "<<initp<<endl);
|
||
if (precondsp) UINFO(6, " Pcon "<<precondsp<<endl);
|
||
if (condp) UINFO(6, " Cond "<<condp<<endl);
|
||
if (incp) UINFO(6, " Inc "<<incp<<endl);
|
||
|
||
// Initial value check
|
||
AstAssign* initAssp = initp->castAssign();
|
||
if (!initAssp) return cantUnroll(nodep, "no initial assignment");
|
||
if (initp->nextp() && initp->nextp()!=nodep) nodep->v3fatalSrc("initial assignment shouldn't be a list");
|
||
if (!initAssp->lhsp()->castVarRef()) return cantUnroll(nodep, "no initial assignment to simple variable");
|
||
//
|
||
// Condition check
|
||
if (condp->nextp()) nodep->v3fatalSrc("conditional shouldn't be a list");
|
||
//
|
||
// Assignment of next value check
|
||
AstAssign* incAssp = incp->castAssign();
|
||
if (!incAssp) return cantUnroll(nodep, "no increment assignment");
|
||
if (incAssp->nextp()) nodep->v3fatalSrc("increment shouldn't be a list");
|
||
|
||
m_forVarp = initAssp->lhsp()->castVarRef()->varp();
|
||
m_forVscp = initAssp->lhsp()->castVarRef()->varScopep();
|
||
if (nodep->castGenFor() && !m_forVarp->isGenVar()) {
|
||
nodep->v3error("Non-genvar used in generate for: "<<m_forVarp->prettyName()<<endl);
|
||
}
|
||
if (m_generate) V3Const::constifyParamsEdit(initAssp->rhsp()); // rhsp may change
|
||
|
||
// This check shouldn't be needed when using V3Simulate
|
||
// however, for repeat loops, the loop variable is auto-generated
|
||
// and the initp statements will reference a variable outside of the initp scope
|
||
// alas, failing to simulate.
|
||
AstConst* constInitp = initAssp->rhsp()->castConst();
|
||
if (!constInitp) return cantUnroll(nodep, "non-constant initializer");
|
||
|
||
//
|
||
// Now, make sure there's no assignment to this variable in the loop
|
||
m_varModeCheck = true;
|
||
m_varAssignHit = false;
|
||
m_ignoreIncp = incp;
|
||
precondsp->iterateAndNext(*this);
|
||
bodysp->iterateAndNext(*this);
|
||
incp->iterateAndNext(*this);
|
||
m_varModeCheck = false;
|
||
m_ignoreIncp = NULL;
|
||
if (m_varAssignHit) return cantUnroll(nodep, "genvar assigned *inside* loop");
|
||
|
||
//
|
||
if (m_forVscp) { UINFO(8, " Loop Variable: "<<m_forVscp<<endl); }
|
||
else { UINFO(8, " Loop Variable: "<<m_forVarp<<endl); }
|
||
if (debug()>=9) nodep->dumpTree(cout,"- for: ");
|
||
|
||
|
||
if (!m_generate) {
|
||
AstAssign *incpAssign = incp->castAssign();
|
||
if (!canSimulate(incpAssign->rhsp())) return cantUnroll(incp, "Unable to simulate increment");
|
||
if (!canSimulate(condp)) return cantUnroll(condp, "Unable to simulate condition");
|
||
|
||
// Check whether to we actually want to try and unroll.
|
||
int loops;
|
||
if (!countLoops(initAssp, condp, incp, unrollCount(), loops))
|
||
return cantUnroll(nodep, "Unable to simulate loop");
|
||
|
||
// Less than 10 statements in the body?
|
||
int bodySize = 0;
|
||
int bodyLimit = v3Global.opt.unrollStmts();
|
||
if (loops>0) bodyLimit = v3Global.opt.unrollStmts() / loops;
|
||
if (bodySizeOverRecurse(precondsp, bodySize/*ref*/, bodyLimit)
|
||
|| bodySizeOverRecurse(bodysp, bodySize/*ref*/, bodyLimit)
|
||
|| bodySizeOverRecurse(incp, bodySize/*ref*/, bodyLimit)) {
|
||
return cantUnroll(nodep, "too many statements");
|
||
}
|
||
}
|
||
// Finally, we can do it
|
||
if (!forUnroller(nodep, initAssp, condp, precondsp, incp, bodysp)) {
|
||
return cantUnroll(nodep, "Unable to unroll loop");
|
||
}
|
||
VL_DANGLING(nodep);
|
||
// Cleanup
|
||
return true;
|
||
}
|
||
|
||
bool canSimulate(AstNode *nodep) {
|
||
SimulateVisitor simvis;
|
||
AstNode* clonep = nodep->cloneTree(true);
|
||
simvis.mainCheckTree(clonep);
|
||
pushDeletep(clonep); clonep = NULL;
|
||
return simvis.optimizable();
|
||
}
|
||
|
||
bool simulateTree(AstNode *nodep, const V3Number *loopValue, AstNode *dtypep, V3Number &outNum) {
|
||
AstNode* clone = nodep->cloneTree(true);
|
||
if (!clone) {
|
||
nodep->v3fatalSrc("Failed to clone tree");
|
||
return false;
|
||
}
|
||
if (loopValue) {
|
||
m_varValuep = new AstConst (nodep->fileline(), *loopValue);
|
||
// Iteration requires a back, so put under temporary node
|
||
AstBegin* tempp = new AstBegin (nodep->fileline(), "[EditWrapper]", clone);
|
||
m_varModeReplace = true;
|
||
tempp->stmtsp()->iterateAndNext(*this);
|
||
m_varModeReplace = false;
|
||
clone = tempp->stmtsp()->unlinkFrBackWithNext();
|
||
tempp->deleteTree();
|
||
tempp = NULL;
|
||
pushDeletep(m_varValuep); m_varValuep = NULL;
|
||
}
|
||
SimulateVisitor simvis;
|
||
simvis.mainParamEmulate(clone);
|
||
if (!simvis.optimizable()) {
|
||
UINFO(3, "Unable to simulate" << endl);
|
||
if (debug()>=9) nodep->dumpTree(cout,"- _simtree: ");
|
||
return false;
|
||
}
|
||
// Fetch the result
|
||
V3Number* res = simvis.fetchNumberNull(clone);
|
||
if (!res) {
|
||
UINFO(3, "No number returned from simulation" << endl);
|
||
return false;
|
||
}
|
||
// Patch up datatype
|
||
if (dtypep) {
|
||
AstConst new_con (clone->fileline(), *res);
|
||
new_con.dtypeFrom(dtypep);
|
||
outNum = new_con.num();
|
||
return true;
|
||
}
|
||
outNum = *res;
|
||
return true;
|
||
}
|
||
|
||
bool countLoops(AstAssign *initp, AstNode *condp, AstNode *incp, int max, int &outLoopsr) {
|
||
outLoopsr = 0;
|
||
V3Number loopValue = V3Number(initp->fileline());
|
||
if (!simulateTree(initp->rhsp(), NULL, initp, loopValue)) {
|
||
return false;
|
||
}
|
||
while (1) {
|
||
V3Number res = V3Number(initp->fileline());
|
||
if (!simulateTree(condp, &loopValue, NULL, res)) {
|
||
return false;
|
||
}
|
||
if (!res.isEqOne()) {
|
||
break;
|
||
}
|
||
|
||
outLoopsr++;
|
||
|
||
// Run inc
|
||
AstAssign* incpass = incp->castAssign();
|
||
V3Number newLoopValue = V3Number(initp->fileline());
|
||
if (!simulateTree(incpass->rhsp(), &loopValue, incpass, newLoopValue)) {
|
||
return false;
|
||
}
|
||
loopValue.opAssign(newLoopValue);
|
||
if (outLoopsr > max) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool forUnroller(AstNode* nodep,
|
||
AstAssign* initp,
|
||
AstNode* condp,
|
||
AstNode* precondsp,
|
||
AstNode* incp, AstNode* bodysp) {
|
||
UINFO(9, "forUnroller "<<nodep<<endl);
|
||
V3Number loopValue = V3Number(nodep->fileline());
|
||
if (!simulateTree(initp->rhsp(), NULL, initp, loopValue)) {
|
||
return false;
|
||
}
|
||
AstNode* stmtsp = NULL;
|
||
if (initp) {
|
||
initp->unlinkFrBack(); // Always a single statement; nextp() may be nodep
|
||
// Don't add to list, we do it once, and setting loop index isn't needed as we're constant propagating it
|
||
}
|
||
if (precondsp) {
|
||
precondsp->unlinkFrBackWithNext();
|
||
stmtsp = AstNode::addNextNull(stmtsp, precondsp);
|
||
}
|
||
if (bodysp) {
|
||
bodysp->unlinkFrBackWithNext();
|
||
stmtsp = AstNode::addNextNull(stmtsp, bodysp); // Maybe null if no body
|
||
}
|
||
if (incp && !nodep->castGenFor()) { // Generates don't need to increment loop index
|
||
incp->unlinkFrBackWithNext();
|
||
stmtsp = AstNode::addNextNull(stmtsp, incp); // Maybe null if no body
|
||
}
|
||
// Mark variable to disable some later warnings
|
||
m_forVarp->usedLoopIdx(true);
|
||
|
||
AstNode* newbodysp = NULL;
|
||
++m_statLoops;
|
||
if (stmtsp) {
|
||
int times = 0;
|
||
while (1) {
|
||
UINFO(8," Looping "<<loopValue<<endl);
|
||
V3Number res = V3Number(nodep->fileline());
|
||
if (!simulateTree(condp, &loopValue, NULL, res)) {
|
||
nodep->v3error("Loop unrolling failed.");
|
||
return false;
|
||
}
|
||
if (!res.isEqOne()) {
|
||
break; // Done with the loop
|
||
}
|
||
else {
|
||
// Replace iterator values with constant.
|
||
AstNode* oneloopp = stmtsp->cloneTree(true);
|
||
|
||
m_varValuep = new AstConst(nodep->fileline(), loopValue);
|
||
|
||
// Iteration requires a back, so put under temporary node
|
||
if (oneloopp) {
|
||
AstBegin* tempp = new AstBegin(oneloopp->fileline(),"[EditWrapper]",oneloopp);
|
||
m_varModeReplace = true;
|
||
tempp->stmtsp()->iterateAndNext(*this);
|
||
m_varModeReplace = false;
|
||
oneloopp = tempp->stmtsp()->unlinkFrBackWithNext(); tempp->deleteTree(); VL_DANGLING(tempp);
|
||
}
|
||
if (m_generate) {
|
||
string index = AstNode::encodeNumber(m_varValuep->toSInt());
|
||
string nname = m_beginName + "__BRA__" + index + "__KET__";
|
||
oneloopp = new AstBegin(oneloopp->fileline(),nname,oneloopp,true);
|
||
}
|
||
pushDeletep(m_varValuep); m_varValuep=NULL;
|
||
if (newbodysp) newbodysp->addNext(oneloopp);
|
||
else newbodysp = oneloopp;
|
||
|
||
++m_statIters;
|
||
if (++times > unrollCount()*3) {
|
||
nodep->v3error("Loop unrolling took too long;"
|
||
" probably this is an infinite loop, or set --unroll-count above "
|
||
<<unrollCount());
|
||
break;
|
||
}
|
||
|
||
// loopValue += valInc
|
||
AstAssign *incpass = incp->castAssign();
|
||
V3Number newLoopValue = V3Number(nodep->fileline());
|
||
if (!simulateTree(incpass->rhsp(), &loopValue, incpass, newLoopValue)) {
|
||
nodep->v3error("Loop unrolling failed");
|
||
return false;
|
||
}
|
||
loopValue.opAssign(newLoopValue);
|
||
}
|
||
}
|
||
}
|
||
// Replace the FOR()
|
||
if (newbodysp) nodep->replaceWith(newbodysp);
|
||
else nodep->unlinkFrBack();
|
||
if (bodysp) { pushDeletep(bodysp); VL_DANGLING(bodysp); }
|
||
if (precondsp) { pushDeletep(precondsp); VL_DANGLING(precondsp); }
|
||
if (initp) { pushDeletep(initp); VL_DANGLING(initp); }
|
||
if (incp && !incp->backp()) { pushDeletep(incp); VL_DANGLING(incp); }
|
||
if (debug()>=9 && newbodysp) newbodysp->dumpTree(cout,"- _new: ");
|
||
return true;
|
||
}
|
||
|
||
virtual void visit(AstWhile* nodep) {
|
||
nodep->iterateChildren(*this);
|
||
if (m_varModeCheck || m_varModeReplace) {
|
||
} else {
|
||
// Constify before unroll call, as it may change what is underneath.
|
||
if (nodep->precondsp()) V3Const::constifyEdit(nodep->precondsp()); // precondsp may change
|
||
if (nodep->condp()) V3Const::constifyEdit(nodep->condp()); // condp may change
|
||
// Grab initial value
|
||
AstNode* initp = NULL; // Should be statement before the while.
|
||
if (nodep->backp()->nextp() == nodep) initp=nodep->backp();
|
||
if (initp) { V3Const::constifyEdit(initp); VL_DANGLING(initp); }
|
||
if (nodep->backp()->nextp() == nodep) initp=nodep->backp();
|
||
// Grab assignment
|
||
AstNode* incp = NULL; // Should be last statement
|
||
if (nodep->incsp()) V3Const::constifyEdit(nodep->incsp());
|
||
if (nodep->incsp()) incp = nodep->incsp();
|
||
else {
|
||
for (incp = nodep->bodysp(); incp && incp->nextp(); incp = incp->nextp()) {}
|
||
if (incp) { V3Const::constifyEdit(incp); VL_DANGLING(incp); }
|
||
for (incp = nodep->bodysp(); incp && incp->nextp(); incp = incp->nextp()) {} // Again, as may have changed
|
||
}
|
||
// And check it
|
||
if (forUnrollCheck(nodep, initp,
|
||
nodep->precondsp(), nodep->condp(),
|
||
incp, nodep->bodysp())) {
|
||
pushDeletep(nodep); VL_DANGLING(nodep); // Did replacement
|
||
}
|
||
}
|
||
}
|
||
virtual void visit(AstGenFor* nodep) {
|
||
if (!m_generate || m_varModeReplace) {
|
||
nodep->iterateChildren(*this);
|
||
} // else V3Param will recursively call each for loop to be unrolled for us
|
||
if (m_varModeCheck || m_varModeReplace) {
|
||
} else {
|
||
// Constify before unroll call, as it may change what is underneath.
|
||
if (nodep->initsp()) V3Const::constifyEdit(nodep->initsp()); // initsp may change
|
||
if (nodep->condp()) V3Const::constifyEdit(nodep->condp()); // condp may change
|
||
if (nodep->incsp()) V3Const::constifyEdit(nodep->incsp()); // incsp may change
|
||
if (nodep->condp()->isZero()) {
|
||
// We don't need to do any loops. Remove the GenFor,
|
||
// Genvar's don't care about any initial assignments.
|
||
//
|
||
// Note normal For's can't do exactly this deletion, as
|
||
// we'd need to initialize the variable to the initial
|
||
// condition, but they'll become while's which can be
|
||
// deleted by V3Const.
|
||
nodep->unlinkFrBack()->deleteTree(); VL_DANGLING(nodep);
|
||
} else if (forUnrollCheck(nodep, nodep->initsp(),
|
||
NULL, nodep->condp(),
|
||
nodep->incsp(), nodep->bodysp())) {
|
||
pushDeletep(nodep); VL_DANGLING(nodep); // Did replacement
|
||
} else {
|
||
nodep->v3error("For loop doesn't have genvar index, or is malformed");
|
||
}
|
||
}
|
||
}
|
||
virtual void visit(AstNodeFor* nodep) {
|
||
if (m_generate) { // Ignore for's when expanding genfor's
|
||
nodep->iterateChildren(*this);
|
||
} else {
|
||
nodep->v3error("V3Begin should have removed standard FORs");
|
||
}
|
||
}
|
||
|
||
virtual void visit(AstVarRef* nodep) {
|
||
if (m_varModeCheck
|
||
&& nodep->varp() == m_forVarp
|
||
&& nodep->varScopep() == m_forVscp
|
||
&& nodep->lvalue()) {
|
||
UINFO(8," Itervar assigned to: "<<nodep<<endl);
|
||
m_varAssignHit = true;
|
||
}
|
||
|
||
if (m_varModeReplace
|
||
&& nodep->varp() == m_forVarp
|
||
&& nodep->varScopep() == m_forVscp
|
||
&& !nodep->lvalue()) {
|
||
AstNode* newconstp = m_varValuep->cloneTree(false);
|
||
nodep->replaceWith(newconstp);
|
||
pushDeletep(nodep);
|
||
}
|
||
}
|
||
|
||
//--------------------
|
||
// Default: Just iterate
|
||
virtual void visit(AstNode* nodep) {
|
||
if (m_varModeCheck && nodep == m_ignoreIncp) {
|
||
// Ignore subtree that is the increment
|
||
} else {
|
||
nodep->iterateChildren(*this);
|
||
}
|
||
}
|
||
|
||
public:
|
||
// CONSTUCTORS
|
||
UnrollVisitor(AstNode* nodep, bool generate, const string& beginName) {
|
||
m_forVarp = NULL;
|
||
m_forVscp = NULL;
|
||
m_ignoreIncp = NULL;
|
||
m_varModeCheck = false;
|
||
m_varModeReplace = false;
|
||
m_generate = generate;
|
||
m_beginName = beginName;
|
||
//
|
||
nodep->accept(*this);
|
||
}
|
||
virtual ~UnrollVisitor() {
|
||
V3Stats::addStatSum("Optimizations, Unrolled Loops", m_statLoops);
|
||
V3Stats::addStatSum("Optimizations, Unrolled Iterations", m_statIters);
|
||
}
|
||
};
|
||
|
||
//######################################################################
|
||
// Unroll class functions
|
||
|
||
void V3Unroll::unrollAll(AstNetlist* nodep) {
|
||
UINFO(2,__FUNCTION__<<": "<<endl);
|
||
{
|
||
UnrollVisitor visitor (nodep, false, "");
|
||
} // Destruct before checking
|
||
V3Global::dumpCheckGlobalTree("unroll", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
|
||
}
|
||
|
||
void V3Unroll::unrollGen(AstNodeFor* nodep, const string& beginName) {
|
||
UINFO(2,__FUNCTION__<<": "<<endl);
|
||
UnrollVisitor visitor (nodep, true, beginName);
|
||
}
|