Improve Loop unrolling (#6480) (#6493)

This patch implements #6480. All loop statements are represented using
AstLoop and AstLoopTest.

This necessitates rework of the loop unroller to handle loops of
arbitrary form. To enable this, I have split the old unroller used for
'generate for' statements and moved it into V3Param, and subsequently
rewrote V3Unroll to handle the new representation. V3Unroll can now
unroll more complex loops, including with loop conditions containing
multiple variable references or inlined functions.

Handling the more generic code also requires some restrictions. If a
loop contains any of the following, it cannot be unrolled:
- A timing control that might suspend the loop
- A non-inlined call to a non-pure function

These constructs can change the values of variables in the loop, so are
generally not safe to unroll if they are present. (We could still unroll
if all the variables needed for unrolling are automatic, however we
don't do that right now.)

These restrictions seem ok in the benchmark suite, where the new
unroller can generally unroll many more loops than before.
This commit is contained in:
Geza Lore 2025-09-29 16:25:25 +02:00 committed by GitHub
parent 5c72b45975
commit 603f4c615a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 3151 additions and 2891 deletions

View File

@ -349,6 +349,7 @@ set(COMMON_SOURCES
V3Undriven.cpp
V3Unknown.cpp
V3Unroll.cpp
V3UnrollGen.cpp
V3VariableOrder.cpp
V3Waiver.cpp
V3Width.cpp

View File

@ -324,6 +324,7 @@ RAW_OBJS_PCH_ASTNOMT = \
V3Undriven.o \
V3Unknown.o \
V3Unroll.o \
V3UnrollGen.o \
V3Width.o \
V3WidthCommit.o \
V3WidthSel.o \

View File

@ -349,16 +349,21 @@ private:
AstVar* const cntVarp = new AstVar{flp, VVarType::BLOCKTEMP, delayName + "__counter",
nodep->findBasicDType(VBasicDTypeKwd::UINT32)};
cntVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT);
cntVarp->funcLocal(true);
AstBegin* const beginp = new AstBegin{flp, delayName + "__block", cntVarp, true};
beginp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, valuep});
beginp->addStmtsp(new AstWhile{
nodep->fileline(),
new AstGt{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 0}},
controlp,
new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE},
new AstSub{flp, new AstVarRef{flp, cntVarp, VAccess::READ},
new AstConst{flp, 1}}}});
{
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(new AstLoopTest{
flp, loopp,
new AstGt{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 0}}});
loopp->addStmtsp(controlp);
loopp->addStmtsp(
new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE},
new AstSub{flp, new AstVarRef{flp, cntVarp, VAccess::READ},
new AstConst{flp, 1}}});
beginp->addStmtsp(loopp);
}
nodep->replaceWith(beginp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}

View File

@ -520,20 +520,6 @@ public:
// * = Add a newline for $display
bool addNewline() const { return displayType().addNewline(); }
};
class AstDoWhile final : public AstNodeStmt {
// @astgen op1 := condp : AstNodeExpr
// @astgen op2 := stmtsp : List[AstNode]
public:
AstDoWhile(FileLine* fl, AstNodeExpr* conditionp, AstNode* stmtsp = nullptr)
: ASTGEN_SUPER_DoWhile(fl) {
condp(conditionp);
addStmtsp(stmtsp);
}
ASTGEN_MEMBERS_AstDoWhile;
bool isGateOptimizable() const override { return false; }
int instrCount() const override { return INSTR_COUNT_BRANCH; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
};
class AstDumpCtl final : public AstNodeStmt {
// $dumpon etc
// Parents: expr
@ -677,6 +663,58 @@ public:
}
AstJumpBlock* blockp() const { return m_blockp; }
};
class AstLoop final : public AstNodeStmt {
// An inifinite loop, used to model all source level procedural loops.
// Executes as:
// while (true) {
// stmtsp;
// // <- 'continue' inside 'stmtsp goes here
// contsp;
// }
// 'contsp' is moved into 'stmtsp' in LinkJump when 'continue' statements are resovled.
// @astgen op1 := stmtsp : List[AstNode]
// @astgen op2 := contsp : List[AstNode] // Empty after LinkJump
VOptionBool m_unroll; // Full, none, or default unrolling
public:
AstLoop(FileLine* fl)
: ASTGEN_SUPER_Loop(fl) {}
ASTGEN_MEMBERS_AstLoop;
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
bool sameNode(const AstNode* thatp) const override {
return m_unroll == VN_DBG_AS(thatp, Loop)->m_unroll;
}
bool isGateOptimizable() const override { return false; }
int instrCount() const override { return INSTR_COUNT_BRANCH; }
bool maybePointedTo() const override VL_MT_SAFE { return true; }
// ACCESSORS
VOptionBool unroll() const { return m_unroll; }
void unroll(const VOptionBool flag) { m_unroll = flag; }
};
class AstLoopTest final : public AstNodeStmt {
// The condition test inside an AstLoop. If the condition is true,
// execution continues after this AstLoopTest statement. If the condition
// is false, control is transfered to after the corresponding AstLoop.
// In other words: AstLoopTest is like a conditional 'break' statement,
// which breaks out of the loop if the condition is false.
// @astgen op1 := condp : AstNodeExpr // The loop condition
// @astgen ptr := m_loopp : AstLoop // The corresponding AstLoop
public:
AstLoopTest(FileLine* fl, AstLoop* loopp, AstNodeExpr* condp)
: ASTGEN_SUPER_LoopTest(fl)
, m_loopp{loopp} {
this->condp(condp);
}
ASTGEN_MEMBERS_AstLoopTest;
const char* broken() const override;
void dump(std::ostream& str) const override;
bool sameNode(const AstNode*) const override { return true; }
bool isGateOptimizable() const override { return false; }
bool isBrancher() const override { return true; }
int instrCount() const override { return 0; }
// ACCESSORS
AstLoop* loopp() const { return m_loopp; }
};
class AstMonitorOff final : public AstNodeStmt {
const bool m_off; // Monitor off. Using 0=on allows faster init and comparison
@ -1044,28 +1082,6 @@ public:
ASTGEN_MEMBERS_AstWaitFork;
bool isTimingControl() const override { return true; }
};
class AstWhile final : public AstNodeStmt {
// @astgen op1 := condp : AstNodeExpr
// @astgen op2 := stmtsp : List[AstNode]
// @astgen op3 := incsp : List[AstNode]
VOptionBool m_unrollFull; // Full, disable, or default unrolling
public:
AstWhile(FileLine* fl, AstNodeExpr* condp, AstNode* stmtsp = nullptr, AstNode* incsp = nullptr)
: ASTGEN_SUPER_While(fl) {
this->condp(condp);
addStmtsp(stmtsp);
addIncsp(incsp);
}
ASTGEN_MEMBERS_AstWhile;
void dump(std::ostream& str) const override;
bool isGateOptimizable() const override { return false; }
int instrCount() const override { return INSTR_COUNT_BRANCH; }
bool sameNode(const AstNode* /*samep*/) const override { return true; }
// Stop statement searchback here
void addNextStmt(AstNode* newp, AstNode* belowp) override;
VOptionBool unrollFull() const { return m_unrollFull; }
void unrollFull(const VOptionBool flag) { m_unrollFull = flag; }
};
// === AstNodeAssign ===
class AstAssign final : public AstNodeAssign {

View File

@ -1551,24 +1551,6 @@ void AstNodeStmt::addNextStmt(AstNode* newp, AstNode*) {
this->addNextHere(newp);
}
void AstWhile::addNextStmt(AstNode* newp, AstNode* belowp) {
// Special, as statements need to be put in different places
// Belowp is how we came to recurse up to this point
if (belowp == condp()) {
// Becomes first statement in body, body may have been empty
if (stmtsp()) {
stmtsp()->addHereThisAsNext(newp);
} else {
addStmtsp(newp);
}
} else if (belowp == stmtsp()) {
// Next statement in body
belowp->addNextHere(newp);
} else {
belowp->v3fatalSrc("Doesn't look like this was really under the while");
}
}
//======================================================================
// Per-type Debugging
@ -2065,6 +2047,30 @@ const char* AstJumpGo::broken() const {
return nullptr;
}
void AstLoop::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (unroll().isSetTrue())
str << " [unrollfull]";
else if (unroll().isSetFalse())
str << " [unrollnone]";
}
void AstLoop::dumpJson(std::ostream& str) const {
dumpJsonStr(str, "unroll",
unroll().isSetTrue() ? "full"
: unroll().isSetFalse() ? "none"
: "default");
dumpJsonGen(str);
}
void AstLoopTest::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " -> ";
loopp()->dump(str);
}
const char* AstLoopTest::broken() const {
BROKEN_RTN(!loopp()->brokeExistsAbove());
return nullptr;
}
void AstMemberDType::dump(std::ostream& str) const {
this->AstNodeDType::dump(str);
if (isConstrainedRand()) str << " [CONSTRAINEDRAND]";
@ -2732,7 +2738,7 @@ void AstVar::dump(std::ostream& str) const {
if (isSigUserRWPublic()) str << " [PWR]";
if (isInternal()) str << " [INTERNAL]";
if (isLatched()) str << " [LATCHED]";
if (isUsedLoopIdx()) str << " [LOOP]";
if (isUsedLoopIdx()) str << " [LOOPIDX]";
if (rand().isRandomizable()) str << rand();
if (noReset()) str << " [!RST]";
if (attrIsolateAssign()) str << " [aISO]";
@ -2783,13 +2789,6 @@ bool AstVar::sameNode(const AstNode* samep) const {
const AstVar* const asamep = VN_DBG_AS(samep, Var);
return name() == asamep->name() && varType() == asamep->varType();
}
void AstWhile::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (unrollFull().isSetTrue())
str << " [unrollfull]";
else if (unrollFull().isSetFalse())
str << " [unrolldis]";
}
void AstScope::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [abovep=" << nodeAddr(aboveScopep()) << "]";

View File

@ -415,11 +415,13 @@ static AstNode* createForeachLoop(AstNodeForeach* nodep, AstNode* bodysp, AstVar
else
incp = new AstSub{fl, varRefp->cloneTree(false), new AstConst{fl, 1}};
AstWhile* const whilep = new AstWhile{
fl, condp, bodysp, new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, incp}};
AstLoop* const loopp = new AstLoop{fl};
loopp->addStmtsp(new AstLoopTest{fl, loopp, condp});
loopp->addStmtsp(bodysp);
loopp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, incp});
AstNode* const stmtsp = varp; // New statements for outer loop
stmtsp->addNext(new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, leftp});
stmtsp->addNext(whilep);
stmtsp->addNext(loopp);
return stmtsp;
}
static AstNode* createForeachLoopRanged(AstNodeForeach* nodep, AstNode* bodysp, AstVar* varp,
@ -519,12 +521,14 @@ AstNode* V3Begin::convertToWhile(AstForeach* nodep) {
AstLogOr* const orp
= new AstLogOr{fl, new AstVarRef{fl, first_varp, VAccess::READ},
new AstNeq{fl, new AstConst{fl, 0}, nextp}};
AstNode* const whilep = new AstWhile{fl, orp, first_clearp};
AstLoop* const lp = new AstLoop{fl};
lp->addStmtsp(new AstLoopTest{fl, lp, orp});
lp->addStmtsp(first_clearp);
first_clearp->addNext(bodyPointp);
AstNode* const ifbodyp
= new AstAssign{fl, new AstVarRef{fl, first_varp, VAccess::WRITE},
new AstConst{fl, AstConst::BitTrue{}}};
ifbodyp->addNext(whilep);
ifbodyp->addNext(lp);
loopp = varp;
loopp->addNext(first_varp);
loopp->addNext(

View File

@ -39,10 +39,10 @@ std::string CfgBlock::name() const {
ss << "if (";
V3EmitV::debugVerilogForTree(ifp->condp(), ss);
ss << ") ...";
} else if (const AstWhile* const whilep = VN_CAST(nodep, While)) {
ss << "while (";
V3EmitV::debugVerilogForTree(whilep->condp(), ss);
ss << ") ...";
} else if (const AstLoopTest* const testp = VN_CAST(nodep, LoopTest)) {
ss << "if (!";
V3EmitV::debugVerilogForTree(testp->condp(), ss);
ss << ") break;";
} else {
V3EmitV::debugVerilogForTree(nodep, ss);
}

View File

@ -39,6 +39,8 @@ class CfgBuilder final : public VNVisitorConst {
CfgBlock* m_currBBp = nullptr;
// Continuation block for given JumpBlock
std::unordered_map<AstJumpBlock*, CfgBlock*> m_jumpBlockContp;
// Continuation block for given Loop
std::unordered_map<AstLoop*, CfgBlock*> m_loopContp;
// METHODS
@ -119,29 +121,40 @@ class CfgBuilder final : public VNVisitorConst {
// Set continuation
m_currBBp = contBBp;
}
void visit(AstWhile* nodep) override {
void visit(AstLoop* nodep) override {
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
if (!m_cfgp) return;
// Create the header block
CfgBlock* const headBBp = m_cfgp->addBlock();
m_cfgp->addTakenEdge(m_currBBp, headBBp);
// Don't acutally need to add this 'nodep' to any block
// The While goes in the header block - semantically the condition check only ...
m_currBBp = headBBp;
addStmt(nodep);
// Create the body/continuation blocks
CfgBlock* const bodyBBp = m_cfgp->addBlock();
// Create continuation block
CfgBlock* const contBBp = m_cfgp->addBlock();
m_cfgp->addTakenEdge(headBBp, bodyBBp);
m_cfgp->addUntknEdge(headBBp, contBBp);
const bool newEntry = m_loopContp.emplace(nodep, contBBp).second;
UASSERT_OBJ(newEntry, nodep, "AstLoop visited twice");
// Create the body block
CfgBlock* const bodyBBp = m_cfgp->addBlock();
m_cfgp->addTakenEdge(m_currBBp, bodyBBp);
// Build the body
m_currBBp = bodyBBp;
iterateAndNextConstNull(nodep->stmtsp());
iterateAndNextConstNull(nodep->incsp());
if (!m_cfgp) return;
if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, headBBp);
if (m_currBBp) m_cfgp->addTakenEdge(m_currBBp, bodyBBp);
// Set continuation
m_currBBp = contBBp;
}
void visit(AstLoopTest* nodep) override {
if (!m_cfgp) return;
// Add terminator statement to current block - semantically the condition check only ...
addStmt(nodep);
// Create continuation blocks
CfgBlock* const contBBp = m_cfgp->addBlock();
m_cfgp->addTakenEdge(m_currBBp, contBBp);
m_cfgp->addUntknEdge(m_currBBp, m_loopContp.at(nodep->loopp()));
// Set continuation
m_currBBp = contBBp;

View File

@ -169,7 +169,7 @@ class CfgLiveVariables final : VNVisitorConst {
// Only the condition check belongs to the terminated basic block
void visit(AstIf* nodep) override { single(nodep->condp()); }
void visit(AstWhile* nodep) override { single(nodep->condp()); }
void visit(AstLoopTest* nodep) override { single(nodep->condp()); }
// CONSTRUCTOR
explicit CfgLiveVariables(const CfgGraph& cfg)

View File

@ -268,7 +268,8 @@ class CleanVisitor final : public VNVisitor {
ensureClean(nodep->condp());
setClean(nodep, isClean(nodep->thenp()) && isClean(nodep->elsep()));
}
void visit(AstWhile* nodep) override {
void visit(AstLoop* nodep) override { iterateChildren(nodep); }
void visit(AstLoopTest* nodep) override {
iterateChildren(nodep);
ensureClean(nodep->condp());
}

View File

@ -926,7 +926,8 @@ class ConstVisitor final : public VNVisitor {
bool m_doV = false; // Verilog, not C++ conversion
bool m_doGenerate = false; // Postpone width checking inside generate
bool m_convertLogicToBit = false; // Convert logical operators to bitwise
bool m_hasJumpDelay = false; // JumpGo or Delay under this while
bool m_hasJumpDelay = false; // JumpGo or Delay under this loop
bool m_hasLoopTest = false; // Contains AstLoopTest
bool m_underRecFunc = false; // Under a recursive function
AstNodeModule* m_modp = nullptr; // Current module
const AstArraySel* m_selp = nullptr; // Current select
@ -3588,31 +3589,62 @@ class ConstVisitor final : public VNVisitor {
// replaceWithSimulation on the Arg's parent FuncRef replaces these
iterateChildren(nodep);
}
void visit(AstWhile* nodep) override {
void visit(AstLoop* nodep) override {
VL_RESTORER(m_hasLoopTest);
const bool oldHasJumpDelay = m_hasJumpDelay;
m_hasJumpDelay = false;
{ iterateChildren(nodep); }
const bool thisWhileHasJumpDelay = m_hasJumpDelay;
m_hasJumpDelay = thisWhileHasJumpDelay || oldHasJumpDelay;
if (m_doNConst) {
if (nodep->condp()->isZero()) {
UINFO(4, "WHILE(0) => nop " << nodep);
nodep->v3warn(UNUSEDLOOP,
"Loop condition is always false; body will never execute");
nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDLOOP, true);
nodep->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else if (nodep->condp()->isNeqZero()) {
if (!thisWhileHasJumpDelay) {
nodep->v3warn(INFINITELOOP, "Infinite loop (condition always true)");
nodep->fileline()->modifyWarnOff(V3ErrorCode::INFINITELOOP,
true); // Complain just once
}
} else if (operandBoolShift(nodep->condp())) {
replaceBoolShift(nodep->condp());
m_hasLoopTest = false;
iterateChildren(nodep);
bool thisLoopHasJumpDelay = m_hasJumpDelay;
m_hasJumpDelay = thisLoopHasJumpDelay || oldHasJumpDelay;
// If the first statement always break, the loop is useless
if (AstLoopTest* const testp = VN_CAST(nodep->stmtsp(), LoopTest)) {
if (testp->condp()->isZero()) {
nodep->v3warn(UNUSEDLOOP, "Loop condition is always false");
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
}
// If last statement always breaks, repalce loop with body
if (AstNode* lastp = nodep->stmtsp()) {
while (AstNode* const nextp = lastp->nextp()) lastp = nextp;
if (AstLoopTest* const testp = VN_CAST(lastp, LoopTest)) {
if (testp->condp()->isZero()) {
VL_DO_DANGLING(pushDeletep(testp->unlinkFrBack()), testp);
nodep->replaceWith(nodep->stmtsp()->unlinkFrBackWithNext());
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
}
}
// Warn on infinite loop
if (!m_hasLoopTest && !thisLoopHasJumpDelay) {
nodep->v3warn(INFINITELOOP, "Infinite loop (condition always true)");
nodep->fileline()->modifyWarnOff(V3ErrorCode::INFINITELOOP, true); // Complain once
}
}
void visit(AstLoopTest* nodep) override {
iterateChildren(nodep);
// If never breaks, remove
if (nodep->condp()->isNeqZero()) {
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
m_hasLoopTest = true;
// If always breaks, subsequent statements are dead code, delete them
if (nodep->condp()->isZero()) {
if (AstNode* const nextp = nodep->nextp()) {
VL_DO_DANGLING(pushDeletep(nextp->unlinkFrBackWithNext()), nodep);
}
return;
}
if (operandBoolShift(nodep->condp())) replaceBoolShift(nodep->condp());
}
void visit(AstInitArray* nodep) override { iterateChildren(nodep); }
void visit(AstInitItem* nodep) override { iterateChildren(nodep); }
void visit(AstUnbounded* nodep) override { iterateChildren(nodep); }
@ -3674,6 +3706,11 @@ class ConstVisitor final : public VNVisitor {
void visit(AstJumpBlock* nodep) override {
iterateChildren(nodep);
// If first statement is an AstLoopTest, pull it before the jump block
if (AstLoopTest* const testp = VN_CAST(nodep->stmtsp(), LoopTest)) {
nodep->addHereThisAsNext(testp->unlinkFrBack());
}
// Remove if empty
if (!nodep->stmtsp()) {
UINFO(4, "JUMPLABEL => empty " << nodep);

View File

@ -135,7 +135,7 @@ class CoverageVisitor final : public VNVisitor {
// NODE STATE
// Entire netlist:
// AstIf::user1() -> bool. True indicates ifelse processed
// AstIf::user2() -> bool. True indicates coverage-generated
// AstIf/AstLoopTest::user2() -> bool. True indicates coverage-generated
const VNUser1InUse m_inuser1;
const VNUser2InUse m_inuser2;
@ -153,7 +153,6 @@ class CoverageVisitor final : public VNVisitor {
bool m_objective = false; // Expression objective
bool m_ifCond = false; // Visiting if condition
bool m_inToggleOff = false; // In function/task etc
bool m_inLoopNotBody = false; // Inside a loop, but not in its body
string m_beginHier; // AstBegin hier name for user coverage points
// STATE - cleared each module
@ -285,23 +284,14 @@ class CoverageVisitor final : public VNVisitor {
}
void visit(AstNodeProcedure* nodep) override { iterateProcedure(nodep); }
// we can cover expressions in while loops, but the counting goes outside
// the while, see: "minimally-intelligent decision about ... clock domain"
// in the Toggle Coverage docs
void visit(AstWhile* nodep) override {
void visit(AstLoop* nodep) override {
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
VL_RESTORER(m_state);
VL_RESTORER(m_inToggleOff);
m_inToggleOff = true;
createHandle(nodep);
{
VL_RESTORER(m_inLoopNotBody);
m_inLoopNotBody = true;
iterateNull(nodep->condp());
iterateAndNextNull(nodep->incsp());
}
iterateAndNextNull(nodep->stmtsp());
if (m_state.lineCoverageOn(nodep)) {
lineTrack(nodep);
AstCoverOtherDecl* const declp
= new AstCoverOtherDecl{nodep->fileline(), "v_line/" + m_modp->prettyName(),
"block", linesCov(m_state, nodep), 0};
@ -311,6 +301,21 @@ class CoverageVisitor final : public VNVisitor {
insertProcStatement(nodep, newp);
}
}
void visit(AstLoopTest* nodep) override {
if (nodep->user2SetOnce()) return;
lineTrack(nodep);
if (m_state.lineCoverageOn(nodep) && nodep->backp()->nextp() == nodep) {
AstCoverOtherDecl* const declp
= new AstCoverOtherDecl{nodep->fileline(), "v_line/" + m_modp->prettyName(),
"block", linesCov(m_state, nodep), 0};
m_modp->addStmtsp(declp);
AstNode* const newp
= newCoverInc(nodep->fileline(), declp, traceNameForLine(nodep, "block"));
nodep->addHereThisAsNext(newp);
createHandle(nodep);
}
iterateChildren(nodep);
}
void visit(AstNodeFTask* nodep) override {
if (!nodep->dpiImport()) iterateProcedure(nodep);
@ -321,7 +326,7 @@ class CoverageVisitor final : public VNVisitor {
itemp->addStmtsp(stmtp);
} else if (AstNodeFTask* const itemp = VN_CAST(nodep, NodeFTask)) {
itemp->addStmtsp(stmtp);
} else if (AstWhile* const itemp = VN_CAST(nodep, While)) {
} else if (AstLoop* const itemp = VN_CAST(nodep, Loop)) {
itemp->addStmtsp(stmtp);
} else if (AstIf* const itemp = VN_CAST(nodep, If)) {
if (m_then) {
@ -499,8 +504,7 @@ class CoverageVisitor final : public VNVisitor {
return includeCondToBranchRecursive(backp);
} else if (VN_IS(backp, Sel) && VN_AS(backp, Sel)->fromp() == nodep) {
return includeCondToBranchRecursive(backp);
} else if (VN_IS(backp, NodeAssign) && VN_AS(backp, NodeAssign)->rhsp() == nodep
&& !m_inLoopNotBody) {
} else if (VN_IS(backp, NodeAssign) && VN_AS(backp, NodeAssign)->rhsp() == nodep) {
return true;
}
return false;

View File

@ -1298,7 +1298,7 @@ class DelayedVisitor final : public VNVisitor {
// Record write reference
recordWriteRef(nodep, false);
}
void visit(AstWhile* nodep) override {
void visit(AstLoop* nodep) override {
VL_RESTORER(m_inLoop);
m_inLoop = true;
iterateChildren(nodep);

View File

@ -118,7 +118,7 @@ public:
class EmitCFunc VL_NOT_FINAL : public EmitCConstInit {
VMemberMap m_memberMap;
AstVarRef* m_wideTempRefp = nullptr; // Variable that _WW macros should be setting
std::unordered_map<AstJumpBlock*, size_t> m_labelNumbers; // Label numbers for JumpBlocks
std::unordered_map<AstJumpBlock*, size_t> m_labelNumbers; // Label numbers for AstJumpBlocks
bool m_inUC = false; // Inside an AstUCStmt or AstUCExpr
bool m_emitConstInit = false; // Emitting constant initializer
bool m_createdScopeHash = false; // Already created a scope hash
@ -1095,21 +1095,47 @@ public:
// Emit
putns(nodep, "goto __Vlabel" + std::to_string(n) + ";\n");
}
void visit(AstLoop* nodep) override {
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
VL_RESTORER(m_createdScopeHash);
// Special case when the AstLoopTest is first for output readability
if (AstLoopTest* const testp = VN_CAST(nodep->stmtsp(), LoopTest)) {
putns(nodep, "while (");
iterateConst(testp->condp());
puts(") {\n");
iterateAndNextConstNull(testp->nextp());
puts("}\n");
return;
}
// Special case when the AstLoopTest is last for output readability
if (AstNode* lastp = nodep->stmtsp()) {
while (AstNode* const nextp = lastp->nextp()) lastp = nextp;
if (AstLoopTest* const testp = VN_CAST(lastp, LoopTest)) {
putns(nodep, "do {\n");
for (AstNode* p = nodep->stmtsp(); p != lastp; p = p->nextp()) iterateConst(p);
puts("} while (");
iterateConst(testp->condp());
puts(");\n");
return;
}
}
// Emit generic case directly
putns(nodep, "while (true) {\n");
iterateAndNextConstNull(nodep->stmtsp());
puts("}\n");
}
void visit(AstLoopTest* nodep) override {
VL_RESTORER(m_createdScopeHash);
putns(nodep, "if (!(");
iterateAndNextConstNull(nodep->condp());
puts(")) break;\n");
}
void visit(AstCLocalScope* nodep) override {
putns(nodep, "{\n");
VL_RESTORER(m_createdScopeHash);
iterateAndNextConstNull(nodep->stmtsp());
puts("}\n");
}
void visit(AstWhile* nodep) override {
VL_RESTORER(m_createdScopeHash);
putns(nodep, "while (");
iterateAndNextConstNull(nodep->condp());
puts(") {\n");
iterateAndNextConstNull(nodep->stmtsp());
iterateAndNextConstNull(nodep->incsp());
puts("}\n");
}
void visit(AstNodeIf* nodep) override {
putns(nodep, "if (");
if (!nodep->branchPred().unknown()) {

View File

@ -399,14 +399,39 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
iterateAndNextConstNull(nodep->stmtsp());
putfs(nodep, "end\n");
}
void visit(AstWhile* nodep) override {
putfs(nodep, "while (");
iterateAndNextConstNull(nodep->condp());
puts(") begin\n");
void visit(AstLoop* nodep) override {
// Special case when the AstLoopTest is first for output readability
if (AstLoopTest* const testp = VN_CAST(nodep->stmtsp(), LoopTest)) {
putfs(nodep, "while (");
iterateConst(testp->condp());
puts(") begin\n");
iterateAndNextConstNull(testp->nextp());
puts("end\n");
return;
}
// Special case when the AstLoopTest is last for output readability
if (AstNode* lastp = nodep->stmtsp()) {
while (AstNode* const nextp = lastp->nextp()) lastp = nextp;
if (AstLoopTest* const testp = VN_CAST(lastp, LoopTest)) {
putfs(nodep, "do begin\n");
for (AstNode* p = nodep->stmtsp(); p != lastp; p = p->nextp()) iterateConst(p);
puts("end while (");
iterateConst(testp->condp());
puts(")\n");
return;
}
}
// Generic case
putfs(nodep, "while (true) begin\n");
iterateAndNextConstNull(nodep->stmtsp());
iterateAndNextConstNull(nodep->incsp());
iterateAndNextConstNull(nodep->contsp());
putfs(nodep, "end\n");
}
void visit(AstLoopTest* nodep) override {
putfs(nodep, "if (!(");
iterateAndNextConstNull(nodep->condp());
puts(")) break;\n");
}
void visit(AstNodeIf* nodep) override {
putfs(nodep, "");
if (const AstIf* const ifp = VN_CAST(nodep, If)) {

View File

@ -126,25 +126,26 @@ class EmitXmlFileVisitor final : public VNVisitorConst {
}
puts("</if>\n");
}
void visit(AstWhile* nodep) override {
outputTag(nodep, "while");
void visit(AstLoop* nodep) override {
outputTag(nodep, "loop");
puts(">\n");
if (nodep->condp()) {
puts("<begin>\n");
iterateAndNextConstNull(nodep->condp());
puts("</begin>\n");
}
if (nodep->stmtsp()) {
puts("<begin>\n");
iterateAndNextConstNull(nodep->stmtsp());
puts("</begin>\n");
}
if (nodep->incsp()) {
if (nodep->contsp()) {
puts("<begin>\n");
iterateAndNextConstNull(nodep->incsp());
iterateAndNextConstNull(nodep->contsp());
puts("</begin>\n");
}
puts("</while>\n");
puts("</loop>\n");
}
void visit(AstLoopTest* nodep) override {
outputTag(nodep, "looptest");
puts(">\n");
iterateAndNextConstNull(nodep->condp());
puts("</looptest>\n");
}
void visit(AstNetlist* nodep) override {
puts("<netlist>\n");

View File

@ -1229,9 +1229,9 @@ class GateUnused final {
if (const AstNodeProcedure* const procedurep = VN_CAST(nodep, NodeProcedure)) {
if (procedurep->stmtsp())
procedurep->stmtsp()->foreach([](const AstWhile* const whilep) { //
whilep->v3warn(UNUSEDLOOP, "Loop is not used and will be optimized out");
whilep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDLOOP, true);
procedurep->stmtsp()->foreach([](const AstLoop* const loopp) { //
loopp->v3warn(UNUSEDLOOP, "Loop is not used and will be optimized out");
loopp->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDLOOP, true);
});
}
}

View File

@ -387,37 +387,25 @@ class LifeVisitor final : public VNVisitor {
VL_DO_DANGLING(delete ifLifep, ifLifep);
VL_DO_DANGLING(delete elseLifep, elseLifep);
}
void visit(AstWhile* nodep) override {
// While's are a problem, as we don't allow loops in the graph. We
// may go around the cond/body multiple times. Thus a
// lifelication just in the body is ok, but we can't delete an
// assignment in the body that's used in the cond. (And otherwise
// would because it only appears used after-the-fact. So, we model
// it as a IF statement, and just don't allow elimination of
// variables across the body.
void visit(AstLoop* nodep) override {
// Similar problem to AstJumpBlock, don't optimize loop bodies - most are unrolled
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
LifeBlock* const prevLifep = m_lifep;
LifeBlock* const condLifep = new LifeBlock{prevLifep, m_statep};
LifeBlock* const bodyLifep = new LifeBlock{prevLifep, m_statep};
{
m_lifep = condLifep;
iterateAndNextNull(nodep->condp());
}
{
VL_RESTORER(m_noopt);
m_lifep = bodyLifep;
setNoopt();
iterateAndNextNull(nodep->stmtsp());
iterateAndNextNull(nodep->incsp());
m_lifep = prevLifep;
}
m_lifep = prevLifep;
UINFO(4, " joinfor");
UINFO(4, " joinloop");
// For the next assignments, clear any variables that were read or written in the block
condLifep->lifeToAbove();
bodyLifep->lifeToAbove();
VL_DO_DANGLING(delete condLifep, condLifep);
VL_DO_DANGLING(delete bodyLifep, bodyLifep);
}
void visit(AstJumpBlock* nodep) override {
// As with While's we can't predict if a JumpGo will kill us or not
// As with Loop's we can't predict if a JumpGo will kill us or not
// It's worse though as an IF(..., JUMPGO) may change the control flow.
// Just don't optimize blocks with labels; they're rare - so far.
LifeBlock* const prevLifep = m_lifep;

View File

@ -56,7 +56,6 @@ class LinkIncVisitor final : public VNVisitor {
AstNodeFTask* m_ftaskp = nullptr; // Function or task we're inside
AstNodeModule* m_modp = nullptr; // Module we're inside
int m_modIncrementsNum = 0; // Var name counter
AstWhile* m_inWhileCondp = nullptr; // Inside condition of this while loop
AstNode* m_insStmtp = nullptr; // Where to insert statement
bool m_unsupportedHere = false; // Used to detect where it's not supported yet
@ -81,14 +80,12 @@ class LinkIncVisitor final : public VNVisitor {
// Return node that must be visited, if any
UINFOTREE(9, newp, "", "newstmt");
UASSERT_OBJ(m_insStmtp, nodep, "Expression not underneath a statement");
// In a while condition, the statement also needs to go on the
// back-edge to the loop header, 'incsp' is that place.
if (m_inWhileCondp) m_inWhileCondp->addIncsp(newp->cloneTreePure(true));
m_insStmtp->addHereThisAsNext(newp);
}
// VISITORS
void visit(AstNodeModule* nodep) override {
if (nodep->dead()) return;
VL_RESTORER(m_modp);
VL_RESTORER(m_modIncrementsNum);
m_modp = nodep;
@ -113,20 +110,10 @@ class LinkIncVisitor final : public VNVisitor {
}
iterateAndNextNull(nodep->passsp());
}
void visit(AstWhile* nodep) override {
// Special, as statements need to be put in different places
m_insStmtp = nodep;
{
// Conditions insert before the loop and into incsp
VL_RESTORER(m_inWhileCondp);
m_inWhileCondp = nodep;
iterateAndNextNull(nodep->condp());
}
// Body insert just before themselves
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());
iterateAndNextNull(nodep->incsp());
// Done the loop
m_insStmtp = nullptr; // Next thing should be new statement
}
void visit(AstNodeForeach* nodep) override {
@ -181,6 +168,11 @@ class LinkIncVisitor final : public VNVisitor {
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(AstNodeStmt* nodep) override {
m_insStmtp = nodep;
iterateChildren(nodep);

View File

@ -86,24 +86,18 @@ class LinkJumpVisitor final : public VNVisitor {
} else if (AstForeach* const foreachp = VN_CAST(nodep, Foreach)) {
if (endOfIter) {
underp = foreachp->stmtsp();
// Keep a LoopTest **at the front** outside the jump block
if (VN_IS(underp, LoopTest)) underp = underp->nextp();
} else {
underp = nodep;
under_and_next = false; // IE we skip the entire foreach
}
} else if (AstWhile* const whilep = VN_CAST(nodep, While)) {
} else if (AstLoop* const loopp = VN_CAST(nodep, Loop)) {
if (endOfIter) {
underp = whilep->stmtsp();
underp = loopp->stmtsp();
} else {
underp = nodep;
under_and_next = false; // IE we skip the entire while
}
} else if (AstDoWhile* const dowhilep = VN_CAST(nodep, DoWhile)) {
// Handle it the same as AstWhile, because it will be converted to it
if (endOfIter) {
underp = dowhilep->stmtsp();
} else {
underp = nodep;
under_and_next = false;
under_and_next = false; // IE we skip the entire loop
}
} else {
nodep->v3fatalSrc("Unknown jump point for break/disable/continue");
@ -307,52 +301,34 @@ class LinkJumpVisitor final : public VNVisitor {
nodep->fileline(), new AstVarRef{nodep->fileline(), varp, VAccess::READ}, zerosp};
AstNode* const bodysp = nodep->stmtsp();
if (bodysp) bodysp->unlinkFrBackWithNext();
AstWhile* const whilep = new AstWhile{nodep->fileline(), condp, bodysp, decp};
if (!m_unrollFull.isDefault()) whilep->unrollFull(m_unrollFull);
FileLine* const flp = nodep->fileline();
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(new AstLoopTest{flp, loopp, condp});
loopp->addStmtsp(bodysp);
loopp->addContsp(decp);
if (!m_unrollFull.isDefault()) loopp->unroll(m_unrollFull);
m_unrollFull = VOptionBool::OPT_DEFAULT_FALSE;
beginp->addStmtsp(initsp);
beginp->addStmtsp(whilep);
beginp->addStmtsp(loopp);
// Replacement AstBegin will be iterated next
nodep->replaceWith(beginp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstWhile* nodep) override {
// Don't need to track AstRepeat/AstFor as they have already been converted
if (!m_unrollFull.isDefault()) nodep->unrollFull(m_unrollFull);
if (m_modp->hasParameterList() || m_modp->hasGParam())
void visit(AstLoop* nodep) override {
if (!m_unrollFull.isDefault()) nodep->unroll(m_unrollFull);
if (m_modp->hasParameterList() || m_modp->hasGParam()) {
nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDLOOP, true);
}
m_unrollFull = VOptionBool::OPT_DEFAULT_FALSE;
VL_RESTORER(m_loopp);
VL_RESTORER(m_loopInc);
m_loopp = nodep;
m_loopInc = false;
iterateAndNextNull(nodep->condp());
iterateAndNextNull(nodep->stmtsp());
m_loopInc = true;
iterateAndNextNull(nodep->incsp());
}
void visit(AstDoWhile* nodep) override {
// It is converted to AstWhile in this visit method
VL_RESTORER(m_loopp);
{
m_loopp = nodep;
iterateAndNextNull(nodep->condp());
iterateAndNextNull(nodep->stmtsp());
}
AstNodeExpr* const condp = nodep->condp() ? nodep->condp()->unlinkFrBack() : nullptr;
AstNode* const bodyp = nodep->stmtsp() ? nodep->stmtsp()->unlinkFrBack() : nullptr;
AstWhile* const whilep = new AstWhile{nodep->fileline(), condp, bodyp};
if (!m_unrollFull.isDefault()) whilep->unrollFull(m_unrollFull);
m_unrollFull = VOptionBool::OPT_DEFAULT_FALSE;
// No unused warning for converted AstDoWhile, as body always executes once
nodep->fileline()->modifyWarnOff(V3ErrorCode::UNUSEDLOOP, true);
nodep->replaceWith(whilep);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
if (bodyp) {
AstNode* const copiedBodyp = bodyp->cloneTree(false);
addPrefixToBlocksRecurse("__Vdo_while1_", copiedBodyp);
addPrefixToBlocksRecurse("__Vdo_while2_", bodyp);
whilep->addHereThisAsNext(copiedBodyp);
}
iterateAndNextNull(nodep->contsp());
// Move contsp into stmtsp, no longer needed to keep separately
if (nodep->contsp()) nodep->addStmtsp(nodep->contsp()->unlinkFrBackWithNext());
}
void visit(AstNodeForeach* nodep) override {
VL_RESTORER(m_loopp);

View File

@ -130,7 +130,7 @@ class LinkParseVisitor final : public VNVisitor {
// Try very hard to avoid false positives
AstNode* nextp = nodep->nextp();
if (!childp) return;
if (!nextp && VN_IS(nodep, While) && VN_IS(nodep->backp(), Begin))
if (!nextp && VN_IS(nodep, Loop) && VN_IS(nodep->backp(), Begin))
nextp = nodep->backp()->nextp();
if (!nextp) return;
if (VN_IS(childp, Begin) || VN_IS(childp, GenBlock)) return;
@ -573,10 +573,15 @@ class LinkParseVisitor final : public VNVisitor {
checkIndent(nodep, nodep->stmtsp());
iterateChildren(nodep);
}
void visit(AstDoWhile* nodep) override {
void visit(AstLoop* nodep) override {
cleanFileline(nodep);
VL_RESTORER(m_insideLoop);
m_insideLoop = true;
if (VN_IS(nodep->stmtsp(), LoopTest)) {
checkIndent(nodep, nodep->stmtsp()->nextp());
} else {
checkIndent(nodep, nodep->stmtsp());
}
iterateChildren(nodep);
}
void visit(AstWait* nodep) override {
@ -590,13 +595,6 @@ class LinkParseVisitor final : public VNVisitor {
nodep->fileline(newfl);
}
}
void visit(AstWhile* nodep) override {
cleanFileline(nodep);
VL_RESTORER(m_insideLoop);
m_insideLoop = true;
checkIndent(nodep, nodep->stmtsp());
iterateChildren(nodep);
}
void visit(AstNodeModule* nodep) override {
V3Control::applyModule(nodep);
++m_statModules;

View File

@ -55,6 +55,8 @@
#include "V3Hasher.h"
#include "V3Os.h"
#include "V3Parse.h"
#include "V3Simulate.h"
#include "V3Stats.h"
#include "V3Unroll.h"
#include "V3Width.h"
@ -1267,7 +1269,7 @@ class ParamVisitor final : public VNVisitor {
// STATE - across all visitors
ParamState& m_state; // Common state
ParamProcessor m_processor; // De-parameterize a cell, build modules
UnrollStateful m_unroller; // Loop unroller
GenForUnroller m_unroller; // Unroller for AstGenFor
bool m_iterateModule = false; // Iterating module body
string m_unlinkedTxt; // Text for AstUnlinkedRef
@ -1632,10 +1634,10 @@ class ParamVisitor final : public VNVisitor {
// a BEGIN("zzz__BRA__{loop#}__KET__")
const string beginName = nodep->name();
// Leave the original Begin, as need a container for the (possible) GENVAR
// Note V3Unroll will replace some AstVarRef's to the loop variable with constants
// Note m_unroller will replace some AstVarRef's to the loop variable with constants
// Don't remove any deleted nodes in m_unroller until whole process finishes,
// (are held in m_unroller), as some AstXRefs may still point to old nodes.
VL_DO_DANGLING(m_unroller.unrollGen(forp, beginName), forp);
// as some AstXRefs may still point to old nodes.
VL_DO_DANGLING(m_unroller.unroll(forp, beginName), forp);
// Blocks were constructed under the special begin, move them up
// Note forp is null, so grab statements again
if (AstNode* const stmtsp = nodep->genforp()) {

View File

@ -52,7 +52,6 @@ class PremitVisitor final : public VNVisitor {
AstCFunc* m_cfuncp = nullptr; // Current block
size_t m_tmpVarCnt = 0; // Number of temporary variables created inside a function
AstNode* m_stmtp = nullptr; // Current statement
AstWhile* m_inWhileCondp = nullptr; // Inside condition of this while loop
bool m_assignLhs = false; // Inside assignment lhs, don't breakup extracts
// METHODS
@ -100,10 +99,6 @@ class PremitVisitor final : public VNVisitor {
= new AstAssign{flp, new AstVarRef{flp, varp, VAccess::WRITE}, nodep};
// Insert before the statement
m_stmtp->addHereThisAsNext(assignp);
// Statements that are needed for the 'condition' in a while also
// need to be inserted on the back-edge to the loop header.
// 'incsp' is just right palce to do this
if (m_inWhileCondp) m_inWhileCondp->addIncsp(assignp->cloneTree(false));
}
// Replace node with VarRef to new Var
@ -173,24 +168,9 @@ class PremitVisitor final : public VNVisitor {
if (stmtp->user1SetOnce()) return; \
VL_RESTORER(m_assignLhs); \
VL_RESTORER(m_stmtp); \
VL_RESTORER(m_inWhileCondp); \
m_assignLhs = false; \
m_stmtp = stmtp; \
m_inWhileCondp = nullptr
m_stmtp = stmtp;
void visit(AstWhile* nodep) override {
UINFO(4, " WHILE " << nodep);
// cppcheck-suppress shadowVariable // Also restored below
START_STATEMENT_OR_RETURN(nodep);
{
// cppcheck-suppress shadowVariable // Also restored above
VL_RESTORER(m_inWhileCondp);
m_inWhileCondp = nodep;
iterateAndNextNull(nodep->condp());
}
iterateAndNextNull(nodep->stmtsp());
iterateAndNextNull(nodep->incsp());
}
void visit(AstNodeAssign* nodep) override {
START_STATEMENT_OR_RETURN(nodep);

View File

@ -1530,12 +1530,15 @@ class RandomizeVisitor final : public VNVisitor {
AstNode* const stmtsp = iterVarp;
stmtsp->addNext(
new AstAssign{fl, new AstVarRef{fl, iterVarp, VAccess::WRITE}, new AstConst{fl, 0}});
stmtsp->addNext(
new AstWhile{fl, new AstLt{fl, new AstVarRef{fl, iterVarp, VAccess::READ}, sizep},
new AstAssign{fl, setp, rhsp},
new AstAssign{fl, new AstVarRef{fl, iterVarp, VAccess::WRITE},
new AstAdd{fl, new AstConst{fl, 1},
new AstVarRef{fl, iterVarp, VAccess::READ}}}});
AstLoop* const loopp = new AstLoop{fl};
stmtsp->addNext(loopp);
loopp->addStmtsp(new AstLoopTest{
fl, loopp, new AstLt{fl, new AstVarRef{fl, iterVarp, VAccess::READ}, sizep}});
loopp->addStmtsp(new AstAssign{fl, setp, rhsp});
loopp->addStmtsp(new AstAssign{
fl, new AstVarRef{fl, iterVarp, VAccess::WRITE},
new AstAdd{fl, new AstConst{fl, 1}, new AstVarRef{fl, iterVarp, VAccess::READ}}});
return new AstBegin{fl, "", stmtsp, true};
}
static AstNodeStmt* wrapIfRandMode(AstClass* classp, AstVar* const varp, AstNodeStmt* stmtp) {

View File

@ -102,11 +102,13 @@ class ReloopVisitor final : public VNVisitor {
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);
AstLoop* const loopp = new AstLoop{fl};
loopp->addStmtsp(new AstLoopTest{fl, loopp, condp});
initp->addNext(loopp);
itp->AstNode::addNext(initp);
bodyp->replaceWith(itp);
whilep->addStmtsp(bodyp);
loopp->addStmtsp(bodyp);
loopp->addStmtsp(incp);
// Replace constant index with new loop index
AstNodeExpr* const offsetp
@ -122,7 +124,7 @@ class ReloopVisitor final : public VNVisitor {
VL_DO_DANGLING(rbitp->deleteTree(), lbitp);
}
UINFOTREE(9, initp, "", "new");
UINFOTREE(9, whilep, "", "new");
UINFOTREE(9, loopp, "", "new");
// Remove remaining assigns
for (AstNodeAssign* assp : m_mgAssignps) {

View File

@ -263,8 +263,9 @@ EvalLoop createEvalLoop(
// The loop
{
AstWhile* const loopp
= new AstWhile{flp, new AstVarRef{flp, continueFlagp, VAccess::READ}};
AstNodeExpr* const condp = new AstVarRef{flp, continueFlagp, VAccess::READ};
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(new AstLoopTest{flp, loopp, condp});
// Check the iteration limit (aborts if exceeded)
loopp->addStmtsp(checkIterationLimit(netlistp, name, counterp, dumpFuncp));
@ -451,14 +452,12 @@ void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) {
if (VN_IS(procp, Always)) {
subFuncp->slow(false);
FileLine* const flp = procp->fileline();
bodyp = new AstWhile{
flp,
// If we change to use exceptions to handle finish/stop,
// this can get removed
new AstCExpr{flp,
"VL_LIKELY(!vlSymsp->_vm_contextp__->gotFinish())", 1,
true},
bodyp};
AstNodeExpr* const condp = new AstCExpr{
flp, "VL_LIKELY(!vlSymsp->_vm_contextp__->gotFinish())", 1, true};
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(new AstLoopTest{flp, loopp, condp});
loopp->addStmtsp(bodyp);
bodyp = loopp;
}
}
subFuncp->addStmtsp(bodyp);

View File

@ -195,9 +195,8 @@ private:
m_trigAssignMemberVarp = nullptr;
}
}
void visit(AstWhile* nodep) override {
unsupportedWriteToVirtIface(nodep->condp(), "loop condition");
unsupportedWriteToVirtIface(nodep->incsp(), "loop increment statement");
void visit(AstLoop* nodep) override {
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
{
VL_RESTORER(m_trigAssignp);
VL_RESTORER(m_trigAssignIfacep);
@ -211,6 +210,9 @@ private:
m_trigAssignMemberVarp = nullptr;
}
}
void visit(AstLoopTest* nodep) override {
unsupportedWriteToVirtIface(nodep->condp(), "loop condition");
}
void visit(AstJumpBlock* nodep) override {
{
VL_RESTORER(m_trigAssignp);

View File

@ -141,7 +141,7 @@ private:
int m_instrCount; ///< Number of nodes
int m_dataCount; ///< Bytes of data
int m_recurseCount = 0; // Now deep in current recursion
AstJumpGo* m_jumpp = nullptr; ///< Jump label we're branching from
AstNode* m_jumptargetp = nullptr; // AstJumpBlock/AstLoop we are branching over
// Simulating:
// Allocators for constants of various data types
std::unordered_map<const AstNodeDType*, ConstAllocator> m_constps;
@ -409,7 +409,7 @@ private:
}
// True if current node might be jumped over - all visitors must call this up front
bool jumpingOver() const { return m_jumpp; }
bool jumpingOver() const { return m_jumptargetp; }
void assignOutValue(const AstNodeAssign* nodep, AstNode* vscp, const AstNodeExpr* valuep) {
if (VN_IS(nodep, AssignDly)) {
// Don't do setValue, as value isn't yet visible to following statements
@ -1056,9 +1056,9 @@ private:
void visit(AstJumpBlock* nodep) override {
if (jumpingOver()) return;
iterateChildrenConst(nodep);
if (m_jumpp && m_jumpp->blockp() == nodep) {
if (m_jumptargetp == nodep) {
UINFO(5, " JUMP DONE " << nodep);
m_jumpp = nullptr;
m_jumptargetp = nullptr;
}
}
void visit(AstJumpGo* nodep) override {
@ -1066,9 +1066,55 @@ private:
checkNodeInfo(nodep);
if (!m_checkOnly) {
UINFO(5, " JUMP GO " << nodep);
// Should be back at the JumpBlock and clear m_jumpp before another JumpGo
UASSERT_OBJ(!m_jumpp, nodep, "Jump inside jump");
m_jumpp = nodep;
UASSERT_OBJ(!m_jumptargetp, nodep, "Jump inside jump");
m_jumptargetp = nodep->blockp();
}
}
void visit(AstLoop* nodep) override {
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
if (jumpingOver()) return;
UINFO(5, " LOOP " << nodep);
// Doing lots of loops is slow, so only for parameters
if (!m_params) {
badNodeType(nodep);
return;
}
checkNodeInfo(nodep);
if (m_checkOnly) {
iterateChildrenConst(nodep);
return;
}
if (!optimizable()) return;
int loops = 0;
while (true) {
UINFO(5, " LOOP-ITER " << nodep);
iterateAndNextConstNull(nodep->stmtsp());
if (jumpingOver()) break;
// Prep for next loop
if (loops++ > v3Global.opt.unrollCountAdjusted(nodep->unroll(), m_params, true)) {
clearOptimizable(nodep, "Loop unrolling took too long; probably this is an"
"infinite loop, or use /*verilator unroll_full*/, or "
"set --unroll-count above "
+ cvtToStr(loops));
break;
}
}
if (m_jumptargetp == nodep) {
UINFO(5, " LOOP TEST DONE " << nodep);
m_jumptargetp = nullptr;
}
}
void visit(AstLoopTest* nodep) override {
if (jumpingOver()) return;
checkNodeInfo(nodep);
iterateConst(nodep->condp());
if (!m_checkOnly && optimizable() && fetchConst(nodep->condp())->num().isEqZero()) {
UINFO(5, " LOOP TEST GO " << nodep);
UASSERT_OBJ(!m_jumptargetp, nodep, "Jump inside jump");
m_jumptargetp = nodep->loopp();
}
}
@ -1082,46 +1128,6 @@ private:
}
checkNodeInfo(nodep);
}
void visit(AstWhile* nodep) override {
// Doing lots of Whiles is slow, so only for parameters
if (jumpingOver()) return;
UINFO(5, " WHILE " << nodep);
if (!m_params) {
badNodeType(nodep);
return;
}
checkNodeInfo(nodep);
if (m_checkOnly) {
iterateChildrenConst(nodep);
} else if (optimizable()) {
int loops = 0;
while (true) {
UINFO(5, " WHILE-ITER " << nodep);
iterateAndNextConstNull(nodep->condp());
if (jumpingOver()) break;
if (!optimizable()) break;
if (!fetchConst(nodep->condp())->num().isNeqZero()) { //
break;
}
iterateAndNextConstNull(nodep->stmtsp());
if (jumpingOver()) break;
iterateAndNextConstNull(nodep->incsp());
if (jumpingOver()) break;
// Prep for next loop
if (loops++
> v3Global.opt.unrollCountAdjusted(nodep->unrollFull(), m_params, true)) {
clearOptimizable(nodep, "Loop unrolling took too long; probably this is an"
"infinite loop, or use /*verilator unroll_full*/, or "
"set --unroll-count above "
+ cvtToStr(loops));
break;
}
}
}
}
void visit(AstFuncRef* nodep) override {
if (jumpingOver()) return;
if (!optimizable()) return; // Accelerate
@ -1375,7 +1381,7 @@ private:
}
void mainGuts(AstNode* nodep) {
iterateConst(nodep);
UASSERT_OBJ(!m_jumpp, m_jumpp, "JumpGo branched to label that wasn't found");
UASSERT_OBJ(!m_jumptargetp, m_jumptargetp, "Jump target was not found");
}
public:
@ -1395,7 +1401,7 @@ public:
m_isCoverage = false;
m_instrCount = 0;
m_dataCount = 0;
m_jumpp = nullptr;
m_jumptargetp = nullptr;
AstNode::user1ClearTree();
m_varAux.clear();
@ -1409,10 +1415,6 @@ public:
setMode(true /*scoped*/, false /*checking*/, false /*params*/);
mainGuts(nodep);
}
void mainCheckTree(AstNode* nodep) {
setMode(false /*scoped*/, true /*checking*/, false /*params*/);
mainGuts(nodep);
}
void mainParamEmulate(AstNode* nodep) {
setMode(false /*scoped*/, false /*checking*/, true /*params*/);
mainGuts(nodep);

View File

@ -338,8 +338,7 @@ protected:
void visit(AstAlways* nodep) override = 0;
void visit(AstNodeIf* nodep) override = 0;
// We don't do AstWhile loops, due to the standard question
// of what is before vs. after
// We don't do AstLoop, due to the standard question of what is before vs. after
void visit(AstAssignDly* nodep) override {
VL_RESTORER(m_inDly);

View File

@ -1610,21 +1610,6 @@ class TaskVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
void visit(AstWhile* nodep) override {
// Special, as statements need to be put in different places
{
// Conditions will create a StmtExpr
// Leave m_instStmtp = null, so will assert if not
iterateAndNextNull(nodep->condp());
}
{
// Body insert just before themselves
VL_RESTORER(m_insStmtp);
m_insStmtp = nullptr; // First thing should be new statement
iterateAndNextNull(nodep->stmtsp());
iterateAndNextNull(nodep->incsp());
}
}
void visit(AstNodeForeach* nodep) override { // LCOV_EXCL_LINE
nodep->v3fatalSrc(
"Foreach statements should have been converted to while statements in V3Begin.cpp");

View File

@ -944,9 +944,11 @@ class TimingControlVisitor final : public VNVisitor {
}
// Create the trigger eval loop, which will await the evaluation step and check the
// trigger
AstWhile* const loopp = new AstWhile{
flp, new AstLogNot{flp, new AstVarRef{flp, trigvscp, VAccess::READ}},
awaitEvalp->makeStmt()};
AstNodeExpr* const condp
= new AstLogNot{flp, new AstVarRef{flp, trigvscp, VAccess::READ}};
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(new AstLoopTest{flp, loopp, condp});
loopp->addStmtsp(awaitEvalp->makeStmt());
// Put pre updates before the trigger check and assignment
for (AstNodeStmt* const stmtp : senResults.m_preUpdates) loopp->addStmtsp(stmtp);
// Then the trigger check and assignment
@ -1206,7 +1208,9 @@ class TimingControlVisitor final : public VNVisitor {
flp, new AstSenItem{flp, VEdgeType::ET_CHANGED, condp->cloneTree(false)}},
nullptr};
controlp->user2(true); // Commit immediately
AstWhile* const loopp = new AstWhile{flp, new AstLogNot{flp, condp}, controlp};
AstLoop* const loopp = new AstLoop{flp};
loopp->addStmtsp(new AstLoopTest{flp, loopp, new AstLogNot{flp, condp}});
loopp->addStmtsp(controlp);
if (stmtsp) AstNode::addNext<AstNode, AstNode>(loopp, stmtsp);
nodep->replaceWith(loopp);
}

View File

@ -1,6 +1,6 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Add temporaries, such as for unroll nodes
// DESCRIPTION: Verilator: Loop unrolling
//
// Code available from: https://verilator.org
//
@ -13,14 +13,8 @@
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
// 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
// Unroll AstLoopStmts
//
//*************************************************************************
@ -29,489 +23,374 @@
#include "V3Unroll.h"
#include "V3Const.h"
#include "V3Simulate.h"
#include "V3Stats.h"
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Unroll state, as a visitor of each AstNode
// Statistics tracking
class UnrollVisitor final : public VNVisitor {
// STATE - across all visitors
AstVar* m_forVarp; // Iterator variable
const AstVarScope* m_forVscp; // Iterator variable scope (nullptr for generate pass)
const AstNode* m_ignoreIncp; // Increment node to ignore
bool m_varModeCheck; // Just checking RHS assignments
bool m_varAssignHit; // Assign var hit
bool m_forkHit; // Fork hit
bool m_generate; // Expand single generate For loop
string m_beginName; // What name to give begin iterations
// STATE - Statistic tracking
VDouble0 m_statLoops; // Statistic tracking
VDouble0 m_statIters; // Statistic tracking
struct UnrollStats final {
class Stat final {
size_t m_value = 0; // Statistics value
const char* const m_name; // Name for stats file and UDEBUG
public:
Stat(const char* const name)
: m_name{name} {}
~Stat() { V3Stats::addStat("Optimizations, Loop unrolling, "s + m_name, m_value); }
const char* name() const { return m_name; }
Stat& operator++() {
++m_value;
return *this;
}
Stat& operator+=(size_t v) {
m_value += v;
return *this;
}
};
// STATE - statistics
Stat m_nFailCall{"Failed - non-inlined call"};
Stat m_nFailCondition{"Failed - unknown loop condition"};
Stat m_nFailFork{"Failed - contains fork"};
Stat m_nFailInfinite{"Failed - infinite loop"};
Stat m_nFailNestedLoopTest{"Failed - loop test in sub-statement"};
Stat m_nFailTimingControl{"Failed - contains timing control"};
Stat m_nFailUnrollCount{"Failed - reached --unroll-count"};
Stat m_nFailUnrollStmts{"Failed - reached --unroll-stmts"};
Stat m_nPragmaDisabled{"Pragma unroll_disable"};
Stat m_nUnrolledLoops{"Unrolled loops"};
Stat m_nUnrolledIters{"Unrolled iterations"};
};
//######################################################################
// Unroll one AstLoop
class UnrollOneVisitor final : VNVisitor {
// NODE STATE
// AstVarScope::user1p() AstConst: Value of this AstVarScope
const VNUser1InUse m_user1InUse;
// STATE
UnrollStats& m_stats; // Statistics tracking
AstLoop* const m_loopp; // The loop we are trying to unroll
AstNode* m_stmtsp = nullptr; // Resulting unrolled statement
size_t m_unrolledSize = 0; // Number of nodes in unrolled loop
// Temporary block needed for iteration of cloned statements
AstBegin* const m_wrapp = new AstBegin{m_loopp->fileline(), "[EditWrapper]", nullptr, false};
const bool m_unrollFull = m_loopp->unroll().isSetTrue(); // Completely unroll the loop?
bool m_ok = true; // Unrolling successful so far, gave up if false
// METHODS
void replaceVarRef(AstNode* bodyp, AstNode* varValuep) {
// Replace all occurances of loop variable in bodyp and next
bodyp->foreachAndNext([this, varValuep](AstVarRef* refp) {
if (refp->varp() == m_forVarp && refp->varScopep() == m_forVscp
&& refp->access().isReadOnly()) {
AstNode* const newconstp = varValuep->cloneTree(false);
refp->replaceWith(newconstp);
VL_DO_DANGLING(pushDeletep(refp), refp);
}
});
void cantUnroll(AstNode* nodep, UnrollStats::Stat& stat) {
m_ok = false;
++stat;
UINFO(4, " Can't Unroll: " << stat.name() << " :" << nodep);
}
bool cantUnroll(AstNode* nodep, const char* reason) const {
if (m_generate)
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Can't unroll generate for; " << reason);
UINFO(4, " Can't Unroll: " << reason << " :" << nodep);
// UINFOTREE(9, nodep, "", "cant");
V3Stats::addStatSum("Unrolling gave up, "s + reason, 1);
return false;
}
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* const nodep,
const VOptionBool& unrollFull, // Pragma unroll_full, unroll_disable
AstNode* const initp, // Maybe under nodep (no nextp), or standalone (ignore nextp)
AstNode* condp,
AstNode* const 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);
if (initp) UINFO(6, " Init " << initp);
if (condp) UINFO(6, " Cond " << condp);
if (incp) UINFO(6, " Inc " << incp);
if (unrollFull.isSetFalse()) return cantUnroll(nodep, "pragma unroll_disable");
// Initial value check
AstAssign* const initAssp = VN_CAST(initp, Assign);
if (!initAssp) return cantUnroll(nodep, "no initial assignment");
UASSERT_OBJ(!(initp->nextp() && initp->nextp() != nodep), nodep,
"initial assignment shouldn't be a list");
if (!VN_IS(initAssp->lhsp(), VarRef)) {
return cantUnroll(nodep, "no initial assignment to simple variable");
}
//
// Condition check
UASSERT_OBJ(!condp->nextp(), nodep, "conditional shouldn't be a list");
//
// Assignment of next value check
const AstAssign* const incAssp = VN_CAST(incp, Assign);
if (!incAssp) return cantUnroll(nodep, "no increment assignment");
if (incAssp->nextp()) return cantUnroll(nodep, "multiple increments");
m_forVarp = VN_AS(initAssp->lhsp(), VarRef)->varp();
m_forVscp = VN_AS(initAssp->lhsp(), VarRef)->varScopep();
if (VN_IS(nodep, GenFor) && !m_forVarp->isGenVar()) {
nodep->v3error("Non-genvar used in generate for: " << m_forVarp->prettyNameQ());
} else if (!VN_IS(nodep, GenFor) && m_forVarp->isGenVar()) {
// Likely impossible as V3LinkResolve will earlier throw bad genvar use error
nodep->v3error("Genvar not legal in non-generate for" // LCOV_EXCL_LINE
" (IEEE 1800-2023 27.4): "
<< m_forVarp->prettyNameQ());
}
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.
const AstConst* const constInitp = VN_CAST(initAssp->rhsp(), Const);
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_forkHit = false;
m_ignoreIncp = incp;
iterateAndNextNull(bodysp);
iterateAndNextNull(incp);
m_varModeCheck = false;
m_ignoreIncp = nullptr;
if (m_varAssignHit) return cantUnroll(nodep, "genvar assigned *inside* loop");
if (m_forkHit) return cantUnroll(nodep, "fork inside loop");
//
if (m_forVscp) {
UINFO(8, " Loop Variable: " << m_forVscp);
} else {
UINFO(8, " Loop Variable: " << m_forVarp);
}
UINFOTREE(9, nodep, "", "for");
if (!m_generate) {
const AstAssign* const incpAssign = VN_AS(incp, Assign);
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;
const int limit = v3Global.opt.unrollCountAdjusted(unrollFull, m_generate, false);
if (!countLoops(initAssp, condp, incp, limit, loops)) {
return cantUnroll(nodep, "Unable to simulate loop");
}
// Less than 10 statements in the body?
if (!unrollFull.isSetTrue()) {
int bodySize = 0;
int bodyLimit = v3Global.opt.unrollStmts();
if (loops > 0) bodyLimit = v3Global.opt.unrollStmts() / loops;
if (bodySizeOverRecurse(bodysp, bodySize /*ref*/, bodyLimit)
|| bodySizeOverRecurse(incp, bodySize /*ref*/, bodyLimit)) {
return cantUnroll(nodep, "too many statements");
}
}
}
// Finally, we can do it
if (!forUnroller(nodep, unrollFull, initAssp, condp, 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);
VL_DO_CLEAR(pushDeletep(clonep), clonep = nullptr);
return simvis.optimizable();
}
bool simulateTree(AstNode* nodep, const V3Number* loopValue, AstNode* dtypep,
V3Number& outNum) {
AstNode* clonep = nodep->cloneTree(true);
UASSERT_OBJ(clonep, nodep, "Failed to clone tree");
if (loopValue) {
AstConst* varValuep = new AstConst{nodep->fileline(), *loopValue};
// Iteration requires a back, so put under temporary node
AstBegin* tempp = new AstBegin{nodep->fileline(), "[EditWrapper]", clonep, false};
replaceVarRef(tempp->stmtsp(), varValuep);
clonep = tempp->stmtsp()->unlinkFrBackWithNext();
VL_DO_CLEAR(tempp->deleteTree(), tempp = nullptr);
VL_DO_DANGLING(pushDeletep(varValuep), varValuep);
}
SimulateVisitor simvis;
simvis.mainParamEmulate(clonep);
if (!simvis.optimizable()) {
UINFO(4, "Unable to simulate");
UINFOTREE(9, nodep, "", "_simtree");
VL_DO_DANGLING(clonep->deleteTree(), clonep);
return false;
}
// Fetch the result
V3Number* resp = simvis.fetchNumberNull(clonep);
if (!resp) {
UINFO(3, "No number returned from simulation");
VL_DO_DANGLING(clonep->deleteTree(), clonep);
return false;
}
// Patch up datatype
if (dtypep) {
AstConst new_con{clonep->fileline(), *resp};
new_con.dtypeFrom(dtypep);
outNum = new_con.num();
outNum.isSigned(dtypep->isSigned());
VL_DO_DANGLING(clonep->deleteTree(), clonep);
return true;
}
outNum = *resp;
VL_DO_DANGLING(clonep->deleteTree(), clonep);
return true;
}
bool countLoops(AstAssign* initp, AstNode* condp, AstNode* incp, int max, int& outLoopsr) {
outLoopsr = 0;
V3Number loopValue{initp};
if (!simulateTree(initp->rhsp(), nullptr, initp, loopValue)) { //
return false;
}
while (true) {
V3Number res{initp};
if (!simulateTree(condp, &loopValue, nullptr, res)) { //
return false;
}
if (!res.isEqOne()) break;
outLoopsr++;
// Run inc
AstAssign* const incpass = VN_AS(incp, Assign);
V3Number newLoopValue{initp};
if (!simulateTree(incpass->rhsp(), &loopValue, incpass, newLoopValue)) {
return false;
}
loopValue.opAssign(newLoopValue);
if (outLoopsr > max) return false;
}
return true;
}
bool forUnroller(AstNode* nodep, const VOptionBool& unrollFull, AstAssign* initp,
AstNode* condp, AstNode* incp, AstNode* bodysp) {
UINFO(9, "forUnroller " << nodep);
V3Number loopValue{nodep};
if (!simulateTree(initp->rhsp(), nullptr, initp, loopValue)) { //
return false;
}
AstNode* stmtsp = nullptr;
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 if we have > 1 loop, as we're constant propagating it
pushDeletep(initp); // Always cloned below.
}
if (bodysp) {
bodysp->unlinkFrBackWithNext();
stmtsp = AstNode::addNext(stmtsp, bodysp); // Maybe null if no body
}
if (incp && !VN_IS(nodep, GenFor)) { // Generates don't need to increment loop index
incp->unlinkFrBackWithNext();
stmtsp = AstNode::addNext(stmtsp, incp); // Maybe null if no body
}
// Mark variable to disable some later warnings
m_forVarp->usedLoopIdx(true);
++m_statLoops;
AstNode* newbodysp = nullptr;
if (initp && !m_generate) { // Set variable to initial value (may optimize away later)
AstNode* clonep = initp->cloneTree(true);
AstConst* varValuep = new AstConst{nodep->fileline(), loopValue};
// Iteration requires a back, so put under temporary node
AstBegin* tempp = new AstBegin{nodep->fileline(), "[EditWrapper]", clonep, false};
replaceVarRef(clonep, varValuep);
clonep = tempp->stmtsp()->unlinkFrBackWithNext();
VL_DO_CLEAR(tempp->deleteTree(), tempp = nullptr);
VL_DO_DANGLING(pushDeletep(varValuep), varValuep);
newbodysp = clonep;
}
if (stmtsp) {
pushDeletep(stmtsp); // Always cloned below.
int times = 0;
while (true) {
UINFO(8, " Looping " << loopValue);
V3Number res{nodep};
if (!simulateTree(condp, &loopValue, nullptr, res)) {
nodep->v3error("Loop unrolling failed.");
// Returns false if the loop terminated (or we gave up)
bool unrollOneIteration(AstNode* stmtsp) {
// True if the loop contains at least one dependent AstLoopTest
bool foundLoopTest = false;
// Process one body statement at a time
for (AstNode* stmtp = stmtsp; stmtp; stmtp = stmtp->nextp()) {
// Check if this is a loop test - before substitution
if (AstLoopTest* const testp = VN_CAST(stmtp, LoopTest)) {
AstNode* const condp = V3Const::constifyEdit(testp->condp());
if (condp->isZero()) {
// Loop terminates
return false;
} else if (condp->isNeqZero()) {
// Loop test is unconditionally true, ignore
continue;
}
if (!res.isEqOne()) {
break; // Done with the loop
} else {
// Replace iterator values with constant
AstNode* oneloopp = stmtsp->cloneTree(true);
AstConst* varValuep = new AstConst{nodep->fileline(), loopValue};
if (oneloopp) {
// Iteration requires a back, so put under temporary node
AstBegin* const tempp
= new AstBegin{oneloopp->fileline(), "[EditWrapper]", oneloopp, false};
replaceVarRef(tempp->stmtsp(), varValuep);
oneloopp = tempp->stmtsp()->unlinkFrBackWithNext();
VL_DO_DANGLING(tempp->deleteTree(), tempp);
}
if (m_generate) {
const string index = AstNode::encodeNumber(varValuep->toSInt());
const string nname = m_beginName + "__BRA__" + index + "__KET__";
oneloopp = new AstGenBlock{oneloopp->fileline(), nname, oneloopp, false};
}
VL_DO_DANGLING(pushDeletep(varValuep), varValuep);
if (newbodysp) {
newbodysp->addNext(oneloopp);
} else {
newbodysp = oneloopp;
}
}
++m_statIters;
const int limit
= v3Global.opt.unrollCountAdjusted(unrollFull, m_generate, false);
if (++times / 3 > limit) {
nodep->v3error(
"Loop unrolling took too long;"
" probably this is an infinite loop, "
" or use /*verilator unroll_full*/, or set --unroll-count above "
<< times);
break;
}
// Clone and iterate one body statement
m_wrapp->addStmtsp(stmtp->cloneTree(false));
iterateAndNextNull(m_wrapp->stmtsp());
// Give up if failed
if (!m_ok) return false;
// loopValue += valInc
AstAssign* const incpass = VN_AS(incp, Assign);
V3Number newLoopValue{nodep};
if (!simulateTree(incpass->rhsp(), &loopValue, incpass, newLoopValue)) {
nodep->v3error("Loop unrolling failed");
// Add statements to unrolled body
while (AstNode* const nodep = m_wrapp->stmtsp()) {
// Check if we reached the size limit, unless full unrolling is requested
if (!m_loopp->unroll().isSetTrue()) {
m_unrolledSize += nodep->nodeCount();
if (m_unrolledSize > static_cast<size_t>(v3Global.opt.unrollStmts())) {
cantUnroll(m_loopp, m_stats.m_nFailUnrollStmts);
return false;
}
loopValue.opAssign(newLoopValue);
}
// Will be adding to results (or deleting)
nodep->unlinkFrBack();
// If a LoopTest, check how it resolved
if (AstLoopTest* const testp = VN_CAST(nodep, LoopTest)) {
foundLoopTest = true;
// Will not actually need it, nor any subsequent
pushDeletep(testp);
// Loop continues - add rest of statements
if (testp->condp()->isNeqZero()) continue;
// Won't need any of the trailing statements
if (m_wrapp->stmtsp()) pushDeletep(m_wrapp->stmtsp()->unlinkFrBackWithNext());
// Loop terminates
if (testp->condp()->isZero()) return false;
// Loop condition unknown - cannot unroll
cantUnroll(testp->condp(), m_stats.m_nFailCondition);
return false;
}
// Add this statement to the result list
m_stmtsp = AstNode::addNext(m_stmtsp, nodep);
// Check if terminated via JumpGo
if (VN_IS(nodep, JumpGo)) {
UASSERT_OBJ(!m_wrapp->stmtsp(), nodep, "Statements after JumpGo");
// This JumpGo is going directly into the body of the unrolled loop.
// A JumpGo always redirects to the end of an enclosing JumpBlock,
// so this JumpGo must go outside the loop. The loop terminates.
return false;
}
}
}
if (!newbodysp) { // initp might have effects after the loop
if (m_generate && initp) { // GENFOR(ASSIGN(...)) need to move under a new Initial
newbodysp = new AstInitial{initp->fileline(), initp->cloneTree(true)};
} else {
newbodysp = initp ? initp->cloneTree(true) : nullptr;
}
// If there is no loop test in the body, give up, it's an infinite loop
if (!foundLoopTest) {
cantUnroll(m_loopp, m_stats.m_nFailInfinite);
return false;
}
// Replace the FOR()
if (newbodysp) {
nodep->replaceWith(newbodysp);
} else {
nodep->unlinkFrBack();
}
if (newbodysp) UINFOTREE(9, newbodysp, "", "_new");
// One iteration done, loop continues
return true;
}
// VISITORS
void visit(AstWhile* nodep) override {
iterateChildren(nodep);
if (!m_varModeCheck) {
// Constify before unroll call, as it may change what is underneath.
if (nodep->condp()) V3Const::constifyEdit(nodep->condp()); // condp may change
// Grab initial value
AstNode* initp = nullptr; // Should be statement before the while.
if (nodep->backp()->nextp() == nodep) initp = nodep->backp();
if (initp) VL_DO_DANGLING(V3Const::constifyEdit(initp), initp);
if (nodep->backp()->nextp() == nodep) initp = nodep->backp();
// Grab assignment
AstNode* incp = nullptr; // Should be last statement
AstNode* stmtsp = nodep->stmtsp();
if (nodep->incsp()) V3Const::constifyEdit(nodep->incsp());
// cppcheck-suppress duplicateCondition
if (nodep->incsp()) {
incp = nodep->incsp();
} else {
for (incp = nodep->stmtsp(); incp && incp->nextp(); incp = incp->nextp()) {}
if (incp) VL_DO_DANGLING(V3Const::constifyEdit(incp), incp);
// Again, as may have changed
stmtsp = nodep->stmtsp();
for (incp = nodep->stmtsp(); incp && incp->nextp(); incp = incp->nextp()) {}
if (incp == stmtsp) stmtsp = nullptr;
// Substitute all reads of bound variables with their value. If a write is
// encountered, remove the binding and don't substitute that variable.
// Returns false if we can't unroll
bool process(AstNode* nodep) {
UASSERT_OBJ(m_ok, nodep, "Should not call 'substituteCondVscp' if we gave up");
if (!nodep) return true;
// Variable references we should try to substitute
std::vector<AstVarRef*> toSubstitute;
// Iterate subtree
nodep->foreach([&](AstNode* np) {
// Failed earlier
if (!m_ok) return;
// Check for AstLoopTest
if (AstLoopTest* const testp = VN_CAST(np, LoopTest)) {
// Nested loop is OK, bail only if the nested LoopTest is for the current loop
if (testp->loopp() == m_loopp) cantUnroll(np, m_stats.m_nFailNestedLoopTest);
return;
}
// And check it
if (forUnrollCheck(nodep, nodep->unrollFull(), initp, nodep->condp(), incp, stmtsp)) {
VL_DO_DANGLING(pushDeletep(nodep), nodep); // Did replacement
}
}
}
void visit(AstGenFor* nodep) override {
UASSERT_OBJ(m_generate, nodep, "There should be no GenFor left when unrolling all");
if (!m_varModeCheck) {
// 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.
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
} else if (forUnrollCheck(nodep, VOptionBool{}, nodep->initsp(), nodep->condp(),
nodep->incsp(), nodep->itemsp())) {
VL_DO_DANGLING(pushDeletep(nodep), nodep); // Did replacement
} else {
nodep->v3error("For loop doesn't have genvar index, or is malformed");
// We will die, do it gracefully
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
// Check for AstFork - can't unroll
if (VN_IS(np, Fork)) {
cantUnroll(np, m_stats.m_nFailFork);
return;
}
// Check for calls - can't unroll if not pure might modify bindings
if (AstNodeCCall* const callp = VN_CAST(np, NodeCCall)) {
if (!callp->isPure()) {
cantUnroll(np, m_stats.m_nFailCall);
return;
}
}
// Check for timing control - can't unroll, might modify bindings
if (np->isTimingControl()) {
cantUnroll(np, m_stats.m_nFailTimingControl);
return;
}
// Process variable references
AstVarRef* const refp = VN_CAST(np, VarRef);
if (!refp) return;
// Ignore if the referenced variable has no binding
AstConst* const valp = VN_AS(refp->varScopep()->user1p(), Const);
if (!valp) return;
// If writen, remove the binding
if (refp->access().isWriteOrRW()) {
refp->varScopep()->user1p(nullptr);
return;
}
// Otherwise add it to the list of variables to substitute
toSubstitute.push_back(refp);
});
// Give up if we have to
if (!m_ok) return false;
// Actually substitute the variables that still have bindings
for (AstVarRef* const refp : toSubstitute) {
// Pick up bound value
AstConst* const valp = VN_AS(refp->varScopep()->user1p(), Const);
// Binding might have been removed after adding to 'toSubstitute'
if (!valp) continue;
// Substitute it
refp->replaceWith(valp->cloneTree(false));
VL_DO_DANGLING(pushDeletep(refp), refp);
}
return true;
}
void visit(AstVarRef* nodep) override {
if (m_varModeCheck && nodep->varp() == m_forVarp && nodep->varScopep() == m_forVscp
&& nodep->access().isWriteOrRW()) {
UINFO(8, " Itervar assigned to: " << nodep);
m_varAssignHit = true;
// CONSTRUCTOR
UnrollOneVisitor(UnrollStats& stats, AstLoop* loopp)
: m_stats{stats}
, m_loopp{loopp} {
UASSERT_OBJ(!loopp->contsp(), loopp, "'contsp' only used before LinkJump");
// Do not unroll if we are told not to
if (loopp->unroll().isSetFalse()) {
cantUnroll(loopp, m_stats.m_nPragmaDisabled);
return;
}
}
void visit(AstFork* nodep) override {
if (m_varModeCheck) {
if (nodep->joinType().joinNone() || nodep->joinType().joinAny()) {
// Forks are not allowed to unroll for loops, so we just set a flag
m_forkHit = true;
// Gather variable bindings from the preceding statements
for (AstNode *succp = loopp, *currp = loopp->backp(); currp->nextp() == succp;
succp = currp, currp = currp->backp()) {
AstAssign* const assignp = VN_CAST(currp, Assign);
if (!assignp) break;
AstConst* const valp = VN_CAST(V3Const::constifyEdit(assignp->rhsp()), Const);
if (!valp) break;
AstVarRef* const lhsp = VN_CAST(assignp->lhsp(), VarRef);
if (!lhsp) break;
// Don't bind if volatile
if (lhsp->varp()->isForced() || lhsp->varp()->isSigUserRWPublic()) continue;
// Don't overwrite a later binding
if (lhsp->varScopep()->user1p()) continue;
// Set up the binding
lhsp->varScopep()->user1p(valp);
}
// Attempt to unroll the loop
const size_t iterLimit = v3Global.opt.unrollCountAdjusted(loopp->unroll(), false, false);
size_t iterCount = 0;
do {
// Allow iterLimit + 1 iterations, which is consistent with the old behaviour
// where 'do' loops used to be unrolled at least once, and while/for loops are
// tested at the front on the last entry to the loop body
if (iterCount > iterLimit) {
cantUnroll(m_loopp, m_stats.m_nFailUnrollCount);
return;
}
} else {
iterateChildren(nodep);
++iterCount;
} while (unrollOneIteration(loopp->stmtsp()));
if (m_ok) {
++m_stats.m_nUnrolledLoops;
m_stats.m_nUnrolledIters += iterCount;
}
}
~UnrollOneVisitor() { VL_DO_DANGLING(m_wrapp->deleteTree(), m_wrapp); }
// VISIT - these are called for the statements directly in the loop body
void visit(AstNode* nodep) override {
if (m_varModeCheck && nodep == m_ignoreIncp) {
// Ignore subtree that is the increment
} else {
iterateChildren(nodep);
if (!m_ok) return;
// Generic body statement, just substitute
process(nodep);
}
void visit(AstLoopTest* nodep) override {
if (!m_ok) return;
// If the condition is a ExprStmt, move it before the LoopTest
if (AstExprStmt* const exprp = VN_CAST(nodep->condp(), ExprStmt)) {
AstNode* const stmtsp = exprp->stmtsp()->unlinkFrBackWithNext();
exprp->replaceWith(exprp->resultp()->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(exprp), exprp);
VNRelinker relinker;
nodep->unlinkFrBack(&relinker);
stmtsp->addNext(nodep);
relinker.relink(stmtsp);
return;
}
// Substitute the condition only, this is not a nested AstLoopTest
process(nodep->condp());
// Also simplify it, it will be checked later
V3Const::constifyEdit(nodep->condp());
}
void visit(AstAssign* nodep) override {
if (!m_ok) return;
// Can't do it if delayed
if (nodep->timingControlp()) {
cantUnroll(nodep, m_stats.m_nFailTimingControl);
return;
}
if (!process(nodep->rhsp())) return;
// If a simple variable assignment, update the binding
AstVarRef* const lhsp = VN_CAST(nodep->lhsp(), VarRef);
AstConst* const valp = VN_CAST(V3Const::constifyEdit(nodep->rhsp()), Const);
if (lhsp && valp && !lhsp->varp()->isForced() && !lhsp->varp()->isSigUserRWPublic()) {
lhsp->varScopep()->user1p(valp);
return;
}
// Otherwise just like a generic statement
process(nodep->lhsp());
}
void visit(AstIf* nodep) override {
if (!m_ok) return;
if (!process(nodep->condp())) return;
// If condition is constant, replce with the relevant branch
if (AstConst* const condp = VN_CAST(V3Const::constifyEdit(nodep->condp()), Const)) {
if (AstNode* const bodyp = condp->isNeqZero() ? nodep->thensp() : nodep->elsesp()) {
nodep->addNextHere(bodyp->unlinkFrBackWithNext()); // This will be iterated next
}
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return;
}
// Otherwise just like a generic statement
process(nodep);
}
void visit(AstJumpGo* nodep) override {
// Remove trailing dead code
if (nodep->nextp()) pushDeletep(nodep->nextp()->unlinkFrBackWithNext());
}
public:
// CONSTRUCTORS
UnrollVisitor() { init(false, ""); }
~UnrollVisitor() override {
V3Stats::addStatSum("Optimizations, Unrolled Loops", m_statLoops);
V3Stats::addStatSum("Optimizations, Unrolled Iterations", m_statIters);
}
// METHODS
void init(bool generate, const string& beginName) {
m_forVarp = nullptr;
m_forVscp = nullptr;
m_ignoreIncp = nullptr;
m_varModeCheck = false;
m_varAssignHit = false;
m_forkHit = false;
m_generate = generate;
m_beginName = beginName;
}
void process(AstNode* nodep, bool generate, const string& beginName) {
init(generate, beginName);
iterate(nodep);
// Unroll the given loop. Returns the resulting statements and the number of
// iterations unrolled (0 if unrolling failed);
static std::pair<AstNode*, bool> apply(UnrollStats& stats, AstLoop* loopp) {
UnrollOneVisitor visitor{stats, loopp};
// If successfully unrolled, return the resulting list of statements - might be empty
if (visitor.m_ok) return {visitor.m_stmtsp, true};
// Otherwise delete intermediate results
if (visitor.m_stmtsp) VL_DO_DANGLING(visitor.m_stmtsp->deleteTree(), visitor.m_stmtsp);
return {nullptr, false};
}
};
//######################################################################
// Unroll class functions
// Unroll all AstLoop statements
UnrollStateful::UnrollStateful()
: m_unrollerp{new UnrollVisitor} {}
UnrollStateful::~UnrollStateful() { delete m_unrollerp; }
class UnrollAllVisitor final : VNVisitor {
// STATE - Statistic tracking
UnrollStats m_stats;
void UnrollStateful::unrollGen(AstGenFor* nodep, const string& beginName) {
UINFO(5, __FUNCTION__ << ": ");
m_unrollerp->process(nodep, true, beginName);
}
// VISIT
void visit(AstLoop* nodep) override {
// Attempt to unroll this loop
const std::pair<AstNode*, bool> pair = UnrollOneVisitor::apply(m_stats, nodep);
// If failed, carry on with nested loop
if (!pair.second) {
iterateChildren(nodep);
return;
}
// Otherwise replace the loop with the unrolled code - might be empty
if (pair.first) {
nodep->replaceWith(pair.first);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else {
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
// Iteration continues with the unrolled body
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
// CONSTRUCTOR
UnrollAllVisitor(AstNetlist* netlistp) { iterate(netlistp); }
public:
static void apply(AstNetlist* netlistp) { UnrollAllVisitor{netlistp}; }
};
//######################################################################
// V3Unroll class functions
void V3Unroll::unrollAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ":");
{
UnrollVisitor visitor;
visitor.process(nodep, false, "");
} // Destruct before checking
UnrollAllVisitor::apply(nodep);
V3Global::dumpCheckGlobalTree("unroll", 0, dumpTreeEitherLevel() >= 3);
}

View File

@ -1,6 +1,6 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Pre C-Emit stage changes
// DESCRIPTION: Verilator: Loop unroller
//
// Code available from: https://verilator.org
//
@ -24,24 +24,24 @@
#include "V3Error.h"
//============================================================================
/// Unroller with saved state, so caller can determine when pushDelete's are executed.
// Generate loop unroller
class UnrollVisitor;
class UnrollGenVisitor;
class UnrollStateful final {
class GenForUnroller final {
// MEMBERS
UnrollVisitor* const m_unrollerp;
VL_UNCOPYABLE(UnrollStateful);
UnrollGenVisitor* const m_unrollerp;
public:
// CONSTRUCTORS
UnrollStateful() VL_MT_DISABLED;
~UnrollStateful() VL_MT_DISABLED;
// CONSTRUCTOR
GenForUnroller() VL_MT_DISABLED;
~GenForUnroller() VL_MT_DISABLED;
// METHODS
void unrollGen(AstGenFor* nodep, const string& beginName) VL_MT_DISABLED;
void unroll(AstGenFor* nodep, const std::string& beginName) VL_MT_DISABLED;
};
//============================================================================
// Loop statement unroller
class V3Unroll final {
public:

323
src/V3UnrollGen.cpp Normal file
View File

@ -0,0 +1,323 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Generate loop unrolling
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2025 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
//
//*************************************************************************
//
// Unroll AstLoopStmts
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Const.h"
#include "V3Simulate.h"
#include "V3Unroll.h"
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Unroll AstGenFor
class UnrollGenVisitor final : public VNVisitor {
// STATE - across all visitors
AstVar* m_forVarp = nullptr; // Iterator variable
const AstNode* m_ignoreIncp = nullptr; // Increment node to ignore
bool m_varModeCheck = false; // Just checking RHS assignments
bool m_varAssignHit = false; // Assign var hit
bool m_forkHit = false; // Fork hit
std::string m_beginName; // What name to give begin iterations
// METHODS
void replaceVarRef(AstNode* bodyp, AstNode* varValuep) {
// Replace all occurances of loop variable in bodyp and next
bodyp->foreachAndNext([this, varValuep](AstVarRef* refp) {
if (refp->varp() == m_forVarp && refp->access().isReadOnly()) {
AstNode* const newconstp = varValuep->cloneTree(false);
refp->replaceWith(newconstp);
VL_DO_DANGLING(pushDeletep(refp), refp);
}
});
}
bool cantUnroll(AstNode* nodep, const char* reason) const {
UINFO(4, " Can't Unroll: " << reason << " :" << nodep);
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Can't unroll generate for; " << reason);
return false;
}
bool forUnrollCheck(
AstGenFor* const nodep,
AstNode* const initp, // Maybe under nodep (no nextp), or standalone (ignore nextp)
AstNodeExpr* condp,
AstNode* const 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);
if (initp) UINFO(6, " Init " << initp);
if (condp) UINFO(6, " Cond " << condp);
if (incp) UINFO(6, " Inc " << incp);
// Initial value check
AstAssign* const initAssp = VN_CAST(initp, Assign);
if (!initAssp) return cantUnroll(nodep, "no initial assignment");
UASSERT_OBJ(!(initp->nextp() && initp->nextp() != nodep), nodep,
"initial assignment shouldn't be a list");
if (!VN_IS(initAssp->lhsp(), VarRef)) {
return cantUnroll(nodep, "no initial assignment to simple variable");
}
//
// Condition check
UASSERT_OBJ(!condp->nextp(), nodep, "conditional shouldn't be a list");
//
// Assignment of next value check
const AstAssign* const incAssp = VN_CAST(incp, Assign);
if (!incAssp) return cantUnroll(nodep, "no increment assignment");
if (incAssp->nextp()) return cantUnroll(nodep, "multiple increments");
m_forVarp = VN_AS(initAssp->lhsp(), VarRef)->varp();
if (!m_forVarp->isGenVar()) {
nodep->v3error("Non-genvar used in generate for: " << m_forVarp->prettyNameQ());
}
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.
const AstConst* const constInitp = VN_CAST(initAssp->rhsp(), Const);
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_forkHit = false;
m_ignoreIncp = incp;
iterateAndNextNull(bodysp);
iterateAndNextNull(incp);
m_varModeCheck = false;
m_ignoreIncp = nullptr;
if (m_varAssignHit) return cantUnroll(nodep, "genvar assigned *inside* loop");
if (m_forkHit) return cantUnroll(nodep, "fork inside loop");
//
UINFO(8, " Loop Variable: " << m_forVarp);
UINFOTREE(9, nodep, "", "for");
// Finally, we can do it
if (!forUnroller(nodep, initAssp, condp, incp, bodysp)) {
return cantUnroll(nodep, "Unable to unroll loop");
}
VL_DANGLING(nodep);
// Cleanup
return true;
}
bool simulateTree(AstNodeExpr* nodep, const V3Number* loopValue, AstNode* dtypep,
V3Number& outNum) {
AstNode* clonep = nodep->cloneTree(true);
UASSERT_OBJ(clonep, nodep, "Failed to clone tree");
if (loopValue) {
AstConst* varValuep = new AstConst{nodep->fileline(), *loopValue};
// Iteration requires a back, so put under temporary node
AstBegin* tempp = new AstBegin{nodep->fileline(), "[EditWrapper]", clonep, false};
replaceVarRef(tempp->stmtsp(), varValuep);
clonep = tempp->stmtsp()->unlinkFrBackWithNext();
VL_DO_CLEAR(tempp->deleteTree(), tempp = nullptr);
VL_DO_DANGLING(pushDeletep(varValuep), varValuep);
}
SimulateVisitor simvis;
simvis.mainParamEmulate(clonep);
if (!simvis.optimizable()) {
UINFO(4, "Unable to simulate");
UINFOTREE(9, nodep, "", "_simtree");
VL_DO_DANGLING(clonep->deleteTree(), clonep);
return false;
}
// Fetch the result
V3Number* resp = simvis.fetchNumberNull(clonep);
if (!resp) {
UINFO(3, "No number returned from simulation");
VL_DO_DANGLING(clonep->deleteTree(), clonep);
return false;
}
// Patch up datatype
if (dtypep) {
AstConst new_con{clonep->fileline(), *resp};
new_con.dtypeFrom(dtypep);
outNum = new_con.num();
outNum.isSigned(dtypep->isSigned());
VL_DO_DANGLING(clonep->deleteTree(), clonep);
return true;
}
outNum = *resp;
VL_DO_DANGLING(clonep->deleteTree(), clonep);
return true;
}
bool forUnroller(AstGenFor* nodep, AstAssign* initp, AstNodeExpr* condp, AstNode* incp,
AstNode* bodysp) {
UINFO(9, "forUnroller " << nodep);
V3Number loopValue{nodep};
if (!simulateTree(initp->rhsp(), nullptr, initp, loopValue)) { //
return false;
}
AstNode* stmtsp = nullptr;
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 if we have > 1 loop, as we're constant propagating it
pushDeletep(initp); // Always cloned below.
}
if (bodysp) {
bodysp->unlinkFrBackWithNext();
stmtsp = AstNode::addNext(stmtsp, bodysp); // Maybe null if no body
}
// Mark variable to disable some later warnings
m_forVarp->usedLoopIdx(true);
AstNode* newbodysp = nullptr;
if (stmtsp) {
pushDeletep(stmtsp); // Always cloned below.
int times = 0;
while (true) {
UINFO(8, " Looping " << loopValue);
V3Number res{nodep};
if (!simulateTree(condp, &loopValue, nullptr, 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);
AstConst* varValuep = new AstConst{nodep->fileline(), loopValue};
if (oneloopp) {
// Iteration requires a back, so put under temporary node
AstBegin* const tempp
= new AstBegin{oneloopp->fileline(), "[EditWrapper]", oneloopp, false};
replaceVarRef(tempp->stmtsp(), varValuep);
oneloopp = tempp->stmtsp()->unlinkFrBackWithNext();
VL_DO_DANGLING(tempp->deleteTree(), tempp);
}
const string index = AstNode::encodeNumber(varValuep->toSInt());
const string nname = m_beginName + "__BRA__" + index + "__KET__";
oneloopp = new AstGenBlock{oneloopp->fileline(), nname, oneloopp, false};
VL_DO_DANGLING(pushDeletep(varValuep), varValuep);
if (newbodysp) {
newbodysp->addNext(oneloopp);
} else {
newbodysp = oneloopp;
}
const int limit = v3Global.opt.unrollCountAdjusted(VOptionBool{}, true, false);
if (++times / 3 > limit) {
nodep->v3error(
"Loop unrolling took too long;"
" probably this is an infinite loop, "
" or use /*verilator unroll_full*/, or set --unroll-count above "
<< times);
break;
}
// loopValue += valInc
AstAssign* const incpass = VN_AS(incp, Assign);
V3Number newLoopValue{nodep};
if (!simulateTree(incpass->rhsp(), &loopValue, incpass, newLoopValue)) {
nodep->v3error("Loop unrolling failed");
return false;
}
loopValue.opAssign(newLoopValue);
}
}
}
if (!newbodysp) { // initp might have effects after the loop
if (initp) { // GENFOR(ASSIGN(...)) need to move under a new Initial
newbodysp = new AstInitial{initp->fileline(), initp->cloneTree(true)};
} else {
newbodysp = initp ? initp->cloneTree(true) : nullptr;
}
}
// Replace the FOR()
if (newbodysp) {
nodep->replaceWith(newbodysp);
} else {
nodep->unlinkFrBack();
}
if (newbodysp) UINFOTREE(9, newbodysp, "", "_new");
return true;
}
// VISITORS
void visit(AstVarRef* nodep) override {
if (m_varModeCheck && nodep->varp() == m_forVarp && nodep->access().isWriteOrRW()) {
UINFO(8, " Itervar assigned to: " << nodep);
m_varAssignHit = true;
}
}
void visit(AstNode* nodep) override {
if (m_varModeCheck && nodep == m_ignoreIncp) {
// Ignore subtree that is the increment
} else {
iterateChildren(nodep);
}
}
public:
// CONSTRUCTORS
UnrollGenVisitor() = default;
void unroll(AstGenFor* nodep, const std::string& beginName) {
m_forVarp = nullptr; // Iterator variable
m_ignoreIncp = nullptr; // Increment node to ignore
m_varModeCheck = false; // Just checking RHS assignments
m_varAssignHit = false; // Assign var hit
m_forkHit = false; // Fork hit
m_beginName = beginName;
// 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.
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
} else if (forUnrollCheck(nodep, nodep->initsp(), nodep->condp(), nodep->incsp(),
nodep->itemsp())) {
VL_DO_DANGLING(pushDeletep(nodep), nodep); // Did replacement
} else {
nodep->v3error("For loop doesn't have genvar index, or is malformed");
// We will die, do it gracefully
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
}
}
};
//######################################################################
// GenForUnroller members
GenForUnroller::GenForUnroller()
: m_unrollerp{new UnrollGenVisitor{}} {}
GenForUnroller::~GenForUnroller() { VL_DO_DANGLING(delete m_unrollerp, m_unrollerp); }
void GenForUnroller::unroll(AstGenFor* nodep, const std::string& beginName) {
m_unrollerp->unroll(nodep, beginName);
}

View File

@ -5466,18 +5466,20 @@ class WidthVisitor final : public VNVisitor {
}
}
void visit(AstLoop* nodep) override {
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
assertAtStatement(nodep);
userIterateAndNext(nodep->stmtsp(), nullptr);
}
void visit(AstLoopTest* nodep) override {
assertAtStatement(nodep);
iterateCheckBool(nodep, "Loop Condition", nodep->condp(), BOTH);
}
void visit(AstRepeat* nodep) override {
assertAtStatement(nodep);
userIterateAndNext(nodep->countp(), WidthVP{SELF, BOTH}.p());
userIterateAndNext(nodep->stmtsp(), nullptr);
}
void visit(AstWhile* nodep) override {
assertAtStatement(nodep);
iterateCheckBool(nodep, "For Test Condition", nodep->condp(),
BOTH); // it's like an if() condition.
userIterateAndNext(nodep->stmtsp(), nullptr);
userIterateAndNext(nodep->incsp(), nullptr);
}
void visit(AstNodeIf* nodep) override {
assertAtStatement(nodep);
// UINFOTREE(1, nodep, "", "IfPre");

View File

@ -372,7 +372,7 @@ static void process() {
// After V3Task so task internal variables will get renamed
V3Name::nameAll(v3Global.rootp());
// Loop unrolling & convert FORs to WHILEs
// Loop unrolling
V3Unroll::unrollAll(v3Global.rootp());
// Expand slices of arrays

View File

@ -3590,14 +3590,31 @@ statement_item<nodep>: // IEEE: statement_item
| yP_MINUSGTGT delay_or_event_controlE expr ';'
{ $$ = new AstFireEvent{$1, $3, true}; }
//
// // IEEE: loop_statement
// do/for/forever/while loops all modelled as AstLoop
| yDO stmtBlock yWHILE '(' expr ')' ';'
{ AstLoop* const loopp = new AstLoop{$1};
loopp->addStmtsp($2);
loopp->addContsp(new AstLoopTest{$<fl>5, loopp, $5});
$$ = loopp; }
| yFOR '(' { VARRESET_NONLIST(UNKNOWN); } for_initializationE ';' exprE ';' for_stepE ')' stmtBlock
{ AstBegin* const blockp = new AstBegin{$1, "", $4, true};
AstLoop* const loopp = new AstLoop{$1};
if ($6) loopp->addStmtsp(new AstLoopTest{$<fl>6, loopp, $6});
loopp->addStmtsp($10);
loopp->addContsp($8);
blockp->addStmtsp(loopp);
$$ = blockp; }
| yFOREVER stmtBlock
{ $$ = new AstWhile{$1, new AstConst{$1, AstConst::BitTrue{}}, $2}; }
{ AstLoop* const loopp = new AstLoop{$1};
loopp->addStmtsp($2);
$$ = loopp; }
| yWHILE '(' expr ')' stmtBlock
{ AstLoop* const loopp = new AstLoop{$1};
loopp->addStmtsp(new AstLoopTest{$<fl>3, loopp, $3});
loopp->addStmtsp($5);
$$ = loopp; }
// Other loop statements
| yREPEAT '(' expr ')' stmtBlock { $$ = new AstRepeat{$1, $3, $5}; }
| yWHILE '(' expr ')' stmtBlock { $$ = new AstWhile{$1, $3, $5}; }
// // for's first ';' is in for_initialization
| statementFor { $$ = $1; }
| yDO stmtBlock yWHILE '(' expr ')' ';' { $$ = new AstDoWhile{$1, $5, $2}; }
// // IEEE says array_identifier here, but dotted accepted in VMM and 1800-2009
| yFOREACH '(' idClassSelForeach ')' stmtBlock
{ $$ = new AstBegin{$1, "", new AstForeach{$1, $3, $5}, true}; }
@ -3660,18 +3677,6 @@ statement_item<nodep>: // IEEE: statement_item
{ $$ = nullptr; BBUNSUP($1, "Unsupported: expect"); DEL($3, $6); }
;
statementFor<beginp>: // IEEE: part of statement
yFOR beginForParen for_initialization expr ';' for_stepE ')' stmtBlock
{ $$ = new AstBegin{$1, "", $3, true};
$$->addStmtsp(new AstWhile{$1, $4, $8, $6}); }
| yFOR beginForParen for_initialization ';' for_stepE ')' stmtBlock
{ $$ = new AstBegin{$1, "", $3, true};
$$->addStmtsp(new AstWhile{$1, new AstConst{$1, AstConst::BitTrue{}}, $7, $5}); }
;
beginForParen: // IEEE: Part of statement (for loop beginning paren)
'(' { VARRESET_NONLIST(UNKNOWN); }
;
statementVerilatorPragmas<nodep>:
yVL_COVERAGE_BLOCK_OFF
{ $$ = new AstPragma{$1, VPragmaType::COVERAGE_BLOCK_OFF}; }
@ -3948,11 +3953,9 @@ assignment_pattern<patternp>: // ==IEEE: assignment_pattern
;
// "datatype id = x {, id = x }" | "yaId = x {, id=x}" is legal
for_initialization<nodep>: // ==IEEE: for_initialization + for_variable_declaration + extra terminating ";"
// // IEEE: for_variable_declaration
for_initializationItemList ';' { $$ = $1; }
// // IEEE: 1800-2017 empty initialization
| ';' { $$ = nullptr; }
for_initializationE<nodep>: // ==IEEE: for_initialization + for_variable_declaration
/* empty */ { $$ = nullptr; }
| for_initializationItemList { $$ = $1; }
;
for_initializationItemList<nodep>: // IEEE: [for_variable_declaration...]

View File

@ -148,12 +148,12 @@
-000000 point: comment=block hier=top.t
~000010 do begin
-000000 point: comment=block hier=top.t
+000010 point: comment=if hier=top.t
~000010 $write("");
-000000 point: comment=block hier=top.t
+000010 point: comment=if hier=top.t
%000000 end while (0);
+000010 point: comment=block hier=top.t
000010 $write("");
+000010 point: comment=block hier=top.t
~000010 end while (0);
-000000 point: comment=block hier=top.t
+000010 point: comment=block hier=top.t
//===
// Task and complicated
%000001 if (cyc==3) begin
@ -522,6 +522,8 @@
end
000044 for (int i = 0; i < 7; i = (i > 4) ? i + 1 : i + 2) begin
+000011 point: comment=block hier=top.t.cond1
+000011 point: comment=cond_then hier=top.t.cond1
+000033 point: comment=cond_else hier=top.t.cond1
+000044 point: comment=block hier=top.t.cond1
000044 k = 1'(i);
+000044 point: comment=block hier=top.t.cond1

View File

@ -55,9 +55,9 @@ DA:105,10
BRDA:105,0,0,0
BRDA:105,0,1,10
DA:106,10
BRDA:106,0,0,0
BRDA:106,0,1,10
DA:107,0
DA:107,10
BRDA:107,0,0,0
BRDA:107,0,1,10
DA:110,1
DA:111,1
DA:113,1
@ -200,12 +200,14 @@ BRDA:354,0,1,55
DA:355,55
DA:357,44
BRDA:357,0,0,11
BRDA:357,0,1,44
BRDA:357,0,1,11
BRDA:357,0,2,33
BRDA:357,0,3,44
DA:358,44
DA:361,11
BRDA:361,0,0,0
BRDA:361,0,1,11
DA:362,11
BRF:77
BRH:30
BRF:79
BRH:32
end_of_record

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@
| ^~~~~~~~~~~~~~
%Error: t/t_func_const_bad.v:36:20: Expecting expression to be constant, but can't determine constant for FUNCREF 'f_bad_infinite'
: ... note: In instance 't'
t/t_func_const_bad.v:38:7: ... Location of non-constant WHILE: Loop unrolling took too long; probably this is aninfinite loop, or use /*verilator unroll_full*/, or set --unroll-count above 16386
t/t_func_const_bad.v:38:7: ... Location of non-constant LOOP: Loop unrolling took too long; probably this is aninfinite loop, or use /*verilator unroll_full*/, or set --unroll-count above 16386
t/t_func_const_bad.v:36:20: ... Called from 'f_bad_infinite()' with parameters:
a = ?32?h3
36 | localparam B4 = f_bad_infinite(3);

View File

@ -5,10 +5,7 @@
%Error-UNSUPPORTED: t/t_interface_virtual_unsup.v:23:25: Unsupported: write to virtual interface in loop condition
23 | while (write_data(vif.data));
| ^~~
%Error-UNSUPPORTED: t/t_interface_virtual_unsup.v:24:34: Unsupported: write to virtual interface in loop condition
24 | for (int i = 0; write_data(vif.data); i += int'(write_data(vif.data)));
| ^~~
%Error-UNSUPPORTED: t/t_interface_virtual_unsup.v:24:66: Unsupported: write to virtual interface in loop increment statement
24 | for (int i = 0; write_data(vif.data); i += int'(write_data(vif.data)));
| ^~~
%Error-UNSUPPORTED: t/t_interface_virtual_unsup.v:24:30: Unsupported: write to virtual interface in loop condition
24 | do ; while (write_data(vif.data));
| ^~~
%Error: Exiting due to

View File

@ -21,7 +21,7 @@ module t;
initial begin
if (write_data(vif.data)) $write("dummy op");
while (write_data(vif.data));
for (int i = 0; write_data(vif.data); i += int'(write_data(vif.data)));
do ; while (write_data(vif.data));
for (int i = 0; write_data(vif.data++); i++);
end

File diff suppressed because it is too large Load Diff

View File

@ -85,199 +85,197 @@
"lhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__i","addr":"(OC)","loc":"d,18:10,18:11","dtypep":"(SB)","access":"WR","varp":"(TB)","varScopep":"(RB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"WHILE","name":"","addr":"(PC)","loc":"d,18:5,18:8",
"condp": [
{"type":"GTS","name":"","addr":"(QC)","loc":"d,18:18,18:19","dtypep":"(RC)",
"lhsp": [
{"type":"CONST","name":"32'sh7","addr":"(SC)","loc":"d,18:20,18:21","dtypep":"(NC)"}
],
"rhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__i","addr":"(TC)","loc":"d,18:16,18:17","dtypep":"(SB)","access":"RD","varp":"(TB)","varScopep":"(RB)","classOrPackagep":"UNLINKED"}
]}
],
{"type":"LOOP","name":"","addr":"(PC)","loc":"d,18:5,18:8","unroll":"default",
"stmtsp": [
{"type":"ASSIGN","name":"","addr":"(UC)","loc":"d,19:14,19:15","dtypep":"(RC)",
"rhsp": [
{"type":"EQ","name":"","addr":"(VC)","loc":"d,19:31,19:33","dtypep":"(RC)",
{"type":"LOOPTEST","name":"","addr":"(QC)","loc":"d,18:16,18:17",
"condp": [
{"type":"GTS","name":"","addr":"(RC)","loc":"d,18:18,18:19","dtypep":"(SC)",
"lhsp": [
{"type":"CONST","name":"2'h0","addr":"(WC)","loc":"d,19:34,19:39","dtypep":"(XC)"}
{"type":"CONST","name":"32'sh7","addr":"(TC)","loc":"d,18:20,18:21","dtypep":"(NC)"}
],
"rhsp": [
{"type":"SEL","name":"","addr":"(YC)","loc":"d,19:20,19:21","dtypep":"(XC)","widthConst":2,"declRange":"[15:0]","declElWidth":1,
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__i","addr":"(UC)","loc":"d,18:16,18:17","dtypep":"(SB)","access":"RD","varp":"(TB)","varScopep":"(RB)","classOrPackagep":"UNLINKED"}
]}
]},
{"type":"ASSIGN","name":"","addr":"(VC)","loc":"d,19:14,19:15","dtypep":"(SC)",
"rhsp": [
{"type":"EQ","name":"","addr":"(WC)","loc":"d,19:31,19:33","dtypep":"(SC)",
"lhsp": [
{"type":"CONST","name":"2'h0","addr":"(XC)","loc":"d,19:34,19:39","dtypep":"(YC)"}
],
"rhsp": [
{"type":"SEL","name":"","addr":"(ZC)","loc":"d,19:20,19:21","dtypep":"(YC)","widthConst":2,"declRange":"[15:0]","declElWidth":1,
"fromp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__val","addr":"(ZC)","loc":"d,19:17,19:20","dtypep":"(H)","access":"RD","varp":"(OB)","varScopep":"(NB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__val","addr":"(AD)","loc":"d,19:17,19:20","dtypep":"(H)","access":"RD","varp":"(OB)","varScopep":"(NB)","classOrPackagep":"UNLINKED"}
],
"lsbp": [
{"type":"SEL","name":"","addr":"(AD)","loc":"d,19:22,19:23","dtypep":"(BD)","widthConst":4,
{"type":"SEL","name":"","addr":"(BD)","loc":"d,19:22,19:23","dtypep":"(CD)","widthConst":4,
"fromp": [
{"type":"MULS","name":"","addr":"(CD)","loc":"d,19:22,19:23","dtypep":"(NC)",
{"type":"MULS","name":"","addr":"(DD)","loc":"d,19:22,19:23","dtypep":"(NC)",
"lhsp": [
{"type":"CONST","name":"32'sh2","addr":"(DD)","loc":"d,19:23,19:24","dtypep":"(NC)"}
{"type":"CONST","name":"32'sh2","addr":"(ED)","loc":"d,19:23,19:24","dtypep":"(NC)"}
],
"rhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__i","addr":"(ED)","loc":"d,19:21,19:22","dtypep":"(SB)","access":"RD","varp":"(TB)","varScopep":"(RB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__i","addr":"(FD)","loc":"d,19:21,19:22","dtypep":"(SB)","access":"RD","varp":"(TB)","varScopep":"(RB)","classOrPackagep":"UNLINKED"}
]}
],
"lsbp": [
{"type":"CONST","name":"32'h0","addr":"(FD)","loc":"d,19:22,19:23","dtypep":"(GD)"}
{"type":"CONST","name":"32'h0","addr":"(GD)","loc":"d,19:22,19:23","dtypep":"(HD)"}
]}
]}
]}
],
"lhsp": [
{"type":"SEL","name":"","addr":"(HD)","loc":"d,19:10,19:11","dtypep":"(RC)","widthConst":1,"declRange":"[6:0]","declElWidth":1,
{"type":"SEL","name":"","addr":"(ID)","loc":"d,19:10,19:11","dtypep":"(SC)","widthConst":1,"declRange":"[6:0]","declElWidth":1,
"fromp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__ret","addr":"(ID)","loc":"d,19:7,19:10","dtypep":"(K)","access":"WR","varp":"(QB)","varScopep":"(PB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__ret","addr":"(JD)","loc":"d,19:7,19:10","dtypep":"(K)","access":"WR","varp":"(QB)","varScopep":"(PB)","classOrPackagep":"UNLINKED"}
],
"lsbp": [
{"type":"SEL","name":"","addr":"(JD)","loc":"d,19:11,19:12","dtypep":"(KD)","widthConst":3,
{"type":"SEL","name":"","addr":"(KD)","loc":"d,19:11,19:12","dtypep":"(LD)","widthConst":3,
"fromp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__i","addr":"(LD)","loc":"d,19:11,19:12","dtypep":"(SB)","access":"RD","varp":"(TB)","varScopep":"(RB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__i","addr":"(MD)","loc":"d,19:11,19:12","dtypep":"(SB)","access":"RD","varp":"(TB)","varScopep":"(RB)","classOrPackagep":"UNLINKED"}
],
"lsbp": [
{"type":"CONST","name":"32'h0","addr":"(MD)","loc":"d,19:11,19:12","dtypep":"(GD)"}
{"type":"CONST","name":"32'h0","addr":"(ND)","loc":"d,19:11,19:12","dtypep":"(HD)"}
]}
]}
],"timingControlp": []}
],
"incsp": [
{"type":"ASSIGN","name":"","addr":"(ND)","loc":"d,18:24,18:26","dtypep":"(SB)",
],"timingControlp": []},
{"type":"ASSIGN","name":"","addr":"(OD)","loc":"d,18:24,18:26","dtypep":"(SB)",
"rhsp": [
{"type":"ADD","name":"","addr":"(OD)","loc":"d,18:24,18:26","dtypep":"(GD)",
{"type":"ADD","name":"","addr":"(PD)","loc":"d,18:24,18:26","dtypep":"(HD)",
"lhsp": [
{"type":"CONST","name":"32'h1","addr":"(PD)","loc":"d,18:24,18:26","dtypep":"(GD)"}
{"type":"CONST","name":"32'h1","addr":"(QD)","loc":"d,18:24,18:26","dtypep":"(HD)"}
],
"rhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__i","addr":"(QD)","loc":"d,18:23,18:24","dtypep":"(SB)","access":"RD","varp":"(TB)","varScopep":"(RB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__i","addr":"(RD)","loc":"d,18:23,18:24","dtypep":"(SB)","access":"RD","varp":"(TB)","varScopep":"(RB)","classOrPackagep":"UNLINKED"}
]}
],
"lhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__i","addr":"(RD)","loc":"d,18:23,18:24","dtypep":"(SB)","access":"WR","varp":"(TB)","varScopep":"(RB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__i","addr":"(SD)","loc":"d,18:23,18:24","dtypep":"(SB)","access":"WR","varp":"(TB)","varScopep":"(RB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []}
]},
{"type":"ASSIGN","name":"","addr":"(SD)","loc":"d,21:5,21:11","dtypep":"(K)",
],"contsp": []},
{"type":"ASSIGN","name":"","addr":"(TD)","loc":"d,21:5,21:11","dtypep":"(K)",
"rhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__ret","addr":"(TD)","loc":"d,21:12,21:15","dtypep":"(K)","access":"RD","varp":"(QB)","varScopep":"(PB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__ret","addr":"(UD)","loc":"d,21:12,21:15","dtypep":"(K)","access":"RD","varp":"(QB)","varScopep":"(PB)","classOrPackagep":"UNLINKED"}
],
"lhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__Vfuncout","addr":"(UD)","loc":"d,21:5,21:11","dtypep":"(K)","access":"WR","varp":"(MB)","varScopep":"(LB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__Vfuncout","addr":"(VD)","loc":"d,21:5,21:11","dtypep":"(K)","access":"WR","varp":"(MB)","varScopep":"(LB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"ASSIGN","name":"","addr":"(VD)","loc":"d,24:14,24:15","dtypep":"(K)",
{"type":"ASSIGN","name":"","addr":"(WD)","loc":"d,24:14,24:15","dtypep":"(K)",
"rhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__Vfuncout","addr":"(WD)","loc":"d,24:16,24:19","dtypep":"(K)","access":"RD","varp":"(MB)","varScopep":"(LB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__0__Vfuncout","addr":"(XD)","loc":"d,24:16,24:19","dtypep":"(K)","access":"RD","varp":"(MB)","varScopep":"(LB)","classOrPackagep":"UNLINKED"}
],
"lhsp": [
{"type":"VARREF","name":"o_a","addr":"(XD)","loc":"d,24:10,24:13","dtypep":"(K)","access":"WR","varp":"(J)","varScopep":"(T)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"o_a","addr":"(YD)","loc":"d,24:10,24:13","dtypep":"(K)","access":"WR","varp":"(J)","varScopep":"(T)","classOrPackagep":"UNLINKED"}
],"timingControlp": []}
]},
{"type":"ALWAYS","name":"","addr":"(YD)","loc":"d,25:14,25:15","keyword":"always","isSuspendable":false,"needProcess":false,"sentreep": [],
{"type":"ALWAYS","name":"","addr":"(ZD)","loc":"d,25:14,25:15","keyword":"always","isSuspendable":false,"needProcess":false,"sentreep": [],
"stmtsp": [
{"type":"COMMENT","name":"Function: foo","addr":"(ZD)","loc":"d,25:16,25:19"},
{"type":"ASSIGN","name":"","addr":"(AE)","loc":"d,25:20,25:23","dtypep":"(H)",
{"type":"COMMENT","name":"Function: foo","addr":"(AE)","loc":"d,25:16,25:19"},
{"type":"ASSIGN","name":"","addr":"(BE)","loc":"d,25:20,25:23","dtypep":"(H)",
"rhsp": [
{"type":"VARREF","name":"i_b","addr":"(BE)","loc":"d,25:20,25:23","dtypep":"(H)","access":"RD","varp":"(I)","varScopep":"(S)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"i_b","addr":"(CE)","loc":"d,25:20,25:23","dtypep":"(H)","access":"RD","varp":"(I)","varScopep":"(S)","classOrPackagep":"UNLINKED"}
],
"lhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__val","addr":"(CE)","loc":"d,15:57,15:60","dtypep":"(H)","access":"WR","varp":"(XB)","varScopep":"(WB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__val","addr":"(DE)","loc":"d,15:57,15:60","dtypep":"(H)","access":"WR","varp":"(XB)","varScopep":"(WB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"CRESET","name":"","addr":"(DE)","loc":"d,16:17,16:20","constructing":false,
{"type":"CRESET","name":"","addr":"(EE)","loc":"d,16:17,16:20","constructing":false,
"varrefp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__ret","addr":"(EE)","loc":"d,16:17,16:20","dtypep":"(K)","access":"WR","varp":"(ZB)","varScopep":"(YB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__ret","addr":"(FE)","loc":"d,16:17,16:20","dtypep":"(K)","access":"WR","varp":"(ZB)","varScopep":"(YB)","classOrPackagep":"UNLINKED"}
]},
{"type":"CRESET","name":"","addr":"(FE)","loc":"d,17:13,17:14","constructing":false,
{"type":"CRESET","name":"","addr":"(GE)","loc":"d,17:13,17:14","constructing":false,
"varrefp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(GE)","loc":"d,17:13,17:14","dtypep":"(SB)","access":"WR","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(HE)","loc":"d,17:13,17:14","dtypep":"(SB)","access":"WR","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
]},
{"type":"ASSIGN","name":"","addr":"(HE)","loc":"d,18:11,18:12","dtypep":"(SB)",
{"type":"ASSIGN","name":"","addr":"(IE)","loc":"d,18:11,18:12","dtypep":"(SB)",
"rhsp": [
{"type":"CONST","name":"32'sh0","addr":"(IE)","loc":"d,18:12,18:13","dtypep":"(NC)"}
{"type":"CONST","name":"32'sh0","addr":"(JE)","loc":"d,18:12,18:13","dtypep":"(NC)"}
],
"lhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(JE)","loc":"d,18:10,18:11","dtypep":"(SB)","access":"WR","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(KE)","loc":"d,18:10,18:11","dtypep":"(SB)","access":"WR","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"WHILE","name":"","addr":"(KE)","loc":"d,18:5,18:8",
"condp": [
{"type":"GTS","name":"","addr":"(LE)","loc":"d,18:18,18:19","dtypep":"(RC)",
"lhsp": [
{"type":"CONST","name":"32'sh7","addr":"(ME)","loc":"d,18:20,18:21","dtypep":"(NC)"}
],
"rhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(NE)","loc":"d,18:16,18:17","dtypep":"(SB)","access":"RD","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
]}
],
{"type":"LOOP","name":"","addr":"(LE)","loc":"d,18:5,18:8","unroll":"default",
"stmtsp": [
{"type":"ASSIGN","name":"","addr":"(OE)","loc":"d,19:14,19:15","dtypep":"(RC)",
"rhsp": [
{"type":"EQ","name":"","addr":"(PE)","loc":"d,19:31,19:33","dtypep":"(RC)",
{"type":"LOOPTEST","name":"","addr":"(ME)","loc":"d,18:16,18:17",
"condp": [
{"type":"GTS","name":"","addr":"(NE)","loc":"d,18:18,18:19","dtypep":"(SC)",
"lhsp": [
{"type":"CONST","name":"2'h0","addr":"(QE)","loc":"d,19:34,19:39","dtypep":"(XC)"}
{"type":"CONST","name":"32'sh7","addr":"(OE)","loc":"d,18:20,18:21","dtypep":"(NC)"}
],
"rhsp": [
{"type":"SEL","name":"","addr":"(RE)","loc":"d,19:20,19:21","dtypep":"(XC)","widthConst":2,"declRange":"[15:0]","declElWidth":1,
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(PE)","loc":"d,18:16,18:17","dtypep":"(SB)","access":"RD","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
]}
]},
{"type":"ASSIGN","name":"","addr":"(QE)","loc":"d,19:14,19:15","dtypep":"(SC)",
"rhsp": [
{"type":"EQ","name":"","addr":"(RE)","loc":"d,19:31,19:33","dtypep":"(SC)",
"lhsp": [
{"type":"CONST","name":"2'h0","addr":"(SE)","loc":"d,19:34,19:39","dtypep":"(YC)"}
],
"rhsp": [
{"type":"SEL","name":"","addr":"(TE)","loc":"d,19:20,19:21","dtypep":"(YC)","widthConst":2,"declRange":"[15:0]","declElWidth":1,
"fromp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__val","addr":"(SE)","loc":"d,19:17,19:20","dtypep":"(H)","access":"RD","varp":"(XB)","varScopep":"(WB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__val","addr":"(UE)","loc":"d,19:17,19:20","dtypep":"(H)","access":"RD","varp":"(XB)","varScopep":"(WB)","classOrPackagep":"UNLINKED"}
],
"lsbp": [
{"type":"SEL","name":"","addr":"(TE)","loc":"d,19:22,19:23","dtypep":"(BD)","widthConst":4,
{"type":"SEL","name":"","addr":"(VE)","loc":"d,19:22,19:23","dtypep":"(CD)","widthConst":4,
"fromp": [
{"type":"MULS","name":"","addr":"(UE)","loc":"d,19:22,19:23","dtypep":"(NC)",
{"type":"MULS","name":"","addr":"(WE)","loc":"d,19:22,19:23","dtypep":"(NC)",
"lhsp": [
{"type":"CONST","name":"32'sh2","addr":"(VE)","loc":"d,19:23,19:24","dtypep":"(NC)"}
{"type":"CONST","name":"32'sh2","addr":"(XE)","loc":"d,19:23,19:24","dtypep":"(NC)"}
],
"rhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(WE)","loc":"d,19:21,19:22","dtypep":"(SB)","access":"RD","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(YE)","loc":"d,19:21,19:22","dtypep":"(SB)","access":"RD","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
]}
],
"lsbp": [
{"type":"CONST","name":"32'h0","addr":"(XE)","loc":"d,19:22,19:23","dtypep":"(GD)"}
{"type":"CONST","name":"32'h0","addr":"(ZE)","loc":"d,19:22,19:23","dtypep":"(HD)"}
]}
]}
]}
],
"lhsp": [
{"type":"SEL","name":"","addr":"(YE)","loc":"d,19:10,19:11","dtypep":"(RC)","widthConst":1,"declRange":"[6:0]","declElWidth":1,
{"type":"SEL","name":"","addr":"(AF)","loc":"d,19:10,19:11","dtypep":"(SC)","widthConst":1,"declRange":"[6:0]","declElWidth":1,
"fromp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__ret","addr":"(ZE)","loc":"d,19:7,19:10","dtypep":"(K)","access":"WR","varp":"(ZB)","varScopep":"(YB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__ret","addr":"(BF)","loc":"d,19:7,19:10","dtypep":"(K)","access":"WR","varp":"(ZB)","varScopep":"(YB)","classOrPackagep":"UNLINKED"}
],
"lsbp": [
{"type":"SEL","name":"","addr":"(AF)","loc":"d,19:11,19:12","dtypep":"(KD)","widthConst":3,
{"type":"SEL","name":"","addr":"(CF)","loc":"d,19:11,19:12","dtypep":"(LD)","widthConst":3,
"fromp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(BF)","loc":"d,19:11,19:12","dtypep":"(SB)","access":"RD","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(DF)","loc":"d,19:11,19:12","dtypep":"(SB)","access":"RD","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
],
"lsbp": [
{"type":"CONST","name":"32'h0","addr":"(CF)","loc":"d,19:11,19:12","dtypep":"(GD)"}
{"type":"CONST","name":"32'h0","addr":"(EF)","loc":"d,19:11,19:12","dtypep":"(HD)"}
]}
]}
],"timingControlp": []}
],
"incsp": [
{"type":"ASSIGN","name":"","addr":"(DF)","loc":"d,18:24,18:26","dtypep":"(SB)",
],"timingControlp": []},
{"type":"ASSIGN","name":"","addr":"(FF)","loc":"d,18:24,18:26","dtypep":"(SB)",
"rhsp": [
{"type":"ADD","name":"","addr":"(EF)","loc":"d,18:24,18:26","dtypep":"(GD)",
{"type":"ADD","name":"","addr":"(GF)","loc":"d,18:24,18:26","dtypep":"(HD)",
"lhsp": [
{"type":"CONST","name":"32'h1","addr":"(FF)","loc":"d,18:24,18:26","dtypep":"(GD)"}
{"type":"CONST","name":"32'h1","addr":"(HF)","loc":"d,18:24,18:26","dtypep":"(HD)"}
],
"rhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(GF)","loc":"d,18:23,18:24","dtypep":"(SB)","access":"RD","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(IF)","loc":"d,18:23,18:24","dtypep":"(SB)","access":"RD","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
]}
],
"lhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(HF)","loc":"d,18:23,18:24","dtypep":"(SB)","access":"WR","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__i","addr":"(JF)","loc":"d,18:23,18:24","dtypep":"(SB)","access":"WR","varp":"(BC)","varScopep":"(AC)","classOrPackagep":"UNLINKED"}
],"timingControlp": []}
]},
{"type":"ASSIGN","name":"","addr":"(IF)","loc":"d,21:5,21:11","dtypep":"(K)",
],"contsp": []},
{"type":"ASSIGN","name":"","addr":"(KF)","loc":"d,21:5,21:11","dtypep":"(K)",
"rhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__ret","addr":"(JF)","loc":"d,21:12,21:15","dtypep":"(K)","access":"RD","varp":"(ZB)","varScopep":"(YB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__ret","addr":"(LF)","loc":"d,21:12,21:15","dtypep":"(K)","access":"RD","varp":"(ZB)","varScopep":"(YB)","classOrPackagep":"UNLINKED"}
],
"lhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__Vfuncout","addr":"(KF)","loc":"d,21:5,21:11","dtypep":"(K)","access":"WR","varp":"(VB)","varScopep":"(UB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__Vfuncout","addr":"(MF)","loc":"d,21:5,21:11","dtypep":"(K)","access":"WR","varp":"(VB)","varScopep":"(UB)","classOrPackagep":"UNLINKED"}
],"timingControlp": []},
{"type":"ASSIGN","name":"","addr":"(LF)","loc":"d,25:14,25:15","dtypep":"(K)",
{"type":"ASSIGN","name":"","addr":"(NF)","loc":"d,25:14,25:15","dtypep":"(K)",
"rhsp": [
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__Vfuncout","addr":"(MF)","loc":"d,25:16,25:19","dtypep":"(K)","access":"RD","varp":"(VB)","varScopep":"(UB)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"__Vfunc_vlvbound_test.foo__1__Vfuncout","addr":"(OF)","loc":"d,25:16,25:19","dtypep":"(K)","access":"RD","varp":"(VB)","varScopep":"(UB)","classOrPackagep":"UNLINKED"}
],
"lhsp": [
{"type":"VARREF","name":"o_b","addr":"(NF)","loc":"d,25:10,25:13","dtypep":"(K)","access":"WR","varp":"(L)","varScopep":"(U)","classOrPackagep":"UNLINKED"}
{"type":"VARREF","name":"o_b","addr":"(PF)","loc":"d,25:10,25:13","dtypep":"(K)","access":"WR","varp":"(L)","varScopep":"(U)","classOrPackagep":"UNLINKED"}
],"timingControlp": []}
]}
],"inlinesp": []}
@ -293,24 +291,24 @@
]}
],"filesp": [],
"miscsp": [
{"type":"TYPETABLE","name":"","addr":"(C)","loc":"a,0:0,0:0","constraintRefp":"UNLINKED","emptyQueuep":"UNLINKED","queueIndexp":"UNLINKED","streamp":"UNLINKED","voidp":"(OF)",
{"type":"TYPETABLE","name":"","addr":"(C)","loc":"a,0:0,0:0","constraintRefp":"UNLINKED","emptyQueuep":"UNLINKED","queueIndexp":"UNLINKED","streamp":"UNLINKED","voidp":"(QF)",
"typesp": [
{"type":"BASICDTYPE","name":"logic","addr":"(RC)","loc":"d,18:18,18:19","dtypep":"(RC)","keyword":"logic","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(XC)","loc":"d,19:34,19:39","dtypep":"(XC)","keyword":"logic","range":"1:0","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(SC)","loc":"d,18:18,18:19","dtypep":"(SC)","keyword":"logic","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(YC)","loc":"d,19:34,19:39","dtypep":"(YC)","keyword":"logic","range":"1:0","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(H)","loc":"d,9:11,9:16","dtypep":"(H)","keyword":"logic","range":"15:0","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(K)","loc":"d,11:12,11:17","dtypep":"(K)","keyword":"logic","range":"6:0","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"integer","addr":"(SB)","loc":"d,17:5,17:12","dtypep":"(SB)","keyword":"integer","range":"31:0","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(KD)","loc":"d,19:10,19:11","dtypep":"(KD)","keyword":"logic","range":"2:0","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(GD)","loc":"d,19:11,19:12","dtypep":"(GD)","keyword":"logic","range":"31:0","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(BD)","loc":"d,19:20,19:21","dtypep":"(BD)","keyword":"logic","range":"3:0","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(LD)","loc":"d,19:10,19:11","dtypep":"(LD)","keyword":"logic","range":"2:0","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(HD)","loc":"d,19:11,19:12","dtypep":"(HD)","keyword":"logic","range":"31:0","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(CD)","loc":"d,19:20,19:21","dtypep":"(CD)","keyword":"logic","range":"3:0","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(NC)","loc":"d,18:12,18:13","dtypep":"(NC)","keyword":"logic","range":"31:0","generic":true,"rangep": []},
{"type":"VOIDDTYPE","name":"","addr":"(OF)","loc":"a,0:0,0:0","dtypep":"(OF)","generic":false}
{"type":"VOIDDTYPE","name":"","addr":"(QF)","loc":"a,0:0,0:0","dtypep":"(QF)","generic":false}
]},
{"type":"CONSTPOOL","name":"","addr":"(D)","loc":"a,0:0,0:0",
"modulep": [
{"type":"MODULE","name":"@CONST-POOL@","addr":"(PF)","loc":"a,0:0,0:0","isChecker":false,"isProgram":false,"hasGenericIface":false,"origName":"@CONST-POOL@","level":0,"modPublic":false,"inLibrary":false,"dead":false,"recursiveClone":false,"recursive":false,"timeunit":"NONE","inlinesp": [],
{"type":"MODULE","name":"@CONST-POOL@","addr":"(RF)","loc":"a,0:0,0:0","isChecker":false,"isProgram":false,"hasGenericIface":false,"origName":"@CONST-POOL@","level":0,"modPublic":false,"inLibrary":false,"dead":false,"recursiveClone":false,"recursive":false,"timeunit":"NONE","inlinesp": [],
"stmtsp": [
{"type":"SCOPE","name":"@CONST-POOL@","addr":"(QF)","loc":"a,0:0,0:0","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(PF)","varsp": [],"blocksp": [],"inlinesp": []}
{"type":"SCOPE","name":"@CONST-POOL@","addr":"(SF)","loc":"a,0:0,0:0","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(RF)","varsp": [],"blocksp": [],"inlinesp": []}
]}
]}
]}

View File

@ -1,38 +1,42 @@
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:172:7: Loop condition is always false; body will never execute
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:169:7: Loop condition is always false
: ... note: In instance 't.with_always'
172 | while(0);
169 | while(0);
| ^~~~~
... For warning description see https://verilator.org/warn/UNUSEDLOOP?v=latest
... Use "/* verilator lint_off UNUSEDLOOP */" and lint_on around source to disable this message.
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:155:7: Loop condition is always false; body will never execute
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:155:7: Loop condition is always false
: ... note: In instance 't.non_parametrized_initial'
155 | while(0);
| ^~~~~
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:114:7: Loop condition is always false; body will never execute
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:158:7: Loop condition is always false
: ... note: In instance 't.non_parametrized_initial'
158 | do ; while(0);
| ^~
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:114:7: Loop condition is always false
114 | while(always_zero < 0) begin
| ^~~~~
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:156:7: Loop condition is always false; body will never execute
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:156:7: Loop condition is always false
156 | while(always_false);
| ^~~~~
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:157:7: Loop condition is always false; body will never execute
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:157:7: Loop condition is always false
157 | while(always_zero < 0);
| ^~~~~
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:174:7: Loop condition is always false; body will never execute
174 | while(always_false) begin
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:171:7: Loop condition is always false
171 | while(always_false) begin
| ^~~~~
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:184:7: Loop condition is always false; body will never execute
184 | while(always_zero) begin
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:181:7: Loop condition is always false
181 | while(always_zero) begin
| ^~~~~
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:188:7: Loop condition is always false; body will never execute
188 | for (int i = 0; always_zero; i++)
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:185:7: Loop condition is always false
185 | for (int i = 0; always_zero; i++)
| ^~~
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:193:7: Loop condition is always false; body will never execute
193 | for (int i = 0; i < always_zero; i++)
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:190:7: Loop condition is always false
190 | for (int i = 0; i < always_zero; i++)
| ^~~
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:136:7: Loop is not used and will be optimized out
136 | while(param_unused_while < always_zero) begin
| ^~~~~
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:283:7: Loop is not used and will be optimized out
283 | while (m_2_ticked);
%Warning-UNUSEDLOOP: t/t_lint_removed_unused_loop_bad.v:280:7: Loop is not used and will be optimized out
280 | while (m_2_ticked);
| ^~~~~
%Error: Exiting due to

View File

@ -155,10 +155,7 @@ module non_parametrized_initial;
while(0);
while(always_false);
while(always_zero < 0);
// inlined - no warning
do begin
end while(0);
do ; while(0);
// unrolled - no warning
for (int i = 0; i < 1; i++);

View File

@ -18,7 +18,7 @@ test.execute()
test.file_grep(test.stats, r'NBA, variables using ShadowVar scheme\s+(\d+)', 1)
test.file_grep(test.stats, r'NBA, variables using ShadowVarMasked scheme\s+(\d+)', 2)
test.file_grep(test.stats, r'NBA, variables using FlagUnique scheme\s+(\d+)', 1)
test.file_grep(test.stats, r'Optimizations, Unrolled Loops\s+(\d+)', 0)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 0)
test.file_grep_not(test.stats, r'Warnings, Suppressed BLKANDNBLK')
test.passes()

View File

@ -18,7 +18,7 @@ test.execute()
test.file_grep(test.stats, r'NBA, variables using ShadowVar scheme\s+(\d+)', 1)
test.file_grep(test.stats, r'NBA, variables using ShadowVarMasked scheme\s+(\d+)', 2)
test.file_grep(test.stats, r'NBA, variables using FlagUnique scheme\s+(\d+)', 1)
test.file_grep(test.stats, r'Optimizations, Unrolled Loops\s+(\d+)', 0)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 0)
test.file_grep(test.stats, r'Warnings, Suppressed BLKANDNBLK\s+(\d+)', 2)
test.passes()

View File

@ -17,7 +17,7 @@ test.compile(v_flags2=['+define+TEST_FULL', '--stats'])
test.execute(expect_filename=test.golden_filename)
test.file_grep(test.stats, r'Optimizations, Unrolled Iterations\s+(\d+)', 11)
test.file_grep(test.stats, r'Optimizations, Unrolled Loops\s+(\d+)', 4)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled iterations\s+(\d+)', 107)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 27)
test.passes()

View File

@ -17,6 +17,7 @@ test.compile(verilator_flags2=['--unroll-count 4 --unroll-stmts 9999 --stats -DT
make_top_shell=False,
make_main=False)
test.file_grep(test.stats, r'Optimizations, Unrolled Loops\s+(\d+)', 1)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Pragma unroll_disable\s+(\d+)', 4)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 0)
test.passes()

View File

@ -17,6 +17,7 @@ test.compile(verilator_flags2=['--unroll-count 4 --unroll-stmts 9999 --stats -DT
make_top_shell=False,
make_main=False)
test.file_grep(test.stats, r'Optimizations, Unrolled Loops\s+(\d+)', 5)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 9)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled iterations\s+(\d+)', 45)
test.passes()

View File

@ -17,6 +17,9 @@ test.compile(verilator_flags2=['--unroll-count 4 --unroll-stmts 9999 --stats -DT
make_top_shell=False,
make_main=False)
test.file_grep(test.stats, r'Optimizations, Unrolled Loops\s+(\d+)', 3)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 3)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled iterations\s+(\d+)', 9)
test.file_grep(test.stats,
r'Optimizations, Loop unrolling, Failed - reached --unroll-count\s+(\d+)', 2)
test.passes()

View File

@ -0,0 +1,34 @@
loop_0 0
loop_0 1
loop_0 2
loop_1 0 5
loop_1 2 6
loop_1 4 7
loop_1 6 8
loop_1 8 9
loop_2 0 5
loop_2 1 5
loop_2 2 5
loop_2 3 5
loop_2 4 5
loop_3 4 0
loop_3 3 0
loop_3 2 0
loop_3 1 0
loop_3 0 0
loop_4
loop_5 1
loop_5 2
loop_5 3
loop_5 4
loop_5 5
loop_5 6
loop_5 7
loop_6 0
loop_6 1
loop_6 2
loop_6 3
loop_6 4
loop_6 5
stopping loop_6
*-* All Finished *-*

32
test_regress/t/t_unroll_stmt.py Executable file
View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 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
import vltest_bootstrap
test.scenarios('vlt_all')
test.compile(verilator_flags2=["--binary", "--stats"])
test.execute(expect_filename=test.golden_filename)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Failed - contains fork\s+(\d+)', 0)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Failed - infinite loop\s+(\d+)', 0)
test.file_grep(test.stats,
r'Optimizations, Loop unrolling, Failed - loop test in sub-statement\s+(\d+)', 0)
test.file_grep(test.stats,
r'Optimizations, Loop unrolling, Failed - reached --unroll-count\s+(\d+)', 0)
test.file_grep(test.stats,
r'Optimizations, Loop unrolling, Failed - reached --unroll-stmts\s+(\d+)', 0)
test.file_grep(test.stats,
r'Optimizations, Loop unrolling, Failed - unknown loop condition\s+(\d+)', 0)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Pragma unroll_disable\s+(\d+)', 0)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled loops\s+(\d+)', 6)
test.file_grep(test.stats, r'Optimizations, Loop unrolling, Unrolled iterations\s+(\d+)', 40)
test.passes()

View File

@ -0,0 +1,67 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
module t;
int static_loop_cond;
function logic f_loop_cond();
return ++static_loop_cond < 8;
endfunction
initial begin
// Basic loop
for (int i = 0; i < 3; ++i) begin : loop_0
$display("loop_0 %0d", i);
end
// Loop with 2 init/step
for (int i = 0, j = 5; i < j; i += 2, j += 1) begin : loop_1
$display("loop_1 %0d %0d", i, j);
end
// While loop with non-trivial init
begin
automatic int i = 0;
automatic int j = 5; // Not a variable
while (i < j) begin : loop_2
$display("loop_2 %0d %0d", i++, j);
end
end
// Do loop with non-trivial init
begin
automatic int i = 5;
automatic int j = 0; // Not a variable
do begin : loop_3
$display("loop_3 %0d %0d", --i, j);
end while (i > j);
end
// Do loop that executes once - replaced by V3Const, not unrolled
do begin: loop_4
$display("loop_4");
end while(0);
// Loop with inlined function as condition
static_loop_cond = 0;
while (f_loop_cond()) begin : loop_5
$display("loop_5 %0d", static_loop_cond);
end
// Self disabling loop in via 'then' branch of 'if'
begin
automatic logic found = 0;
for (int i = 0; i < 10; ++i) begin : loop_6
if (!found) begin
$display("loop_6 %0d", i);
if (i == $c32("5")) begin // Unknown condition
$display("stopping loop_6"); // This line is important
found = 1;
end
end
end
end
// Done
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -53,14 +53,6 @@
</stmtexpr>
</cfunc>
<cfunc loc="d,11,8,11,9" name="_eval_initial__TOP">
<var loc="d,28,11,28,14" name="t.all" dtype_id="10" vartype="string" origName="t__DOT__all"/>
<creset loc="d,28,11,28,14">
<varref loc="d,28,11,28,14" name="t.all" dtype_id="10"/>
</creset>
<var loc="d,52,17,52,18" name="t.unnamedblk1.e" dtype_id="2" vartype="my_t" origName="t__DOT__unnamedblk1__DOT__e"/>
<creset loc="d,52,17,52,18">
<varref loc="d,52,17,52,18" name="t.unnamedblk1.e" dtype_id="2"/>
</creset>
<var loc="d,49,120,49,121" name="__Vtemp_1" dtype_id="10" vartype="string" origName="__Vtemp_1"/>
<assign loc="d,32,9,32,10" dtype_id="11">
<const loc="d,32,11,32,14" name="4&apos;h3" dtype_id="11"/>
@ -520,80 +512,10 @@
<stop loc="d,49,139,49,144"/>
</begin>
</if>
<assign loc="d,51,11,51,12" dtype_id="10">
<const loc="d,51,13,51,15" name="&quot;&quot;" dtype_id="10"/>
<varref loc="d,51,7,51,10" name="t.all" dtype_id="10"/>
</assign>
<assign loc="d,52,19,52,20" dtype_id="11">
<const loc="d,52,23,52,28" name="4&apos;h1" dtype_id="11"/>
<varref loc="d,52,17,52,18" name="t.unnamedblk1.e" dtype_id="11"/>
</assign>
<while loc="d,52,7,52,10">
<begin>
<neq loc="d,52,32,52,34" dtype_id="8">
<const loc="d,52,37,52,41" name="4&apos;h4" dtype_id="11"/>
<ccast loc="d,52,30,52,31" dtype_id="11">
<varref loc="d,52,30,52,31" name="t.unnamedblk1.e" dtype_id="11"/>
</ccast>
</neq>
</begin>
<begin>
<assign loc="d,53,14,53,15" dtype_id="10">
<concatn loc="d,53,20,53,21" dtype_id="10">
<varref loc="d,53,17,53,20" name="t.all" dtype_id="10"/>
<arraysel loc="d,53,22,53,23" dtype_id="10">
<varref loc="d,17,12,17,16" name="__Venumtab_enum_name1" dtype_id="16"/>
<and loc="d,53,22,53,23" dtype_id="13">
<const loc="d,53,22,53,23" name="32&apos;h7" dtype_id="14"/>
<ccast loc="d,53,22,53,23" dtype_id="13">
<varref loc="d,53,22,53,23" name="t.unnamedblk1.e" dtype_id="13"/>
</ccast>
</and>
</arraysel>
</concatn>
<varref loc="d,53,10,53,13" name="t.all" dtype_id="10"/>
</assign>
</begin>
<begin>
<assign loc="d,52,45,52,46" dtype_id="11">
<arraysel loc="d,52,47,52,48" dtype_id="11">
<varref loc="d,17,12,17,16" name="__Venumtab_enum_next1" dtype_id="12"/>
<and loc="d,52,47,52,48" dtype_id="13">
<const loc="d,52,47,52,48" name="32&apos;h7" dtype_id="14"/>
<ccast loc="d,52,47,52,48" dtype_id="13">
<varref loc="d,52,47,52,48" name="t.unnamedblk1.e" dtype_id="13"/>
</ccast>
</and>
</arraysel>
<varref loc="d,52,43,52,44" name="t.unnamedblk1.e" dtype_id="11"/>
</assign>
</begin>
</while>
<assign loc="d,55,9,55,10" dtype_id="11">
<const loc="d,55,13,55,17" name="4&apos;h4" dtype_id="11"/>
<varref loc="d,55,7,55,8" name="t.e" dtype_id="11"/>
</assign>
<assign loc="d,56,11,56,12" dtype_id="10">
<concatn loc="d,56,17,56,18" dtype_id="10">
<varref loc="d,56,14,56,17" name="t.all" dtype_id="10"/>
<const loc="d,17,12,17,16" name="&quot;E04&quot;" dtype_id="10"/>
</concatn>
<varref loc="d,56,7,56,10" name="t.all" dtype_id="10"/>
</assign>
<if loc="d,57,10,57,12">
<neqn loc="d,57,20,57,22" dtype_id="8">
<const loc="d,57,24,57,35" name="&quot;E01E03E04&quot;" dtype_id="10"/>
<varref loc="d,57,15,57,18" name="t.all" dtype_id="10"/>
</neqn>
<begin>
<display loc="d,57,44,57,50" displaytype="$write">
<sformatf loc="d,57,44,57,50" name="%%Error: t/t_enum_type_methods.v:57: got=&apos;%@&apos; exp=&apos;E01E03E04&apos;&#10;" dtype_id="10">
<varref loc="d,57,123,57,126" name="t.all" dtype_id="10"/>
</sformatf>
</display>
<stop loc="d,57,145,57,150"/>
</begin>
</if>
</cfunc>
<cfunc loc="a,0,0,0,0" name="_eval_final"/>
<cfunc loc="a,0,0,0,0" name="_eval_settle"/>
@ -1596,11 +1518,11 @@
<const loc="d,11,8,11,9" name="1&apos;h1" dtype_id="8"/>
<varref loc="d,11,8,11,9" name="__VnbaContinue" dtype_id="8"/>
</assign>
<while loc="a,0,0,0,0">
<begin>
<varref loc="a,0,0,0,0" name="__VnbaContinue" dtype_id="8"/>
</begin>
<loop loc="a,0,0,0,0">
<begin>
<looptest loc="a,0,0,0,0">
<varref loc="a,0,0,0,0" name="__VnbaContinue" dtype_id="8"/>
</looptest>
<if loc="a,0,0,0,0">
<lt loc="a,0,0,0,0" dtype_id="8">
<const loc="a,0,0,0,0" name="32&apos;h64" dtype_id="14"/>
@ -1639,11 +1561,11 @@
<const loc="d,11,8,11,9" name="1&apos;h1" dtype_id="8"/>
<varref loc="d,11,8,11,9" name="__VactContinue" dtype_id="8"/>
</assign>
<while loc="a,0,0,0,0">
<begin>
<varref loc="a,0,0,0,0" name="__VactContinue" dtype_id="8"/>
</begin>
<loop loc="a,0,0,0,0">
<begin>
<looptest loc="a,0,0,0,0">
<varref loc="a,0,0,0,0" name="__VactContinue" dtype_id="8"/>
</looptest>
<if loc="a,0,0,0,0">
<lt loc="a,0,0,0,0" dtype_id="8">
<const loc="a,0,0,0,0" name="32&apos;h64" dtype_id="14"/>
@ -1684,7 +1606,7 @@
</begin>
</if>
</begin>
</while>
</loop>
<if loc="a,0,0,0,0">
<ccall loc="a,0,0,0,0" dtype_id="8" func="_eval_phase__nba"/>
<begin>
@ -1695,7 +1617,7 @@
</begin>
</if>
</begin>
</while>
</loop>
</cfunc>
<cfunc loc="d,11,8,11,9" name="_eval_debug_assertions">
<if loc="d,15,10,15,13">
@ -1828,7 +1750,6 @@
<const loc="d,17,12,17,16" name="32&apos;h0" dtype_id="14"/>
</range>
</unpackarraydtype>
<refdtype loc="d,52,12,52,16" id="22" name="my_t" sub_dtype_id="2"/>
<basicdtype loc="d,23,23,23,24" id="9" name="logic" left="31" right="0" signed="true"/>
<voiddtype loc="a,0,0,0,0" id="7"/>
<basicdtype loc="d,11,8,11,9" id="6" name="VlTriggerVec"/>

View File

@ -26,8 +26,8 @@ test.files_identical(out_filename, test.golden_filename, 'logfile')
test.file_grep(out_filename, r'<constpool')
test.file_grep(out_filename, r'<inititem')
test.file_grep(out_filename, r'<if')
test.file_grep(out_filename, r'<while')
test.file_grep(out_filename, r'<begin>') # for <if> and <while>
test.file_grep(out_filename, r'<loop')
test.file_grep(out_filename, r'<begin>') # for <if> and <loop>
test.file_grep(out_filename, r' signed=') # for <basicdtype>
test.file_grep(out_filename, r' func=') # for <ccall>

View File

@ -73,14 +73,14 @@
<const loc="d,18,12,18,13" name="32&apos;sh0" dtype_id="4"/>
<varref loc="d,18,10,18,11" name="__Vfunc_vlvbound_test.foo__0__i" dtype_id="3"/>
</assign>
<while loc="d,18,5,18,8">
<begin>
<gts loc="d,18,18,18,19" dtype_id="5">
<const loc="d,18,20,18,21" name="32&apos;sh7" dtype_id="4"/>
<varref loc="d,18,16,18,17" name="__Vfunc_vlvbound_test.foo__0__i" dtype_id="3"/>
</gts>
</begin>
<loop loc="d,18,5,18,8">
<begin>
<looptest loc="d,18,16,18,17">
<gts loc="d,18,18,18,19" dtype_id="5">
<const loc="d,18,20,18,21" name="32&apos;sh7" dtype_id="4"/>
<varref loc="d,18,16,18,17" name="__Vfunc_vlvbound_test.foo__0__i" dtype_id="3"/>
</gts>
</looptest>
<assign loc="d,19,14,19,15" dtype_id="5">
<eq loc="d,19,31,19,33" dtype_id="5">
<const loc="d,19,34,19,39" name="2&apos;h0" dtype_id="6"/>
@ -103,8 +103,6 @@
</sel>
</sel>
</assign>
</begin>
<begin>
<assign loc="d,18,24,18,26" dtype_id="3">
<add loc="d,18,24,18,26" dtype_id="8">
<const loc="d,18,24,18,26" name="32&apos;h1" dtype_id="8"/>
@ -113,7 +111,7 @@
<varref loc="d,18,23,18,24" name="__Vfunc_vlvbound_test.foo__0__i" dtype_id="3"/>
</assign>
</begin>
</while>
</loop>
<assign loc="d,21,5,21,11" dtype_id="2">
<varref loc="d,21,12,21,15" name="__Vfunc_vlvbound_test.foo__0__ret" dtype_id="2"/>
<varref loc="d,21,5,21,11" name="__Vfunc_vlvbound_test.foo__0__Vfuncout" dtype_id="2"/>
@ -139,14 +137,14 @@
<const loc="d,18,12,18,13" name="32&apos;sh0" dtype_id="4"/>
<varref loc="d,18,10,18,11" name="__Vfunc_vlvbound_test.foo__1__i" dtype_id="3"/>
</assign>
<while loc="d,18,5,18,8">
<begin>
<gts loc="d,18,18,18,19" dtype_id="5">
<const loc="d,18,20,18,21" name="32&apos;sh7" dtype_id="4"/>
<varref loc="d,18,16,18,17" name="__Vfunc_vlvbound_test.foo__1__i" dtype_id="3"/>
</gts>
</begin>
<loop loc="d,18,5,18,8">
<begin>
<looptest loc="d,18,16,18,17">
<gts loc="d,18,18,18,19" dtype_id="5">
<const loc="d,18,20,18,21" name="32&apos;sh7" dtype_id="4"/>
<varref loc="d,18,16,18,17" name="__Vfunc_vlvbound_test.foo__1__i" dtype_id="3"/>
</gts>
</looptest>
<assign loc="d,19,14,19,15" dtype_id="5">
<eq loc="d,19,31,19,33" dtype_id="5">
<const loc="d,19,34,19,39" name="2&apos;h0" dtype_id="6"/>
@ -169,8 +167,6 @@
</sel>
</sel>
</assign>
</begin>
<begin>
<assign loc="d,18,24,18,26" dtype_id="3">
<add loc="d,18,24,18,26" dtype_id="8">
<const loc="d,18,24,18,26" name="32&apos;h1" dtype_id="8"/>
@ -179,7 +175,7 @@
<varref loc="d,18,23,18,24" name="__Vfunc_vlvbound_test.foo__1__i" dtype_id="3"/>
</assign>
</begin>
</while>
</loop>
<assign loc="d,21,5,21,11" dtype_id="2">
<varref loc="d,21,12,21,15" name="__Vfunc_vlvbound_test.foo__1__ret" dtype_id="2"/>
<varref loc="d,21,5,21,11" name="__Vfunc_vlvbound_test.foo__1__Vfuncout" dtype_id="2"/>