mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 01:54:01 +02:00
Improve automatic selection of logic for Dfg synthesis (#6370)
Reduce set of synthesized logic to be more in-line with what Dfg used to handle before + drivers of circular variables. This was always the intention but the previous algorithm was both a bit too eager, and also missed some circular variables. We can add back more heuristics based on performance measurements for non-circular logic later.
This commit is contained in:
+181
-121
@@ -535,6 +535,7 @@ class AstToDfgSynthesize final {
|
||||
AstToDfgConverter<T_Scoped> m_converter; // The convert instance to use for each construct
|
||||
size_t m_nBranchCond = 0; // Sequence numbers for temporaries
|
||||
size_t m_nPathPred = 0; // Sequence numbers for temporaries
|
||||
DfgWorklist m_toRevert{m_dfg}; // We need a worklist for reverting synthesis
|
||||
|
||||
// STATE - for current DfgLogic being synthesized
|
||||
DfgLogic* m_logicp = nullptr; // Current logic vertex we are synthesizing
|
||||
@@ -1587,46 +1588,102 @@ class AstToDfgSynthesize final {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Revert synthesis of the given DfgLogic
|
||||
void revert(DfgLogic& vtx) {
|
||||
for (DfgVertex* const p : vtx.synth()) VL_DO_DANGLING(p->unlinkDelete(m_dfg), p);
|
||||
vtx.synth().clear();
|
||||
}
|
||||
|
||||
// Revert all logic driving the given unresolved driver, delete it,
|
||||
// and transitively the same for variables driven by the reverted logic.
|
||||
void revertTransivelyAndRemove(DfgUnresolved* vtxp, VDouble0& statCountr) {
|
||||
// The result variable will be driven from Ast code, mark as such
|
||||
vtxp->singleSink()->as<DfgVertexVar>()->setHasModWrRefs();
|
||||
|
||||
// Gather all logic driving this unresolved driver
|
||||
std::vector<DfgLogic*> logicps;
|
||||
logicps.reserve(vtxp->nInputs());
|
||||
vtxp->foreachSource([&](DfgVertex& src) {
|
||||
if (DfgLogic* const p = src.cast<DfgLogic>()) logicps.emplace_back(p);
|
||||
return false;
|
||||
});
|
||||
|
||||
// Delete the unresolved driver
|
||||
VL_DO_DANGLING(vtxp->unlinkDelete(m_dfg), vtxp);
|
||||
|
||||
// Transitively for the rest
|
||||
for (DfgLogic* const logicp : logicps) {
|
||||
if (!logicp->synth().empty()) {
|
||||
// Revert all DfgLogic in m_toRevert, or DfgLogic driving the DfgUnresolved
|
||||
// vertices in m_toRevert, and transitively the same for any DfgUnresolved
|
||||
// driven by the reverted DfgLogic. Delete all DfgUnresolved involed.
|
||||
void revert(VDouble0& statCountr) {
|
||||
m_toRevert.foreach([&](DfgVertex& vtx) {
|
||||
// Process DfgLogic
|
||||
if (DfgLogic* const vtxp = vtx.cast<DfgLogic>()) {
|
||||
UASSERT_OBJ(vtxp->selectedForSynthesis(), vtxp, "Shouldn't reach here unselected");
|
||||
// Revert this logic
|
||||
UASSERT_OBJ(!vtxp->reverted(), vtxp, "Should be reverting now");
|
||||
++statCountr;
|
||||
revert(*logicp);
|
||||
for (DfgVertex* const p : vtxp->synth()) VL_DO_DANGLING(p->unlinkDelete(m_dfg), p);
|
||||
vtxp->synth().clear();
|
||||
vtxp->setReverted();
|
||||
// Add all DfgUnresolved it drives to the work list
|
||||
vtxp->foreachSink([&](DfgVertex& snk) {
|
||||
m_toRevert.push_front(*snk.as<DfgUnresolved>());
|
||||
return false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
while (DfgVertex* const sinkp = logicp->firtsSinkp()) {
|
||||
revertTransivelyAndRemove(sinkp->as<DfgUnresolved>(), statCountr);
|
||||
|
||||
// Process DfgUnresolved
|
||||
if (DfgUnresolved* const vtxp = vtx.cast<DfgUnresolved>()) {
|
||||
// The result variable will be driven from Ast code, mark as such
|
||||
vtxp->singleSink()->as<DfgVertexVar>()->setHasModWrRefs();
|
||||
// Add all driving DfgLogic to the work list
|
||||
vtxp->foreachSource([&](DfgVertex& src) {
|
||||
DfgLogic* const lp = src.cast<DfgLogic>();
|
||||
if (lp && !lp->reverted()) m_toRevert.push_front(*lp);
|
||||
return false;
|
||||
});
|
||||
// Delete the DfgUnresolved driver
|
||||
VL_DO_DANGLING(vtxp->unlinkDelete(m_dfg), vtxp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// There should be nothing else on the worklist
|
||||
vtx.v3fatalSrc("Unexpected vertex type");
|
||||
});
|
||||
}
|
||||
|
||||
// Synthesize all of the given vertices
|
||||
void main(const std::vector<DfgLogic*>& logicps) {
|
||||
void main() {
|
||||
//-------------------------------------------------------------------
|
||||
UINFO(5, "Step 1: Attempting to synthesize each of the given DfgLogic");
|
||||
for (DfgLogic* const logicp : logicps) {
|
||||
UINFO(5, "Step 0: Remove all DfgLogic not selected for synthesis");
|
||||
for (DfgVertex* const vtxp : m_dfg.opVertices().unlinkable()) {
|
||||
// Only processing DfgUnresolved
|
||||
if (!vtxp->is<DfgUnresolved>()) continue;
|
||||
bool anySelected = false;
|
||||
bool anyUnselected = false;
|
||||
vtxp->foreachSource([&](DfgVertex& src) {
|
||||
const DfgLogic& logic = *src.as<DfgLogic>();
|
||||
if (logic.selectedForSynthesis()) {
|
||||
anySelected = true;
|
||||
} else {
|
||||
anyUnselected = true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
// There should be a driver
|
||||
UASSERT_OBJ(anySelected || anyUnselected, vtxp, "'DfgUnresolved' with no driver");
|
||||
// All drivers should be selected or all should be unselected
|
||||
UASSERT_OBJ(!(anySelected && anyUnselected), vtxp, "Invalid 'DfgLogic' selection");
|
||||
// If all drivers are unselected, delete this DfgUnresolved here
|
||||
if (anyUnselected) {
|
||||
// The result variable will be driven from Ast code, mark as such
|
||||
vtxp->singleSink()->as<DfgVertexVar>()->setHasModWrRefs();
|
||||
VL_DO_DANGLING(vtxp->unlinkDelete(m_dfg), vtxp);
|
||||
}
|
||||
}
|
||||
for (DfgVertex* const vtxp : m_dfg.opVertices().unlinkable()) {
|
||||
// Only processing DfgLogic
|
||||
DfgLogic* const logicp = vtxp->cast<DfgLogic>();
|
||||
if (!logicp) continue;
|
||||
if (logicp->selectedForSynthesis()) continue;
|
||||
// There should be no sinks left for unselected DfgLogic, delete them here
|
||||
UASSERT_OBJ(!logicp->hasSinks(), vtxp, "Unselected 'DfgLogic' with sinks remaining");
|
||||
// Input variables will be read in Ast code, mark as such
|
||||
logicp->foreachSource([](DfgVertex& src) {
|
||||
src.as<DfgVertexVar>()->setHasModRdRefs();
|
||||
return false;
|
||||
});
|
||||
VL_DO_DANGLING(logicp->unlinkDelete(m_dfg), logicp);
|
||||
}
|
||||
debugDump("synth-selected");
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
UINFO(5, "Step 1: Attempting to synthesize each of the selected DfgLogic");
|
||||
for (DfgVertex& vtx : m_dfg.opVertices()) {
|
||||
DfgLogic* const logicp = vtx.cast<DfgLogic>();
|
||||
if (!logicp) continue;
|
||||
|
||||
// We should only have DfgLogic remaining that was selected for synthesis
|
||||
UASSERT_OBJ(logicp->selectedForSynthesis(), logicp, "Unselected DfgLogic remains");
|
||||
|
||||
// Debug aid
|
||||
if (VL_UNLIKELY(s_dfgSynthDebugLimit)) {
|
||||
if (s_dfgSynthDebugCount == s_dfgSynthDebugLimit) break;
|
||||
@@ -1641,27 +1698,18 @@ class AstToDfgSynthesize final {
|
||||
}
|
||||
}
|
||||
|
||||
// Synthesize it, revert partial construction if failed
|
||||
if (!synthesize(*logicp)) revert(*logicp);
|
||||
// Synthesize it, if failed, enqueue for reversion
|
||||
if (!synthesize(*logicp)) {
|
||||
logicp->setNonSynthesizable();
|
||||
m_toRevert.push_front(*logicp);
|
||||
}
|
||||
}
|
||||
debugDump("synth-converted");
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
UINFO(5, "Step 2: Revert drivers of variables with unsynthesizeable drivers");
|
||||
// We do this as the variables might be multi-driven, we just can't know at this point
|
||||
for (const DfgVertexVar& var : m_dfg.varVertices()) {
|
||||
if (!var.srcp()) continue;
|
||||
DfgUnresolved* const unresolvedp = var.srcp()->cast<DfgUnresolved>();
|
||||
if (!unresolvedp) break; // Stop when reached the synthesized temporaries
|
||||
|
||||
// Check if any driver have failed to synthesize
|
||||
const bool failed = unresolvedp->foreachSource([&](DfgVertex& src) {
|
||||
DfgLogic* const driverp = src.cast<DfgLogic>();
|
||||
return driverp && driverp->synth().empty();
|
||||
});
|
||||
// Revert all logic involved
|
||||
if (failed) revertTransivelyAndRemove(unresolvedp, m_ctx.m_synt.revertNonSyn);
|
||||
}
|
||||
// We do this as the variables might be multi-driven, we can't know if synthesis failed
|
||||
revert(m_ctx.m_synt.revertNonSyn);
|
||||
debugDump("synth-reverted");
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
@@ -1689,6 +1737,7 @@ class AstToDfgSynthesize final {
|
||||
if (normalizeDrivers(var, drivers)) return makeSplice(var, drivers);
|
||||
// If mutlidriven, record and ignore
|
||||
multidrivenps.emplace_back(&var);
|
||||
m_toRevert.push_front(*unresolvedp);
|
||||
return nullptr;
|
||||
}();
|
||||
// Bail if multidriven
|
||||
@@ -1697,16 +1746,10 @@ class AstToDfgSynthesize final {
|
||||
const bool newEntry = resolvedDrivers.emplace(&var, resolvedp).second;
|
||||
UASSERT_OBJ(newEntry, &var, "Dupliacte driver");
|
||||
}
|
||||
// Mark as multidriven for future DFG runs - here, so we get all warnings above
|
||||
for (const DfgVertexVar* const vtxp : multidrivenps) vtxp->varp()->setDfgMultidriven();
|
||||
// Revert and remove drivers of multi-driven variables
|
||||
for (const DfgVertexVar* const vtxp : multidrivenps) {
|
||||
// Mark as multidriven for future DFG runs - here, so we get all warning before
|
||||
vtxp->varp()->setDfgMultidriven();
|
||||
// Might not have a driver if transitively removed on an earlier iteration
|
||||
if (!vtxp->srcp()) continue;
|
||||
// Revert all logic involved
|
||||
DfgUnresolved* const unresolvedp = vtxp->srcp()->as<DfgUnresolved>();
|
||||
revertTransivelyAndRemove(unresolvedp, m_ctx.m_synt.revertMultidrive);
|
||||
}
|
||||
revert(m_ctx.m_synt.revertMultidrive);
|
||||
// Replace all DfgUnresolved with the resolved drivers
|
||||
for (const DfgVertexVar& var : m_dfg.varVertices()) {
|
||||
if (!var.srcp()) continue;
|
||||
@@ -1720,7 +1763,7 @@ class AstToDfgSynthesize final {
|
||||
debugDump("synth-resolved");
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
UINFO(5, "Step 4: Remove all DfgLogic and DfgUnresolved");
|
||||
UINFO(5, "Step 4: Remove all remaining DfgLogic and DfgUnresolved");
|
||||
for (DfgVertex* const vtxp : m_dfg.opVertices().unlinkable()) {
|
||||
// Previous step should have removed all DfgUnresolved
|
||||
UASSERT_OBJ(!vtxp->is<DfgUnresolved>(), vtxp, "DfgUnresolved remains");
|
||||
@@ -1776,17 +1819,30 @@ class AstToDfgSynthesize final {
|
||||
}
|
||||
}
|
||||
debugDump("synth-rmsplice");
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// CONSTRUCTOR
|
||||
AstToDfgSynthesize(DfgGraph& dfg, V3DfgSynthesisContext& ctx)
|
||||
: m_dfg{dfg}
|
||||
, m_ctx{ctx}
|
||||
, m_converter{dfg, ctx} {
|
||||
main();
|
||||
}
|
||||
|
||||
public:
|
||||
static void apply(DfgGraph& dfg, V3DfgSynthesisContext& ctx) {
|
||||
AstToDfgSynthesize{dfg, ctx};
|
||||
|
||||
// Final step outside, as both AstToDfgSynthesize and removeUnused used DfgUserMap
|
||||
UINFO(5, "Step 6: Remove all unused vertices");
|
||||
V3DfgPasses::removeUnused(m_dfg);
|
||||
debugDump("synth-rmunused");
|
||||
V3DfgPasses::removeUnused(dfg);
|
||||
if (dumpDfgLevel() >= 9) dfg.dumpDotFilePrefixed(ctx.prefix() + "synth-rmunused");
|
||||
|
||||
// No operation vertex should have multiple sinks. Cyclic decomoposition
|
||||
// depends on this and it can easily be ensured by using temporaries.
|
||||
// Also, all sources should be connected at this point
|
||||
if (v3Global.opt.debugCheck()) {
|
||||
for (DfgVertex& vtx : m_dfg.opVertices()) {
|
||||
for (DfgVertex& vtx : dfg.opVertices()) {
|
||||
UASSERT_OBJ(!vtx.hasMultipleSinks(), &vtx, "Operation has multiple sinks");
|
||||
for (size_t i = 0; i < vtx.nInputs(); ++i) {
|
||||
UASSERT_OBJ(vtx.inputp(i), &vtx, "Unconnected source operand");
|
||||
@@ -1794,79 +1850,83 @@ class AstToDfgSynthesize final {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CONSTRUCTOR
|
||||
AstToDfgSynthesize(DfgGraph& dfg, const std::vector<DfgLogic*>& logicps,
|
||||
V3DfgSynthesisContext& ctx)
|
||||
: m_dfg{dfg}
|
||||
, m_ctx{ctx}
|
||||
, m_converter{dfg, ctx} {}
|
||||
|
||||
public:
|
||||
static void apply(DfgGraph& dfg, const std::vector<DfgLogic*>& logicps,
|
||||
V3DfgSynthesisContext& ctx) {
|
||||
AstToDfgSynthesize{dfg, logicps, ctx}.main(logicps);
|
||||
}
|
||||
};
|
||||
|
||||
void V3DfgPasses::synthesize(DfgGraph& dfg, V3DfgContext& ctx) {
|
||||
// The vertices to synthesize
|
||||
std::vector<DfgLogic*> logicps;
|
||||
|
||||
// Decide which DfgLogic to attempt to synthesize
|
||||
static void dfgSelectLogicForSynthesis(DfgGraph& dfg) {
|
||||
// If we are told to synthesize everything, we will do so ...
|
||||
if (v3Global.opt.fDfgSynthesizeAll()) {
|
||||
// If we are told to synthesize everything, we will do so ...
|
||||
for (DfgVertex& vtx : dfg.opVertices()) {
|
||||
if (DfgLogic* const logicp = vtx.cast<DfgLogic>()) logicps.emplace_back(logicp);
|
||||
if (DfgLogic* const logicp = vtx.cast<DfgLogic>()) logicp->setSelectedForSynthesis();
|
||||
}
|
||||
} else {
|
||||
// Otherwise figure out which vertices are worth synthesizing.
|
||||
return;
|
||||
}
|
||||
|
||||
// Find cycles
|
||||
// Otherwise figure out which vertices are likely worth synthesizing.
|
||||
|
||||
// Bather circular variables
|
||||
std::vector<DfgVertexVar*> circularVarps;
|
||||
{
|
||||
DfgUserMap<uint64_t> scc = dfg.makeUserMap<uint64_t>();
|
||||
V3DfgPasses::colorStronglyConnectedComponents(dfg, scc);
|
||||
|
||||
// First, gather variables, we will then attempt to synthesize all their drivers
|
||||
std::vector<DfgVertexVar*> varps;
|
||||
for (DfgVertexVar& var : dfg.varVertices()) {
|
||||
// Can ignore variables with no drivers
|
||||
if (!var.srcp()) continue;
|
||||
|
||||
// Circular variable - synthesize
|
||||
if (scc.at(var)) {
|
||||
varps.emplace_back(&var);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Must be driven from a DfgUnresolved at this point, pick it up
|
||||
const DfgUnresolved* const unresolvedp = var.srcp()->as<DfgUnresolved>();
|
||||
|
||||
// Inspect drivers to figure out if we should synthesize them
|
||||
const bool doIt = unresolvedp->foreachSource([&](const DfgVertex& src) {
|
||||
const DfgLogic* const logicp = src.as<DfgLogic>();
|
||||
// Synthesize continuous assignments (this is the earlier behaviour).
|
||||
// Synthesize always blocks with no more than 4 basic blocks and 4 edges
|
||||
// These are usually simple branches (if (rst) ... else ...), or close to it
|
||||
return VN_IS(logicp->nodep(), AssignW) //
|
||||
|| (logicp->cfg().nBlocks() <= 4 && logicp->cfg().nEdges() <= 4);
|
||||
});
|
||||
if (doIt) varps.emplace_back(&var);
|
||||
if (!scc.at(var)) continue;
|
||||
// This is a circular variable
|
||||
circularVarps.emplace_back(&var);
|
||||
}
|
||||
}
|
||||
|
||||
// Gather all drivers of the selected variables
|
||||
const VNUser2InUse user2InUse; // AstNode (logic) -> bool: already collected
|
||||
for (const DfgVertexVar* const varp : varps) {
|
||||
varp->srcp()->as<DfgUnresolved>()->foreachSource([&](DfgVertex& source) {
|
||||
DfgLogic* const logicp = source.as<DfgLogic>();
|
||||
if (!logicp->nodep()->user2Inc()) logicps.emplace_back(logicp);
|
||||
// We need to expand the selection to cover all drivers, use a work list
|
||||
DfgWorklist worklist{dfg};
|
||||
|
||||
// Synthesize all drivers of circular variables
|
||||
for (const DfgVertexVar* const varp : circularVarps) {
|
||||
varp->srcp()->as<DfgUnresolved>()->foreachSource([&](DfgVertex& driver) {
|
||||
worklist.push_front(*driver.as<DfgLogic>());
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Synthesize all AssignW and simple blocks driving exactly one variable
|
||||
// This is approximately the old default behaviour of Dfg
|
||||
for (DfgVertex& vtx : dfg.opVertices()) {
|
||||
DfgLogic* const logicp = vtx.cast<DfgLogic>();
|
||||
if (!logicp) continue;
|
||||
if (VN_IS(logicp->nodep(), AssignW)) {
|
||||
worklist.push_front(*logicp);
|
||||
continue;
|
||||
}
|
||||
const CfgGraph& cfg = logicp->cfg();
|
||||
if (!logicp->hasMultipleSinks() && cfg.nBlocks() <= 4 && cfg.nEdges() <= 4) {
|
||||
worklist.push_front(*logicp);
|
||||
}
|
||||
}
|
||||
|
||||
// Now expand to cover all logic driving the same set of variables and mark
|
||||
worklist.foreach([&](DfgVertex& vtx) {
|
||||
DfgLogic& logic = *vtx.as<DfgLogic>();
|
||||
UASSERT_OBJ(!logic.selectedForSynthesis(), &vtx, "Should not be visited twice");
|
||||
// Mark as selected for synthesis
|
||||
logic.setSelectedForSynthesis();
|
||||
// Enqueue all other logic driving the same variables as this one
|
||||
logic.foreachSink([&](DfgVertex& sink) {
|
||||
sink.as<DfgUnresolved>()->foreachSource([&](DfgVertex& sibling) {
|
||||
DfgLogic& siblingLogic = *sibling.as<DfgLogic>();
|
||||
if (!siblingLogic.selectedForSynthesis()) worklist.push_front(siblingLogic);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Synthesize them - also removes un-synthesized DfgLogic, so must run even if logicps.empty()
|
||||
void V3DfgPasses::synthesize(DfgGraph& dfg, V3DfgContext& ctx) {
|
||||
// Select which DfgLogic to attempt to synthesize
|
||||
dfgSelectLogicForSynthesis(dfg);
|
||||
// Synthesize them - also removes un-synthesized DfgLogic, so must run even if nothing selected
|
||||
if (dfg.modulep()) {
|
||||
AstToDfgSynthesize</* T_Scoped: */ false>::apply(dfg, logicps, ctx.m_synthContext);
|
||||
AstToDfgSynthesize</* T_Scoped: */ false>::apply(dfg, ctx.m_synthContext);
|
||||
} else {
|
||||
AstToDfgSynthesize</* T_Scoped: */ true>::apply(dfg, logicps, ctx.m_synthContext);
|
||||
AstToDfgSynthesize</* T_Scoped: */ true>::apply(dfg, ctx.m_synthContext);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user