// $Id$ //************************************************************************* // DESCRIPTION: Verilator: Add temporaries, such as for unroll nodes // // Code available from: http://www.veripool.com/verilator // // AUTHORS: Wilson Snyder with Paul Wasson, Duane Gabli // //************************************************************************* // // Copyright 2003-2006 by Wilson Snyder. This program is free software; you can // redistribute it and/or modify it under the terms of either the GNU // General Public License or the Perl Artistic License. // // 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.h" #include #include #include #include #include "V3Global.h" #include "V3Unroll.h" #include "V3Stats.h" #include "V3Const.h" #include "V3Ast.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 bool m_varModeCheck; // Just checking RHS assignments bool m_varModeReplace; // Replacing varrefs bool m_varAssignHit; // Assign var hit bool m_inBegin; // Inside a begin/end loop bool m_generate; // Expand single generate For loop V3Double0 m_statLoops; // Statistic tracking V3Double0 m_statIters; // Statistic tracking //int debug() { return 9; } // VISITORS virtual void visit(AstModule* nodep, AstNUser*) { UINFO(4," MOD "<iterateChildren(*this); } bool cantUnroll(AstNodeFor* nodep, const char* reason) { if (m_generate) { nodep->v3error("Unsupported: Can't unroll generate for; "<initsp()) V3Const::constifyTree(nodep->initsp()); // May change what is under init, leave here V3Const::constifyTree(nodep->condp()); if (nodep->assignsp()) V3Const::constifyTree(nodep->assignsp()); AstAssign* initp = nodep->initsp()->castAssign(); if (!initp) nodep->v3fatalSrc("no initial assignment"); m_forVarp = initp->lhsp()->castVarRef()->varp(); m_forVscp = initp->lhsp()->castVarRef()->varScopep(); if (nodep->castGenFor() && !m_forVarp->isGenVar()) { nodep->v3error("Non-genvar used in generate for: "<name()<condp()->castNodeBiop(); AstAssign* assignp = nodep->assignsp()->castAssign(); AstNodeBiop* incInstrp = assignp->rhsp()->castNodeBiop(); if (!assignp) nodep->v3fatalSrc("no increment assignment"); UINFO(4, " FOR Check "<=9) nodep->dumpTree(cout," for: "); // // Extract the constant loop bounds // To keep the IF levels low, we return as each test fails. if (m_generate) V3Const::constifyParam(initp->rhsp()); AstConst* constInitp = initp->rhsp()->castConst(); if (!constInitp) return cantUnroll(nodep, "non-constant initializer"); bool subtract = incInstrp->castSub(); { if (!subtract && !incInstrp->castAdd()) return cantUnroll(nodep, "missing add/sub for incrementer"); AstVarRef* incVarrp = (subtract ? incInstrp->lhsp()->castVarRef() : incInstrp->rhsp()->castVarRef()); if (!incVarrp) return cantUnroll(nodep, "missing variable in incrementer"); if (incVarrp->varp() != m_forVarp || incVarrp->varScopep() != m_forVscp) { return cantUnroll(nodep, "different variables in incrementer"); } } // // Adds have the # on the lhsp because V3Const pushes rhs consts over to the lhs // Subtracts have it on the rhs, because you write i=i-1; i=1-i is non-sensible. AstConst* preconstIncp = (subtract ? incInstrp->rhsp()->castConst() : incInstrp->lhsp()->castConst()); if (m_generate) V3Const::constifyParam(preconstIncp); AstConst* constIncp = (subtract ? incInstrp->rhsp()->castConst() : incInstrp->lhsp()->castConst()); UINFO(8, " Inc expr ok: "<isZero()) return cantUnroll(nodep, "zero increment"); // Or we could loop forever below... bool lt = condp->castLt() || condp->castLtS(); bool lte = condp->castLte() || condp->castLteS(); bool gt = condp->castGt() || condp->castGtS(); bool gte = condp->castGte() || condp->castGteS(); if (!lt && !lte && !gt && !gte) return cantUnroll(nodep, "condition not <= or <"); if (!condp->lhsp()->castVarRef()) return cantUnroll(nodep, "no variable on lhs of condition"); if (condp->lhsp()->castVarRef()->varp() != m_forVarp || condp->lhsp()->castVarRef()->varScopep() != m_forVscp) return cantUnroll(nodep, "different variable in condition"); if (m_generate) V3Const::constifyParam(condp->rhsp()); AstConst* constStopp = condp->rhsp()->castConst(); if (!constStopp) return cantUnroll(nodep, "non-constant final value"); UINFO(8, " Stop expr ok: "<width()>32 || constInitp->num().isFourState() || constStopp->width()>32 || constStopp->num().isFourState() || constIncp->width()>32 || constIncp->num().isFourState()) return cantUnroll(nodep, "init/final/increment too large or four state"); vlsint32_t valInit = constInitp->num().asInt(); // Extract as unsigned, then make signed vlsint32_t valStop = constStopp->num().asInt(); // Extract as unsigned, then make signed if (lte) valStop++; if (gte) valStop--; vlsint32_t valInc = constIncp->num().asSInt(); if (subtract) valInc = -valInc; UINFO(8," In Numbers: for (v="<bodysp(); bodp; bodp=bodp->nextp()) { bodySize++; } if (bodySize > v3Global.opt.unrollStmts()) return cantUnroll(nodep, "too many statements"); } // // Now, make sure there's no assignment to this variable in the loop m_varModeCheck = true; m_varAssignHit = false; nodep->bodysp()->iterateAndNext(*this); m_varModeCheck = false; if (m_varAssignHit) return cantUnroll(nodep, "genvar assigned *inside* loop"); // // Finally, we can do it forUnroller(nodep, constInitp->num(), condp, constStopp->num(), incInstrp, constIncp->num()); nodep = NULL; return true; } void forUnroller(AstNodeFor* nodep, const V3Number& numInit, AstNodeBiop* cmpInstrp, const V3Number& numStop, AstNodeBiop* incInstrp, const V3Number& numInc) { UINFO(4, " Unroll for var="<fileline(), 1); cmpInstrp->numberOperate(contin, loopValue, numStop); if (contin.isEqZero()) { break; // Done with the loop } else { AstNode* oneloopp = bodysp->cloneTree(true); m_varValuep = new AstConst(nodep->fileline(), loopValue); m_varModeReplace = true; oneloopp->iterateAndNext(*this); m_varModeReplace = false; if (newbodysp) newbodysp->addNext(oneloopp); else newbodysp = oneloopp; m_statIters++; if (++times > v3Global.opt.unrollCount()*3) { nodep->v3error("Loop unrolling took too long; probably this is an infinite loop."); break; } //loopValue += valInc V3Number newnum(nodep->fileline(), m_forVarp->width()); // Can't increment in-place incInstrp->numberOperate(newnum, loopValue, numInc); loopValue.opAssign(newnum); } } } // And, leave the iterator at the right final value. if (!nodep->castGenFor()) { AstVarRef* newrefp = (m_forVscp ? new AstVarRef(nodep->fileline(), m_forVscp, true) : new AstVarRef(nodep->fileline(), m_forVarp, true)); AstAssign* finalAssignp = new AstAssign (nodep->fileline(), newrefp, new AstConst(nodep->fileline(), loopValue)); if (newbodysp) newbodysp->addNext(finalAssignp); else newbodysp = finalAssignp; } // Replace the FOR() if (newbodysp) nodep->replaceWith(newbodysp); else nodep->unlinkFrBack(); if (debug()>=9) newbodysp->dumpTree(cout," _new: "); } virtual void visit(AstNodeFor* nodep, AstNUser*) { if (!m_generate || m_varModeReplace) { nodep->iterateChildren(*this); } if (m_varModeCheck || m_varModeReplace) { } else { if (forUnrollCheck(nodep)) { nodep=NULL; // Did replacement } else if (m_generate || nodep->castGenFor()) { nodep->v3error("For loop doesn't have genvar index, or is misformed"); } else { // So later optimizations don't need to deal with them, // convert leftover FOR's: // FOR(init,cond,assign,body) -> init,WHILE(cond) { body, assign } AstNode* initsp = nodep->initsp(); if (initsp) initsp->unlinkFrBackWithNext(); AstNode* condp = nodep->condp(); if (condp) condp->unlinkFrBackWithNext(); AstNode* assignsp = nodep->assignsp(); if (assignsp) assignsp->unlinkFrBackWithNext(); AstNode* bodysp = nodep->bodysp(); if (bodysp) bodysp->unlinkFrBackWithNext(); bodysp = bodysp->addNext(assignsp); AstNode* newp = new AstWhile(nodep->fileline(), condp, bodysp); initsp = initsp->addNext(newp); newp = initsp; nodep->replaceWith(newp); nodep->deleteTree(); nodep=NULL; } } } virtual void visit(AstBegin* nodep, AstNUser*) { // Naming inside loop body; must have been a generate for. // We need to only rename the 'upper' begin, // anything lower will be renamed "uppernewname.lowerbegin" bool lastBegin = m_inBegin; m_inBegin = true; nodep->iterateChildren(*this); m_inBegin = lastBegin; if (m_varModeReplace && !m_inBegin // above begin, not this one ) { // Rename it, as otherwise we may get a conflict // V3Begin sees these DOTs and makes CellInlines for us. string nname = (string)"genfor"+cvtToStr(m_varValuep->asInt())+"__DOT__"+nodep->name(); //UINFO(8," Rename begin "<name(nname); } } virtual void visit(AstVarRef* nodep, AstNUser*) { if (m_varModeCheck && nodep->varp() == m_forVarp && nodep->varScopep() == m_forVscp && nodep->lvalue()) { UINFO(8," Itervar assigned to: "<varp() == m_forVarp && nodep->varScopep() == m_forVscp) { AstNode* newconstp = m_varValuep->cloneTree(false); nodep->replaceWith(newconstp); } } //-------------------- // Default: Just iterate virtual void visit(AstNode* nodep, AstNUser*) { nodep->iterateChildren(*this); } public: // CONSTUCTORS UnrollVisitor(AstNode* nodep, bool generate) { m_forVarp = NULL; m_forVscp = NULL; m_varModeCheck = false; m_varModeReplace = false; m_inBegin = false; m_generate = generate; // nodep->accept(*this); } virtual ~UnrollVisitor() { V3Stats::addStat("Optimizations, Unrolled Loops", m_statLoops); V3Stats::addStat("Optimizations, Unrolled Iterations", m_statIters); } }; //###################################################################### // Unroll class functions void V3Unroll::unrollAll(AstNetlist* nodep) { UINFO(2,__FUNCTION__<<": "<