Optimize circular logic in Dfg
Introduce a new DfgPrev vertex, representing the value of a variable before any in-graph assignments. This can be used to break all remaining cycles in the graph, so all Dfgs become acyclic after V3DfgBreakCycles. The circular dataflow is still represented, and is taken care of by the scheduler, it is just the DfgGraph that represents the logic that becomes acyclic. This makes V3DfgBreakCycles a mandatory transform, so drop the disabling -fno-dfg-break-cycles option (still parsed, but has no effect). Note the effect of this is small, as most cycles can be fixed up by driver tracing, which is unchanged, but this is required for some upcoming work.
This commit is contained in:
parent
646dcd3838
commit
1364f402da
|
|
@ -715,6 +715,10 @@ Summary:
|
||||||
|
|
||||||
.. option:: -fno-dfg-break-cycles
|
.. option:: -fno-dfg-break-cycles
|
||||||
|
|
||||||
|
Deprecated and has no effect (ignored).
|
||||||
|
|
||||||
|
In versions before 5.052:
|
||||||
|
|
||||||
Rarely needed. Disable breaking combinational cycles during DFG.
|
Rarely needed. Disable breaking combinational cycles during DFG.
|
||||||
|
|
||||||
.. option:: -fno-dfg-peephole
|
.. option:: -fno-dfg-peephole
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,40 @@ static void dumpDotVertex(std::ostream& os, const DfgVertex& vtx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (const DfgPrev* const prevVtxp = vtx.cast<DfgPrev>()) {
|
||||||
|
const AstVarScope* const vscp = prevVtxp->vscp();
|
||||||
|
os << toDotId(vtx);
|
||||||
|
// Begin attributes
|
||||||
|
os << " [";
|
||||||
|
// Begin 'label'
|
||||||
|
os << "label=\"";
|
||||||
|
// Name
|
||||||
|
os << vscp->prettyName();
|
||||||
|
// Address
|
||||||
|
os << '\n' << cvtToHex(prevVtxp);
|
||||||
|
// Type and fanout
|
||||||
|
os << '\n';
|
||||||
|
prevVtxp->dtype().astDtypep()->dumpSmall(os);
|
||||||
|
os << " / F" << prevVtxp->fanout();
|
||||||
|
// End 'label'
|
||||||
|
os << '"';
|
||||||
|
// Shape
|
||||||
|
if (prevVtxp->isPacked()) {
|
||||||
|
os << ", shape=box";
|
||||||
|
} else if (prevVtxp->isArray()) {
|
||||||
|
os << ", shape=box3d";
|
||||||
|
} else {
|
||||||
|
prevVtxp->v3fatalSrc("Unhandled variable type");
|
||||||
|
}
|
||||||
|
// Color
|
||||||
|
const char* const colorp = "mediumorchid1"; // Purple
|
||||||
|
os << ", style=filled";
|
||||||
|
os << ", fillcolor=\"" << colorp << "\"";
|
||||||
|
// End attributes
|
||||||
|
os << "]\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (const DfgConst* const constVtxp = vtx.cast<DfgConst>()) {
|
if (const DfgConst* const constVtxp = vtx.cast<DfgConst>()) {
|
||||||
const V3Number& num = constVtxp->num();
|
const V3Number& num = constVtxp->num();
|
||||||
|
|
||||||
|
|
@ -479,6 +513,10 @@ void DfgVertex::typeCheck(const DfgGraph& dfg) const {
|
||||||
CHECK(!v.srcp() || v.srcp()->dtype() == v.dtype(), "'srcp' should match");
|
CHECK(!v.srcp() || v.srcp()->dtype() == v.dtype(), "'srcp' should match");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case VDfgType::Prev: {
|
||||||
|
CHECK(isPacked() || isArray(), "Should be Packed or Array type");
|
||||||
|
return;
|
||||||
|
}
|
||||||
case VDfgType::SpliceArray:
|
case VDfgType::SpliceArray:
|
||||||
case VDfgType::SplicePacked: {
|
case VDfgType::SplicePacked: {
|
||||||
const DfgVertexSplice& v = *as<DfgVertexSplice>();
|
const DfgVertexSplice& v = *as<DfgVertexSplice>();
|
||||||
|
|
|
||||||
|
|
@ -816,6 +816,7 @@ bool DfgVertex::isCheaperThanLoad() const {
|
||||||
if (is<DfgConst>()) return true;
|
if (is<DfgConst>()) return true;
|
||||||
// Variables
|
// Variables
|
||||||
if (is<DfgVertexVar>()) return true;
|
if (is<DfgVertexVar>()) return true;
|
||||||
|
if (is<DfgPrev>()) return true;
|
||||||
// Array sels are just address computation, but the address itself can be expensive
|
// Array sels are just address computation, but the address itself can be expensive
|
||||||
if (const DfgArraySel* aselp = cast<DfgArraySel>()) {
|
if (const DfgArraySel* aselp = cast<DfgArraySel>()) {
|
||||||
if (aselp->bitp()->is<DfgMatchMasked>()) return false;
|
if (aselp->bitp()->is<DfgMatchMasked>()) return false;
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,18 @@ public:
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update after replacing 'vtxp' with 'replacement'
|
||||||
|
void updateReplacement(const DfgVertex& vtx, DfgVertex& replacement) {
|
||||||
|
updateAcyclic(vtx);
|
||||||
|
if (!get(replacement)) {
|
||||||
|
replacement.foreachSink([&](DfgVertex& dst) {
|
||||||
|
if (!get(dst)) return false;
|
||||||
|
if (!stillCyclicFwd(dst)) updateAcyclic(dst);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Check stored information is consistent with actual SCCs. Note we
|
Check stored information is consistent with actual SCCs. Note we
|
||||||
can't detect during updates if an SCC has split into multiple SCCs.
|
can't detect during updates if an SCC has split into multiple SCCs.
|
||||||
|
|
@ -1518,17 +1530,8 @@ class FixUp final {
|
||||||
UASSERT_OBJ(!vtx.hasSinks() == !m_sccInfo.get(*replacementp), &vtx,
|
UASSERT_OBJ(!vtx.hasSinks() == !m_sccInfo.get(*replacementp), &vtx,
|
||||||
"Replacement vertex SCC inconsistent");
|
"Replacement vertex SCC inconsistent");
|
||||||
|
|
||||||
// If we broke the cycle through this vertex we can update the SccInfo
|
// If we broke the cycle through this vertex, update the SccInfo
|
||||||
if (!vtx.hasSinks()) {
|
if (!vtx.hasSinks()) m_sccInfo.updateReplacement(vtx, *replacementp);
|
||||||
m_sccInfo.updateAcyclic(vtx);
|
|
||||||
if (!m_sccInfo.get(*replacementp)) {
|
|
||||||
replacementp->foreachSink([&](DfgVertex& dst) {
|
|
||||||
if (!m_sccInfo.get(dst)) return false;
|
|
||||||
if (!m_sccInfo.stillCyclicFwd(dst)) m_sccInfo.updateAcyclic(dst);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void main(DfgVertexVar& var) {
|
void main(DfgVertexVar& var) {
|
||||||
|
|
@ -1583,7 +1586,7 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
bool breakCycles(DfgGraph& dfg, V3DfgBreakCyclesContext& ctx) {
|
void breakCycles(DfgGraph& dfg, V3DfgBreakCyclesContext& ctx) {
|
||||||
// Shorthand for dumping graph at given dump level
|
// Shorthand for dumping graph at given dump level
|
||||||
const auto dump = [&](int level, const DfgGraph& dfg, const std::string& name) {
|
const auto dump = [&](int level, const DfgGraph& dfg, const std::string& name) {
|
||||||
if (dumpDfgLevel() >= level) dfg.dumpDotFilePrefixed("breakCycles-" + name);
|
if (dumpDfgLevel() >= level) dfg.dumpDotFilePrefixed("breakCycles-" + name);
|
||||||
|
|
@ -1622,7 +1625,7 @@ bool breakCycles(DfgGraph& dfg, V3DfgBreakCyclesContext& ctx) {
|
||||||
UINFO(7, "Graph became acyclic after " << nImprovements << " improvements");
|
UINFO(7, "Graph became acyclic after " << nImprovements << " improvements");
|
||||||
dump(7, dfg, "result-acyclic");
|
dump(7, dfg, "result-acyclic");
|
||||||
++ctx.m_nFixed;
|
++ctx.m_nFixed;
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
} while (nImprovements != prevNImprovements);
|
} while (nImprovements != prevNImprovements);
|
||||||
|
|
||||||
|
|
@ -1641,17 +1644,62 @@ bool breakCycles(DfgGraph& dfg, V3DfgBreakCyclesContext& ctx) {
|
||||||
++ctx.m_nImproved;
|
++ctx.m_nImproved;
|
||||||
} else {
|
} else {
|
||||||
UINFO(7, "Graph NOT improved");
|
UINFO(7, "Graph NOT improved");
|
||||||
dump(7, dfg, "result-original");
|
|
||||||
++ctx.m_nUnchanged;
|
++ctx.m_nUnchanged;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
// Break any remaining cycles by inserting a DfgPrev for variables still in a cycle.
|
||||||
|
// Doing this unconditionally is both safe and sufficient:
|
||||||
|
// - Safe: a DfgPrev makes the variable's readers read its stored value instead
|
||||||
|
// of its in-graph driver, while the variable itself is still assigned. This
|
||||||
|
// merely reconstructs the cyclic variable-level dataflow, which the scheduler
|
||||||
|
// will resolve via its settle loop, so the computation is equivalent.
|
||||||
|
// - Sufficient: every cycle must pass through a variable, and in particular
|
||||||
|
// through a non-temporary one (synthesis doesn't create cycles within a single
|
||||||
|
// logic block).
|
||||||
|
// Also note that synthesized logic that depends on an in-graph update will read
|
||||||
|
// the synthesis temporary, not the real variable (which is what we are cutting),
|
||||||
|
// so the graph still holds all updates in a form visible to later optimizers.
|
||||||
|
|
||||||
|
// Gather all non-temporary variables as candidate cut points, prefer those that
|
||||||
|
// are kept regardless. Breaking a cycle there is essentially free, as they must be
|
||||||
|
// materialized anyway, whereas cutting an internal variable forces it to survive
|
||||||
|
// via its DfgPrev when it could otherwise be inlined away later.
|
||||||
|
std::vector<DfgVertexVar*> varps;
|
||||||
|
for (DfgVertexVar& vtx : dfg.varVertices()) {
|
||||||
|
if (!vtx.tmpForp()) varps.push_back(&vtx);
|
||||||
|
}
|
||||||
|
std::stable_partition(varps.begin(), varps.end(), [](const DfgVertexVar* varp) {
|
||||||
|
return varp->hasExtRefs() || varp->isVolatile();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Insert DfgPrev vertices until the graph becomes acyclic
|
||||||
|
SccInfo sccInfo{dfg};
|
||||||
|
for (DfgVertexVar* const varp : varps) {
|
||||||
|
// Stop as soon as it becomes acyclic
|
||||||
|
if (!sccInfo.isCyclic()) break;
|
||||||
|
|
||||||
|
// Ignore if variable is not part of a cycle
|
||||||
|
if (!sccInfo.get(*varp)) continue;
|
||||||
|
|
||||||
|
// Insert a DfgPrev vertex for the variable
|
||||||
|
DfgPrev* const prevp = new DfgPrev{dfg, varp->vscp()};
|
||||||
|
sccInfo.add(*prevp, 0);
|
||||||
|
varp->replaceWith(prevp);
|
||||||
|
++ctx.m_nPrevInserted;
|
||||||
|
|
||||||
|
// Update SCC info
|
||||||
|
sccInfo.updateReplacement(*varp, *prevp);
|
||||||
|
}
|
||||||
|
// Validate SccInfo if in debug mode
|
||||||
|
if (v3Global.opt.debugCheck()) sccInfo.validate();
|
||||||
|
UASSERT(!sccInfo.isCyclic(), "Graph should become acyclic");
|
||||||
|
|
||||||
|
dump(7, dfg, "result-withprev");
|
||||||
}
|
}
|
||||||
|
|
||||||
} //namespace V3DfgBreakCycles
|
} //namespace V3DfgBreakCycles
|
||||||
|
void V3DfgPasses::breakCycles(DfgGraph& dfg, V3DfgContext& ctx) {
|
||||||
bool V3DfgPasses::breakCycles(DfgGraph& dfg, V3DfgContext& ctx) {
|
V3DfgBreakCycles::breakCycles(dfg, ctx.m_breakCyclesContext);
|
||||||
const bool res = V3DfgBreakCycles::breakCycles(dfg, ctx.m_breakCyclesContext);
|
|
||||||
if (v3Global.opt.debugCheck()) V3DfgPasses::typeCheck(dfg);
|
if (v3Global.opt.debugCheck()) V3DfgPasses::typeCheck(dfg);
|
||||||
V3DfgPasses::removeUnused(dfg);
|
V3DfgPasses::removeUnused(dfg);
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ public:
|
||||||
VDouble0 m_nImproved; // Number of graphs that were improved but still cyclic
|
VDouble0 m_nImproved; // Number of graphs that were improved but still cyclic
|
||||||
VDouble0 m_nUnchanged; // Number of graphs that were left unchanged
|
VDouble0 m_nUnchanged; // Number of graphs that were left unchanged
|
||||||
VDouble0 m_nImprovements; // Number of changes made to graphs
|
VDouble0 m_nImprovements; // Number of changes made to graphs
|
||||||
|
VDouble0 m_nPrevInserted; // Number of Prev vertices inserted
|
||||||
|
|
||||||
private:
|
private:
|
||||||
V3DfgBreakCyclesContext()
|
V3DfgBreakCyclesContext()
|
||||||
|
|
@ -112,6 +113,7 @@ private:
|
||||||
addStat("improved", m_nImproved);
|
addStat("improved", m_nImproved);
|
||||||
addStat("left unchanged", m_nUnchanged);
|
addStat("left unchanged", m_nUnchanged);
|
||||||
addStat("changes applied", m_nImprovements);
|
addStat("changes applied", m_nImprovements);
|
||||||
|
addStat("prev vertices inserted", m_nPrevInserted);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
class V3DfgCseContext final : public V3DfgSubContext {
|
class V3DfgCseContext final : public V3DfgSubContext {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ class V3DfgCse final {
|
||||||
case VDfgType::CReset:
|
case VDfgType::CReset:
|
||||||
case VDfgType::VarArray:
|
case VDfgType::VarArray:
|
||||||
case VDfgType::VarPacked:
|
case VDfgType::VarPacked:
|
||||||
|
case VDfgType::Prev:
|
||||||
case VDfgType::AstRd: // LCOV_EXCL_STOP
|
case VDfgType::AstRd: // LCOV_EXCL_STOP
|
||||||
vtx.v3fatalSrc("Hash should have been pre-computed");
|
vtx.v3fatalSrc("Hash should have been pre-computed");
|
||||||
|
|
||||||
|
|
@ -173,6 +174,7 @@ class V3DfgCse final {
|
||||||
// Special vertices
|
// Special vertices
|
||||||
case VDfgType::Const: return a.as<DfgConst>()->num().isCaseEq(b.as<DfgConst>()->num());
|
case VDfgType::Const: return a.as<DfgConst>()->num().isCaseEq(b.as<DfgConst>()->num());
|
||||||
case VDfgType::CReset: return false;
|
case VDfgType::CReset: return false;
|
||||||
|
case VDfgType::Prev: return false;
|
||||||
|
|
||||||
case VDfgType::VarArray:
|
case VDfgType::VarArray:
|
||||||
case VDfgType::VarPacked: // CSE does not combine variables
|
case VDfgType::VarPacked: // CSE does not combine variables
|
||||||
|
|
@ -307,9 +309,10 @@ class V3DfgCse final {
|
||||||
for (const DfgVertexVar& vtx : dfg.varVertices()) m_hashCache[vtx] = V3Hash{++varHash};
|
for (const DfgVertexVar& vtx : dfg.varVertices()) m_hashCache[vtx] = V3Hash{++varHash};
|
||||||
// Pre-hash Ast references, these are all unique like variables
|
// Pre-hash Ast references, these are all unique like variables
|
||||||
for (const DfgVertexAst& vtx : dfg.astVertices()) m_hashCache[vtx] = V3Hash{++varHash};
|
for (const DfgVertexAst& vtx : dfg.astVertices()) m_hashCache[vtx] = V3Hash{++varHash};
|
||||||
// Pre-hash CReset vertices, these are all unique
|
// Pre-hash CReset and Prev vertices, these are all unique
|
||||||
for (const DfgVertex& vtx : dfg.opVertices()) {
|
for (const DfgVertex& vtx : dfg.opVertices()) {
|
||||||
if (vtx.is<DfgCReset>()) m_hashCache[vtx] = V3Hash{++varHash};
|
if (vtx.is<DfgCReset>()) m_hashCache[vtx] = V3Hash{++varHash};
|
||||||
|
if (vtx.is<DfgPrev>()) m_hashCache[vtx] = V3Hash{++varHash};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Similarly pre-hash constants for speed. While we don't combine constants, we do want
|
// Similarly pre-hash constants for speed. While we don't combine constants, we do want
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,10 @@ class DfgToAstVisitor final : DfgVisitor {
|
||||||
m_resultp = new AstVarRef{vtxp->fileline(), vtxp->vscp(), VAccess::READ};
|
m_resultp = new AstVarRef{vtxp->fileline(), vtxp->vscp(), VAccess::READ};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void visit(DfgPrev* vtxp) override {
|
||||||
|
m_resultp = new AstVarRef{vtxp->fileline(), vtxp->vscp(), VAccess::READ};
|
||||||
|
}
|
||||||
|
|
||||||
void visit(DfgConst* vtxp) override { //
|
void visit(DfgConst* vtxp) override { //
|
||||||
m_resultp = new AstConst{vtxp->fileline(), vtxp->num()};
|
m_resultp = new AstConst{vtxp->fileline(), vtxp->num()};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@ class DataflowOptimize final {
|
||||||
// - bit2: Read by logic in same module/netlist not represented in DFG
|
// - bit2: Read by logic in same module/netlist not represented in DFG
|
||||||
// - bit3: Written by logic in same module/netlist not represented in DFG
|
// - bit3: Written by logic in same module/netlist not represented in DFG
|
||||||
// - bit4: Has READWRITE references
|
// - bit4: Has READWRITE references
|
||||||
// - bit31-5: Reference count, how many DfgVertexVar represent this variable
|
// - bit5: Has DfgPrev instance
|
||||||
|
// - bit31-6: Reference count, how many DfgVertexVar represent this variable
|
||||||
//
|
//
|
||||||
// AstNode::user2/user3/user4 can be used by various DFG algorithms
|
// AstNode::user2/user3/user4 can be used by various DFG algorithms
|
||||||
const VNUser1InUse m_user1InUse;
|
const VNUser1InUse m_user1InUse;
|
||||||
|
|
@ -116,65 +117,44 @@ class DataflowOptimize final {
|
||||||
V3DfgPasses::synthesize(dfg, m_ctx);
|
V3DfgPasses::synthesize(dfg, m_ctx);
|
||||||
endOfStage("synthesize", dfg, {});
|
endOfStage("synthesize", dfg, {});
|
||||||
|
|
||||||
// Extract the cyclic sub-graphs. We do this because a lot of the optimizations assume a
|
// Extract the cyclic sub-graphs, so breakCycles can operate on small graphs,
|
||||||
// DAG, and large, mostly acyclic graphs could not be optimized due to the presence of
|
// make all of them acyclic, then merge them back to the main graph
|
||||||
// small cycles.
|
{
|
||||||
std::vector<std::unique_ptr<DfgGraph>> cyclicComps = dfg.extractCyclicComponents("cyclic");
|
std::vector<std::unique_ptr<DfgGraph>> comps = dfg.extractCyclicComponents("cyclic");
|
||||||
endOfStage("extractCyclic", dfg, cyclicComps);
|
for (const auto& cp : comps) V3DfgPasses::breakCycles(*cp, m_ctx);
|
||||||
|
dfg.mergeGraphs(std::move(comps));
|
||||||
// Attempt to convert cyclic components into acyclic ones
|
endOfStage("breakCycles", dfg, {});
|
||||||
std::vector<std::unique_ptr<DfgGraph>> madeAcyclicComponents;
|
|
||||||
if (v3Global.opt.fDfgBreakCycles()) {
|
|
||||||
for (auto it = cyclicComps.begin(); it != cyclicComps.end();) {
|
|
||||||
const bool madeAcyclic = V3DfgPasses::breakCycles(**it, m_ctx);
|
|
||||||
// If not made acyclic, keep it in 'cyclicComps'
|
|
||||||
if (!madeAcyclic) {
|
|
||||||
++it;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Otherwise move to 'madeAcyclicComponents'
|
|
||||||
madeAcyclicComponents.emplace_back(std::move(*it));
|
|
||||||
it = cyclicComps.erase(it);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Merge those that were made acyclic back to the graph, this enables optimizing more
|
|
||||||
dfg.mergeGraphs(std::move(madeAcyclicComponents));
|
|
||||||
endOfStage("breakCycles", dfg, cyclicComps);
|
|
||||||
|
|
||||||
// Remove redundant selects
|
// Split the now entirely acyclic DFG into [weakly] connected components
|
||||||
V3DfgPasses::removeSelects(dfg, m_ctx.m_removeSelectsContext);
|
std::vector<std::unique_ptr<DfgGraph>> comps = dfg.splitIntoComponents("acyclic");
|
||||||
for (std::unique_ptr<DfgGraph>& compp : cyclicComps) {
|
|
||||||
V3DfgPasses::removeSelects(*compp, m_ctx.m_removeSelectsContext);
|
|
||||||
}
|
|
||||||
endOfStage("removeSelects", dfg, cyclicComps);
|
|
||||||
|
|
||||||
// Split the acyclic DFG into [weakly] connected components
|
|
||||||
std::vector<std::unique_ptr<DfgGraph>> acyclicComps = dfg.splitIntoComponents("acyclic");
|
|
||||||
UASSERT(dfg.size() == 0, "DfgGraph should have become empty");
|
UASSERT(dfg.size() == 0, "DfgGraph should have become empty");
|
||||||
endOfStage("splitAcyclic", dfg, acyclicComps);
|
endOfStage("splitAcyclic", dfg, comps);
|
||||||
|
|
||||||
// Optimize each acyclic component
|
// Main pass pipeline - optimize each acyclic component
|
||||||
for (auto& cp : acyclicComps) V3DfgPasses::inlineVars(*cp);
|
{
|
||||||
endOfStage("inlineVars", dfg, acyclicComps);
|
for (auto& cp : comps) V3DfgPasses::removeSelects(*cp, m_ctx.m_removeSelectsContext);
|
||||||
for (auto& cp : acyclicComps) V3DfgPasses::cse(*cp, m_ctx.m_cseContext0);
|
endOfStage("removeSelects", dfg, comps);
|
||||||
endOfStage("cse0", dfg, acyclicComps);
|
for (const auto& cp : comps) V3DfgPasses::inlineVars(*cp);
|
||||||
for (auto& cp : acyclicComps) V3DfgPasses::binToOneHot(*cp, m_ctx.m_binToOneHotContext);
|
endOfStage("inlineVars", dfg, comps);
|
||||||
endOfStage("binToOneHot", dfg, acyclicComps);
|
for (auto& cp : comps) V3DfgPasses::cse(*cp, m_ctx.m_cseContext0);
|
||||||
for (auto& cp : acyclicComps) V3DfgPasses::peephole(*cp, m_ctx.m_peepholeContext);
|
endOfStage("cse0", dfg, comps);
|
||||||
endOfStage("peephole", dfg, acyclicComps);
|
for (auto& cp : comps) V3DfgPasses::binToOneHot(*cp, m_ctx.m_binToOneHotContext);
|
||||||
// Accumulate patterns for reporting
|
endOfStage("binToOneHot", dfg, comps);
|
||||||
if (v3Global.opt.dumpDfgPatterns()) {
|
for (auto& cp : comps) V3DfgPasses::peephole(*cp, m_ctx.m_peepholeContext);
|
||||||
V3DfgPasses::dumpPatterns(acyclicComps);
|
endOfStage("peephole", dfg, comps);
|
||||||
endOfStage("dumpPatterns");
|
|
||||||
|
// Accumulate patterns for reporting
|
||||||
|
if (v3Global.opt.dumpDfgPatterns()) V3DfgPasses::dumpPatterns(comps);
|
||||||
|
|
||||||
|
for (auto& cp : comps) V3DfgPasses::pushDownSels(*cp, m_ctx.m_pushDownSelsContext);
|
||||||
|
endOfStage("pushDownSels", dfg, comps);
|
||||||
|
for (auto& cp : comps) V3DfgPasses::cse(*cp, m_ctx.m_cseContext1);
|
||||||
|
endOfStage("cse1", dfg, comps);
|
||||||
}
|
}
|
||||||
for (auto& cp : acyclicComps) V3DfgPasses::pushDownSels(*cp, m_ctx.m_pushDownSelsContext);
|
|
||||||
endOfStage("pushDownSels", dfg, acyclicComps);
|
|
||||||
for (auto& cp : acyclicComps) V3DfgPasses::cse(*cp, m_ctx.m_cseContext1);
|
|
||||||
endOfStage("cse1", dfg, acyclicComps);
|
|
||||||
|
|
||||||
// Merge everything back under the main DFG
|
// Merge everything back under the main graph
|
||||||
dfg.mergeGraphs(std::move(acyclicComps));
|
dfg.mergeGraphs(std::move(comps));
|
||||||
dfg.mergeGraphs(std::move(cyclicComps));
|
|
||||||
endOfStage("optimized", dfg, {});
|
endOfStage("optimized", dfg, {});
|
||||||
|
|
||||||
// Regularize the graph after merging it all back together so all
|
// Regularize the graph after merging it all back together so all
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,10 @@ void removeUnobservable(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
|
||||||
void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
|
void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
|
||||||
// Remove redundant selects
|
// Remove redundant selects
|
||||||
void removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) VL_MT_DISABLED;
|
void removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) VL_MT_DISABLED;
|
||||||
// Attempt to make the given cyclic graph into an acyclic, or "less cyclic"
|
// Make the given cyclic graph into an acyclic one, by tracing drivers, and
|
||||||
// equivalent. Genuine combinational cycles can exist, so this might be
|
// if that is unsuccessful, by inserting Prev vertices. The given graph will
|
||||||
// unsuccessful. Returns true if the graph became acyclic, false otherwise.
|
// always become acyclic after this pass.
|
||||||
bool breakCycles(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
|
void breakCycles(DfgGraph&, V3DfgContext&) VL_MT_DISABLED;
|
||||||
// Construct binary to oneHot decoders
|
// Construct binary to oneHot decoders
|
||||||
void binToOneHot(DfgGraph&, V3DfgBinToOneHotContext&) VL_MT_DISABLED;
|
void binToOneHot(DfgGraph&, V3DfgBinToOneHotContext&) VL_MT_DISABLED;
|
||||||
// Common subexpression elimination
|
// Common subexpression elimination
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,8 @@ class V3DfgPeephole final : public DfgVisitor {
|
||||||
void deleteVertex(DfgVertex* vtxp) {
|
void deleteVertex(DfgVertex* vtxp) {
|
||||||
UASSERT_OBJ(!m_vInfo[vtxp].m_workListIndex, vtxp, "Deleted Vertex is in work list");
|
UASSERT_OBJ(!m_vInfo[vtxp].m_workListIndex, vtxp, "Deleted Vertex is in work list");
|
||||||
UASSERT_OBJ(!vtxp->hasSinks(), vtxp, "Should not delete used vertex");
|
UASSERT_OBJ(!vtxp->hasSinks(), vtxp, "Should not delete used vertex");
|
||||||
|
const DfgVertexVar* const varp = vtxp->cast<DfgVertexVar>();
|
||||||
|
UASSERT_OBJ(!varp || !varp->hasPrev(), vtxp, "Deleting variable consumed via DfgPrev");
|
||||||
|
|
||||||
// Invalidate cache entry
|
// Invalidate cache entry
|
||||||
m_cache.invalidate(vtxp);
|
m_cache.invalidate(vtxp);
|
||||||
|
|
@ -293,7 +295,6 @@ class V3DfgPeephole final : public DfgVisitor {
|
||||||
// This pass only removes variables that are either not driven in this graph,
|
// This pass only removes variables that are either not driven in this graph,
|
||||||
// or are not observable outside the graph. If there is also no external write
|
// or are not observable outside the graph. If there is also no external write
|
||||||
// to the variable and no references in other graph then delete the Ast var too.
|
// to the variable and no references in other graph then delete the Ast var too.
|
||||||
const DfgVertexVar* const varp = vtxp->cast<DfgVertexVar>();
|
|
||||||
if (varp && !varp->isVolatile() && !varp->hasDfgRefs()) {
|
if (varp && !varp->isVolatile() && !varp->hasDfgRefs()) {
|
||||||
m_ctx.m_deleteps.push_back(varp->vscp());
|
m_ctx.m_deleteps.push_back(varp->vscp());
|
||||||
VL_DO_DANGLING(vtxp->unlinkDelete(m_dfg), vtxp);
|
VL_DO_DANGLING(vtxp->unlinkDelete(m_dfg), vtxp);
|
||||||
|
|
|
||||||
|
|
@ -56,16 +56,6 @@ class DfgRegularize final {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unordered_set<const DfgVertexVar*> gatherCyclicVariables() {
|
|
||||||
DfgUserMap<uint64_t> vtx2Scc = m_dfg.makeUserMap<uint64_t>();
|
|
||||||
V3DfgPasses::colorStronglyConnectedComponents(m_dfg, vtx2Scc);
|
|
||||||
std::unordered_set<const DfgVertexVar*> circularVariables;
|
|
||||||
for (const DfgVertexVar& vtx : m_dfg.varVertices()) {
|
|
||||||
if (vtx2Scc[vtx]) circularVariables.emplace(&vtx);
|
|
||||||
}
|
|
||||||
return circularVariables;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isUnused(const DfgVertex& vtx) {
|
static bool isUnused(const DfgVertex& vtx) {
|
||||||
if (vtx.hasSinks()) return false;
|
if (vtx.hasSinks()) return false;
|
||||||
if (const DfgVertexVar* const varp = vtx.cast<DfgVertexVar>()) {
|
if (const DfgVertexVar* const varp = vtx.cast<DfgVertexVar>()) {
|
||||||
|
|
@ -73,6 +63,7 @@ class DfgRegularize final {
|
||||||
UASSERT_OBJ(!varp->hasDfgRefs(), varp, "Should not have refs in other DfgGraph");
|
UASSERT_OBJ(!varp->hasDfgRefs(), varp, "Should not have refs in other DfgGraph");
|
||||||
if (varp->hasModWrRefs()) return false;
|
if (varp->hasModWrRefs()) return false;
|
||||||
if (varp->hasExtRefs()) return false;
|
if (varp->hasExtRefs()) return false;
|
||||||
|
if (varp->hasPrev()) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -87,6 +78,9 @@ class DfgRegularize final {
|
||||||
&aVtx, "Mismatched vertices");
|
&aVtx, "Mismatched vertices");
|
||||||
UASSERT_OBJ(!aVtx.is<DfgVertexVar>(), &aVtx, "Should be an operation vertex");
|
UASSERT_OBJ(!aVtx.is<DfgVertexVar>(), &aVtx, "Should be an operation vertex");
|
||||||
|
|
||||||
|
// Prev is just a variable reference
|
||||||
|
if (aVtx.is<DfgPrev>()) return false;
|
||||||
|
|
||||||
if (bVtx.hasMultipleSinks()) {
|
if (bVtx.hasMultipleSinks()) {
|
||||||
// Add a temporary if it's cheaper to store and load from memory than recompute
|
// Add a temporary if it's cheaper to store and load from memory than recompute
|
||||||
if (!aVtx.isCheaperThanLoad()) return true;
|
if (!aVtx.isCheaperThanLoad()) return true;
|
||||||
|
|
@ -119,10 +113,6 @@ class DfgRegularize final {
|
||||||
}
|
}
|
||||||
|
|
||||||
void eliminateVars() {
|
void eliminateVars() {
|
||||||
// Although we could eliminate some circular variables, doing so would
|
|
||||||
// make UNOPTFLAT traces fairly usesless, so we will not do so.
|
|
||||||
const std::unordered_set<const DfgVertexVar*> circularVariables = gatherCyclicVariables();
|
|
||||||
|
|
||||||
// Worklist based algoritm
|
// Worklist based algoritm
|
||||||
DfgWorklist workList{m_dfg};
|
DfgWorklist workList{m_dfg};
|
||||||
|
|
||||||
|
|
@ -141,7 +131,7 @@ class DfgRegularize final {
|
||||||
});
|
});
|
||||||
// Delete corresponsing Ast variable at the end
|
// Delete corresponsing Ast variable at the end
|
||||||
if (const DfgVertexVar* const varp = vtx.cast<DfgVertexVar>()) {
|
if (const DfgVertexVar* const varp = vtx.cast<DfgVertexVar>()) {
|
||||||
m_ctx.m_deleteps.push_back(varp->vscp());
|
if (!varp->hasPrev()) m_ctx.m_deleteps.push_back(varp->vscp());
|
||||||
}
|
}
|
||||||
// Remove the unused vertex
|
// Remove the unused vertex
|
||||||
vtx.unlinkDelete(m_dfg);
|
vtx.unlinkDelete(m_dfg);
|
||||||
|
|
@ -173,7 +163,7 @@ class DfgRegularize final {
|
||||||
UASSERT_OBJ(!varp->hasDfgRefs(), varp, "Should not have refs in other DfgGraph");
|
UASSERT_OBJ(!varp->hasDfgRefs(), varp, "Should not have refs in other DfgGraph");
|
||||||
|
|
||||||
// Do not eliminate circular variables - need to preserve UNOPTFLAT traces
|
// Do not eliminate circular variables - need to preserve UNOPTFLAT traces
|
||||||
if (circularVariables.count(varp)) return;
|
if (varp->hasPrev()) return;
|
||||||
|
|
||||||
// Do not inline if partially driven (the partial driver network can't be fed into
|
// Do not inline if partially driven (the partial driver network can't be fed into
|
||||||
// arbitrary logic. TODO: we should peeophole these away entirely)
|
// arbitrary logic. TODO: we should peeophole these away entirely)
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,8 @@ protected:
|
||||||
*DfgDataType::fromAst(vscp->varp()->dtypep())}
|
*DfgDataType::fromAst(vscp->varp()->dtypep())}
|
||||||
, m_vscp{vscp} {
|
, m_vscp{vscp} {
|
||||||
// Increment reference count
|
// Increment reference count
|
||||||
m_vscp->user1(m_vscp->user1() + 0x20);
|
m_vscp->user1(m_vscp->user1() + 0x40);
|
||||||
UASSERT_OBJ((m_vscp->user1() >> 5) > 0, m_vscp, "Reference count overflow");
|
UASSERT_OBJ((m_vscp->user1() >> 6) > 0, m_vscp, "Reference count overflow");
|
||||||
// Allocate sources
|
// Allocate sources
|
||||||
newInput();
|
newInput();
|
||||||
newInput();
|
newInput();
|
||||||
|
|
@ -72,8 +72,8 @@ protected:
|
||||||
public:
|
public:
|
||||||
~DfgVertexVar() {
|
~DfgVertexVar() {
|
||||||
// Decrement reference count
|
// Decrement reference count
|
||||||
m_vscp->user1(m_vscp->user1() - 0x20);
|
m_vscp->user1(m_vscp->user1() - 0x40);
|
||||||
UASSERT_OBJ((m_vscp->user1() >> 5) >= 0, m_vscp, "Reference count underflow");
|
UASSERT_OBJ((m_vscp->user1() >> 6) >= 0, m_vscp, "Reference count underflow");
|
||||||
}
|
}
|
||||||
ASTGEN_MEMBERS_DfgVertexVar;
|
ASTGEN_MEMBERS_DfgVertexVar;
|
||||||
|
|
||||||
|
|
@ -87,7 +87,6 @@ public:
|
||||||
std::string srcName(size_t idx) const override final { return idx ? "defaultp" : "srcp"; }
|
std::string srcName(size_t idx) const override final { return idx ? "defaultp" : "srcp"; }
|
||||||
|
|
||||||
// The Ast variable this vertex representess
|
// The Ast variable this vertex representess
|
||||||
// AstVar* varp() const { return m_varp; }
|
|
||||||
AstVarScope* vscp() const { return m_vscp; }
|
AstVarScope* vscp() const { return m_vscp; }
|
||||||
|
|
||||||
// If this is a temporary, the Ast variable it stands for, or same as
|
// If this is a temporary, the Ast variable it stands for, or same as
|
||||||
|
|
@ -100,7 +99,7 @@ public:
|
||||||
void driverFileLine(FileLine* flp) { m_driverFileLine = flp; }
|
void driverFileLine(FileLine* flp) { m_driverFileLine = flp; }
|
||||||
|
|
||||||
// Variable referenced from other DFG in the same module/netlist
|
// Variable referenced from other DFG in the same module/netlist
|
||||||
bool hasDfgRefs() const { return m_vscp->user1() >> 6; } // I.e.: (nodep()->user1() >> 5) > 1
|
bool hasDfgRefs() const { return m_vscp->user1() >> 7; } // I.e.: (nodep()->user1() >> 6) > 1
|
||||||
|
|
||||||
// Variable referenced from Ast code in the same module/netlist
|
// Variable referenced from Ast code in the same module/netlist
|
||||||
static bool hasModWrRefs(const AstVarScope* nodep) { return nodep->user1() & 0x08; }
|
static bool hasModWrRefs(const AstVarScope* nodep) { return nodep->user1() & 0x08; }
|
||||||
|
|
@ -121,8 +120,15 @@ public:
|
||||||
static bool hasRWRefs(const AstVarScope* nodep) { return nodep->user1() & 0x10; }
|
static bool hasRWRefs(const AstVarScope* nodep) { return nodep->user1() & 0x10; }
|
||||||
static void setHasRWRefs(AstVarScope* nodep) { nodep->user1(nodep->user1() | 0x10); }
|
static void setHasRWRefs(AstVarScope* nodep) { nodep->user1(nodep->user1() | 0x10); }
|
||||||
|
|
||||||
// True iff the value of this variable is read outside this DfgGraph
|
// There exists a DfgPrev vertex for this variable
|
||||||
|
static bool hasPrev(const AstVarScope* nodep) { return nodep->user1() & 0x20; }
|
||||||
|
bool hasPrev() const { return hasPrev(m_vscp); }
|
||||||
|
|
||||||
|
// True iff this variable is consumed without an explicit sink: its value is read outside
|
||||||
|
// this DfgGraph, or a DfgPrev reads it within this graph.
|
||||||
bool isObserved() const {
|
bool isObserved() const {
|
||||||
|
// A DfgPrev reads this variable
|
||||||
|
if (hasPrev()) return true;
|
||||||
// A DfgVarVertex is written in exactly one DfgGraph, and might be read in an arbitrary
|
// A DfgVarVertex is written in exactly one DfgGraph, and might be read in an arbitrary
|
||||||
// number of other DfgGraphs. If it's driven in this DfgGraph, it's read in others.
|
// number of other DfgGraphs. If it's driven in this DfgGraph, it's read in others.
|
||||||
if (hasDfgRefs()) return srcp() || defaultp();
|
if (hasDfgRefs()) return srcp() || defaultp();
|
||||||
|
|
@ -162,6 +168,33 @@ public:
|
||||||
ASTGEN_MEMBERS_DfgVarPacked;
|
ASTGEN_MEMBERS_DfgVarPacked;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DfgPrev final : public DfgVertex {
|
||||||
|
// Previous value of variable, before any updates made in this graph.
|
||||||
|
// Used to break combinational cycles.
|
||||||
|
friend class DfgVertex;
|
||||||
|
friend class DfgVisitor;
|
||||||
|
|
||||||
|
AstVarScope* const m_vscp; // The AstVarScope associated with this vertex (not owned)
|
||||||
|
|
||||||
|
public:
|
||||||
|
DfgPrev(DfgGraph& dfg, AstVarScope* vscp)
|
||||||
|
: DfgVertex{dfg, dfgType(), vscp->varp()->fileline(),
|
||||||
|
*DfgDataType::fromAst(vscp->varp()->dtypep())}
|
||||||
|
, m_vscp{vscp} {
|
||||||
|
UASSERT_OBJ(!DfgVertexVar::hasPrev(vscp), vscp, "Variable already has a DfgPrev");
|
||||||
|
m_vscp->user1(m_vscp->user1() | 0x20); // Mark having a DfgPrev
|
||||||
|
}
|
||||||
|
~DfgPrev() {
|
||||||
|
m_vscp->user1(m_vscp->user1() & ~0x20); // Unmark having a DfgPrev
|
||||||
|
}
|
||||||
|
ASTGEN_MEMBERS_DfgPrev;
|
||||||
|
|
||||||
|
// The Ast variable this vertex representess
|
||||||
|
AstVarScope* vscp() const { return m_vscp; }
|
||||||
|
|
||||||
|
std::string srcName(size_t) const override final { return ""; }
|
||||||
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Ast reference vertices
|
// Ast reference vertices
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1466,7 +1466,9 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
|
||||||
DECL_OPTION("-fdead-cells", FOnOff, &m_fDeadCells);
|
DECL_OPTION("-fdead-cells", FOnOff, &m_fDeadCells);
|
||||||
DECL_OPTION("-fdedup", FOnOff, &m_fDedupe);
|
DECL_OPTION("-fdedup", FOnOff, &m_fDedupe);
|
||||||
DECL_OPTION("-fdfg", CbFOnOff, [this](bool flag) { m_fDfg = flag; });
|
DECL_OPTION("-fdfg", CbFOnOff, [this](bool flag) { m_fDfg = flag; });
|
||||||
DECL_OPTION("-fdfg-break-cycles", FOnOff, &m_fDfgBreakCycles);
|
DECL_OPTION("-fdfg-break-cycles", CbFOnOff, [fl](bool) {
|
||||||
|
fl->v3warn(DEPRECATED, "Option '-fdfg-break-cycles' is deprecated and has no effect");
|
||||||
|
});
|
||||||
DECL_OPTION("-fdfg-peephole", FOnOff, &m_fDfgPeephole);
|
DECL_OPTION("-fdfg-peephole", FOnOff, &m_fDfgPeephole);
|
||||||
DECL_OPTION("-fdfg-peephole-", CbPartialMatch, [this](const char* optp) { //
|
DECL_OPTION("-fdfg-peephole-", CbPartialMatch, [this](const char* optp) { //
|
||||||
m_fDfgPeepholeDisabled.erase(optp);
|
m_fDfgPeepholeDisabled.erase(optp);
|
||||||
|
|
|
||||||
|
|
@ -402,7 +402,6 @@ private:
|
||||||
bool m_fConstBitOpTree; // main switch: -fno-const-bit-op-tree constant bit op tree
|
bool m_fConstBitOpTree; // main switch: -fno-const-bit-op-tree constant bit op tree
|
||||||
bool m_fConstEager = true; // main switch: -fno-const-eagerly run V3Const during passes
|
bool m_fConstEager = true; // main switch: -fno-const-eagerly run V3Const during passes
|
||||||
bool m_fDedupe; // main switch: -fno-dedupe: logic deduplication
|
bool m_fDedupe; // main switch: -fno-dedupe: logic deduplication
|
||||||
bool m_fDfgBreakCycles = true; // main switch: -fno-dfg-break-cycles
|
|
||||||
bool m_fDfgPeephole = true; // main switch: -fno-dfg-peephole
|
bool m_fDfgPeephole = true; // main switch: -fno-dfg-peephole
|
||||||
bool m_fDfgPushDownSels = true; // main switch: -fno-dfg-push-down-sels
|
bool m_fDfgPushDownSels = true; // main switch: -fno-dfg-push-down-sels
|
||||||
bool m_fDfg; // main switch: -fno-dfg
|
bool m_fDfg; // main switch: -fno-dfg
|
||||||
|
|
@ -743,7 +742,6 @@ public:
|
||||||
bool fConstEager() const { return m_fConstEager; }
|
bool fConstEager() const { return m_fConstEager; }
|
||||||
bool fDedupe() const { return m_fDedupe; }
|
bool fDedupe() const { return m_fDedupe; }
|
||||||
bool fDfg() const { return m_fDfg; }
|
bool fDfg() const { return m_fDfg; }
|
||||||
bool fDfgBreakCycles() const { return m_fDfgBreakCycles; }
|
|
||||||
bool fDfgPeephole() const { return m_fDfgPeephole; }
|
bool fDfgPeephole() const { return m_fDfgPeephole; }
|
||||||
bool fDfgPushDownSels() const { return m_fDfgPushDownSels; }
|
bool fDfgPushDownSels() const { return m_fDfgPushDownSels; }
|
||||||
bool fDfgSynthesizeAll() const { return m_fDfgSynthesizeAll; }
|
bool fDfgSynthesizeAll() const { return m_fDfgSynthesizeAll; }
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ with open(rdFile, 'r', encoding="utf8") as rdFh, \
|
||||||
test.compile(verilator_flags2=[
|
test.compile(verilator_flags2=[
|
||||||
"--stats",
|
"--stats",
|
||||||
"--build",
|
"--build",
|
||||||
"-fno-dfg" if test.name == "t_dfg_break_cycles" else "-fno-dfg-break-cycles",
|
"-fno-dfg",
|
||||||
"-fno-gate",
|
"-fno-gate",
|
||||||
"+incdir+" + test.obj_dir,
|
"+incdir+" + test.obj_dir,
|
||||||
"-Mdir", test.obj_dir + "/obj_ref",
|
"-Mdir", test.obj_dir + "/obj_ref",
|
||||||
|
|
@ -76,9 +76,8 @@ test.compile(verilator_flags2=[
|
||||||
]) # yapf:disable
|
]) # yapf:disable
|
||||||
|
|
||||||
# Check we got the expected number of circular logic warnings
|
# Check we got the expected number of circular logic warnings
|
||||||
if test.name == "t_dfg_break_cycles":
|
test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt",
|
||||||
test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt",
|
r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles)
|
||||||
r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles)
|
|
||||||
|
|
||||||
# Compile optimized - also builds executable
|
# Compile optimized - also builds executable
|
||||||
test.compile(verilator_flags2=[
|
test.compile(verilator_flags2=[
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify it
|
|
||||||
# under the terms of either the GNU Lesser General Public License Version 3
|
|
||||||
# or the Perl Artistic License Version 2.0.
|
|
||||||
# SPDX-FileCopyrightText: 2025 Wilson Snyder
|
|
||||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
||||||
|
|
||||||
import vltest_bootstrap
|
|
||||||
|
|
||||||
import runpy
|
|
||||||
|
|
||||||
test.scenarios('vlt_all')
|
|
||||||
|
|
||||||
runpy.run_path("t/t_dfg_break_cycles.py", globals())
|
|
||||||
|
|
@ -11,7 +11,9 @@ module t (
|
||||||
rand_a, rand_b, srand_a, srand_b, arand_a, arand_b
|
rand_a, rand_b, srand_a, srand_b, arand_a, arand_b
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// verilator lint_off UNOPTFLAT
|
||||||
`include "portdecl.vh" // Boilerplate generated by t_dfg_peephole.py
|
`include "portdecl.vh" // Boilerplate generated by t_dfg_peephole.py
|
||||||
|
// verilator lint_on UNOPTFLAT
|
||||||
|
|
||||||
input rand_a;
|
input rand_a;
|
||||||
input rand_b;
|
input rand_b;
|
||||||
|
|
@ -429,6 +431,17 @@ module t (
|
||||||
`signal(NARROW_CONCAT_B, narrow_concat[8:4]);
|
`signal(NARROW_CONCAT_B, narrow_concat[8:4]);
|
||||||
`signal(NARROW_CONCAT_C, narrow_concat[5:4]);
|
`signal(NARROW_CONCAT_C, narrow_concat[5:4]);
|
||||||
|
|
||||||
|
// Cycle needing a DfgPrev (variable shift). Peephole must keep the internal
|
||||||
|
// 'break_lo' driver, as the DfgPrev reads its stored value. Shift forced non-zero
|
||||||
|
// for a unique fixed point.
|
||||||
|
// verilator lint_off UNOPTFLAT
|
||||||
|
wire [63:0] break_lo;
|
||||||
|
wire [63:0] break_hi;
|
||||||
|
assign break_hi = (break_lo >> (rand_b[2:0] | 3'b1)) | rand_a;
|
||||||
|
assign break_lo = break_hi & rand_b;
|
||||||
|
// verilator lint_on UNOPTFLAT
|
||||||
|
`signal(DFG_PREV_KEEPS_DRIVER, break_hi);
|
||||||
|
|
||||||
// Assigned at the end to avoid inlining by other passes
|
// Assigned at the end to avoid inlining by other passes
|
||||||
assign const_a = 64'h0123456789abcdef;
|
assign const_a = 64'h0123456789abcdef;
|
||||||
assign const_b = 64'h98badefc10325647;
|
assign const_b = 64'h98badefc10325647;
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,5 @@
|
||||||
%Warning-DEPRECATED: Option '-fno-dfg-pre-inline' is deprecated and has no effect
|
%Warning-DEPRECATED: Option '-fno-dfg-pre-inline' is deprecated and has no effect
|
||||||
%Warning-DEPRECATED: Option '-fno-dfg-post-inline' is deprecated and has no effect
|
%Warning-DEPRECATED: Option '-fno-dfg-post-inline' is deprecated and has no effect
|
||||||
%Warning-DEPRECATED: Option '-fno-dfg-scoped' is deprecated, use '-fno-dfg' instead.
|
%Warning-DEPRECATED: Option '-fno-dfg-scoped' is deprecated, use '-fno-dfg' instead.
|
||||||
|
%Warning-DEPRECATED: Option '-fdfg-break-cycles' is deprecated and has no effect
|
||||||
%Error: Exiting due to
|
%Error: Exiting due to
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import vltest_bootstrap
|
||||||
test.scenarios('vlt')
|
test.scenarios('vlt')
|
||||||
|
|
||||||
test.lint(verilator_flags2=[
|
test.lint(verilator_flags2=[
|
||||||
"--trace-fst-thread --trace-threads 2 --order-clock-delay --clk foo --no-clk bar -fno-dfg-pre-inline -fno-dfg-post-inline -fno-dfg-scoped"
|
"--trace-fst-thread --trace-threads 2 --order-clock-delay --clk foo --no-clk bar -fno-dfg-pre-inline -fno-dfg-post-inline -fno-dfg-scoped -fno-dfg-break-cycles"
|
||||||
],
|
],
|
||||||
fails=True,
|
fails=True,
|
||||||
expect_filename=test.golden_filename)
|
expect_filename=test.golden_filename)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue