Files
verilator/src/V3DfgContext.h
T
Geza Lore ba893abb2c Optimize circular logic in Dfg (#7902)
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.
2026-08-18 14:54:12 +02:00

432 lines
18 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Persistent context for DFG algorithms
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// 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: 2003-2026 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
//
// Various context objects hold data that need to persist across invocations
// of a DFG algorithms.
//
//*************************************************************************
#ifndef VERILATOR_V3DFGCONTEXT_H_
#define VERILATOR_V3DFGCONTEXT_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3Ast.h"
#include "V3DfgPeepholePatterns.h"
#include "V3Error.h"
#include "V3File.h"
#include "V3Global.h"
#include "V3Stats.h"
class V3DfgContext;
//######################################################################
// Base class for all context objects
class V3DfgSubContext VL_NOT_FINAL {
const std::string m_name; // Pass/algorithm name, to be used in statistics
protected:
VL_DEFINE_DEBUG_FUNCTIONS;
V3DfgSubContext(const std::string& name)
: m_name{name} {}
void addStat(const std::string& what, double value) {
V3Stats::addStat("Optimizations, DFG, " + m_name + ", " + what, value);
}
};
//######################################################################
// Contexts for various algorithms - keep sorted
class V3DfgAstToDfgContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_inputs; // Number of input processes (logic constructs)
VDouble0 m_representable; // Number of combinational equations representable
VDouble0 m_nonRepCfg; // Non-representable due to failing to build CFG
VDouble0 m_nonRepLive; // Non-representable due to failing liveness analysis
VDouble0 m_nonRepVar; // Non-representable due to unsupported variable properties
private:
V3DfgAstToDfgContext()
: V3DfgSubContext{"AstToDfg"} {}
~V3DfgAstToDfgContext() {
addStat("input processes", m_inputs);
addStat("representable", m_representable);
addStat("non-representable (cfg)", m_nonRepCfg);
addStat("non-representable (live)", m_nonRepLive);
addStat("non-representable (var)", m_nonRepVar);
// Check the stats are consistent
UASSERT(m_representable + m_nonRepCfg + m_nonRepLive + m_nonRepVar == m_inputs,
"Inconsistent statistics");
}
};
class V3DfgBinToOneHotContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_decodersCreated; // Number of bianry to one-hot decoders created
private:
V3DfgBinToOneHotContext()
: V3DfgSubContext{"BinToOneHot"} {}
~V3DfgBinToOneHotContext() { addStat("decoders created", m_decodersCreated); }
};
class V3DfgBreakCyclesContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_nFixed; // Number of graphs that became acyclic
VDouble0 m_nImproved; // Number of graphs that were improved but still cyclic
VDouble0 m_nUnchanged; // Number of graphs that were left unchanged
VDouble0 m_nImprovements; // Number of changes made to graphs
VDouble0 m_nPrevInserted; // Number of Prev vertices inserted
private:
V3DfgBreakCyclesContext()
: V3DfgSubContext{"BreakCycles"} {}
~V3DfgBreakCyclesContext() {
addStat("made acyclic", m_nFixed);
addStat("improved", m_nImproved);
addStat("left unchanged", m_nUnchanged);
addStat("changes applied", m_nImprovements);
addStat("prev vertices inserted", m_nPrevInserted);
}
};
class V3DfgCseContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_eliminated; // Number of common sub-expressions eliminated
private:
explicit V3DfgCseContext(const std::string& label)
: V3DfgSubContext{"CSE " + label} {}
~V3DfgCseContext() { addStat("expressions eliminated", m_eliminated); }
};
class V3DfgDfgToAstContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_outputVariables; // Number of output variables
VDouble0 m_outputVariablesWithDefault; // Number of outptu variables with a default driver
VDouble0 m_resultEquations; // Number of result combinational equations
VDouble0 m_expressionsInlined; // Number of expressions inlined directly into the Ast
VDouble0 m_varRefsSubstituted; // Number of variable references substituted in the Ast
private:
V3DfgDfgToAstContext()
: V3DfgSubContext{"DfgToAst"} {}
~V3DfgDfgToAstContext() {
addStat("output variables", m_outputVariables);
addStat("output variables with default driver", m_outputVariablesWithDefault);
addStat("result equations", m_resultEquations);
addStat("expressions inlined", m_expressionsInlined);
addStat("var refs substituted", m_varRefsSubstituted);
}
};
class V3DfgPeepholeContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
// Enable flags for each optimization
std::array<bool, VDfgPeepholePattern::_ENUM_END> m_enabled;
// Count of applications for each optimization (for statistics)
std::array<VDouble0, VDfgPeepholePattern::_ENUM_END> m_count;
std::vector<AstNode*> m_deleteps; // AstVar/AstVarScope that can be deleted at the end
private:
V3DfgPeepholeContext()
: V3DfgSubContext{"Peephole"} {
const auto checkEnabled = [this](VDfgPeepholePattern id) {
std::string str{id.ascii()};
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { //
return c == '_' ? '-' : std::tolower(c);
});
m_enabled[id] = v3Global.opt.fDfgPeepholeEnabled(str);
};
#define OPTIMIZATION_CHECK_ENABLED(id, name) checkEnabled(VDfgPeepholePattern::id);
FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_CHECK_ENABLED)
#undef OPTIMIZATION_CHECK_ENABLED
}
~V3DfgPeepholeContext() {
for (AstNode* const nodep : m_deleteps) {
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
const auto emitStat = [this](VDfgPeepholePattern id) {
std::string str{id.ascii()};
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { //
return c == '_' ? ' ' : std::tolower(c);
});
addStat(str, m_count[id]);
};
#define OPTIMIZATION_EMIT_STATS(id, name) emitStat(VDfgPeepholePattern::id);
FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(OPTIMIZATION_EMIT_STATS)
#undef OPTIMIZATION_EMIT_STATS
}
};
class V3DfgPushDownSelsContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
size_t m_pushedDown = 0; // Number of selects pushed down through concatenations
size_t m_wouldBeCyclic = 0; // Number of selects not pushed due to cycle
private:
V3DfgPushDownSelsContext()
: V3DfgSubContext{"PushDownSels"} {}
~V3DfgPushDownSelsContext() {
addStat("sels pushed down", m_pushedDown);
addStat("would be cyclic", m_wouldBeCyclic);
}
};
class V3DfgRegularizeContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
std::vector<AstNode*> m_deleteps; // AstVar/AstVarScope that can be deleted at the end
VDouble0 m_usedVarsInlined; // Number of used variables inlined
VDouble0 m_unusedRemoved; // Number of unused vertices remoevd
VDouble0 m_temporariesIntroduced; // Number of temporaries introduced for reuse
private:
V3DfgRegularizeContext()
: V3DfgSubContext{"Regularize"} {}
~V3DfgRegularizeContext() {
for (AstNode* const nodep : m_deleteps) {
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
addStat("used variables inlined", m_usedVarsInlined);
addStat("unused vertices removed", m_unusedRemoved);
addStat("temporaries introduced", m_temporariesIntroduced);
}
};
class V3DfgRemoveSelectsContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_removedFullWidth; // Number of full width selects removed
VDouble0 m_replacedWithSelFromFull; // Number of selects replaced with sel from full driver
VDouble0 m_replacedWithSelFromPart; // Number of selects replaced with sel from partial driver
VDouble0 m_replacedWithPart; // Number of selects replaced with part of driver
private:
V3DfgRemoveSelectsContext()
: V3DfgSubContext{"RemoveSelects"} {}
~V3DfgRemoveSelectsContext() {
addStat("full width selects removed", m_removedFullWidth);
addStat("replaced with sel from full driver", m_replacedWithSelFromFull);
addStat("replaced with sel from partial driver", m_replacedWithSelFromPart);
addStat("replaced with partial driver", m_replacedWithPart);
}
};
class V3DfgRemoveUnobservableContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
VDouble0 m_varsRemoved; // Number of variables removed from the Dfg
VDouble0 m_varsDeleted; // Number of variables removed from the Dfg and the Ast
VDouble0 m_logicRemoved; // Number of logic blocks removed from the Dfg
VDouble0 m_logicDeleted; // Number of logic blocks removed from the Dfg and the Ast
private:
V3DfgRemoveUnobservableContext()
: V3DfgSubContext{"RemoveUnobservable"} {}
~V3DfgRemoveUnobservableContext() {
addStat("variables removed", m_varsRemoved);
addStat("variables deleted", m_varsDeleted);
addStat("logic removed", m_logicRemoved);
addStat("logic deleted", m_logicDeleted);
}
};
class V3DfgSynthesisContext final : public V3DfgSubContext {
// Only V3DfgContext can create an instance
friend class V3DfgContext;
public:
// STATE
// Stats for conversion
struct {
// Inputs
VDouble0 inputAssignments; // Number of input assignments
VDouble0 inputExpressions; // Number of input equations
// Successful
VDouble0 representable; // Number of representable constructs
// Unsuccessful
VDouble0 nonRepImpure; // Non representable: impure
VDouble0 nonRepDType; // Non representable: unsupported data type
VDouble0 nonRepLValue; // Non representable: unsupported LValue form
VDouble0 nonRepVarRef; // Non representable: unsupported var reference
VDouble0 nonRepOOBSel; // Non representable: out of bounds select
VDouble0 nonRepNode; // Non representable: unsupported AstNode type
VDouble0 nonRepUnknown; // Non representable: unhandled AstNode type
} m_conv;
// Stats for synthesis
struct {
// Inputs
VDouble0 inputAlways; // Number of always blocks attempted
VDouble0 inputAssign; // Number of continuous assignments attempted
// Successful
VDouble0 synthAlways; // Number of always blocks successfully synthesized
VDouble0 synthAssign; // Number of continuous assignments successfully synthesized
// Unsuccessful
VDouble0 nonSynConv; // Non-synthesizable: non-representable (above)
VDouble0 nonSynExtWrite; // Non-synthesizable: has externally written variable
VDouble0 nonSynLoop; // Non-synthesizable: loop in CFG
VDouble0 nonSynStmt; // Non-synthesizable: unsupported statement
VDouble0 nonSynMultidrive; // Non-synthesizable: multidriven value within statement
VDouble0 nonSynArray; // Non-synthesizable: array type unhandled
VDouble0 nonSynLatch; // Non-synthesizable: maybe latch
VDouble0 nonSynJoinInput; // Non-synthesizable: needing to join input variable
VDouble0 nonSynFalseWrite; // Non-synthesizable: does not write output
// Reverted
VDouble0 revertNonSyn; // Reverted due to being driven from non-synthesizable vertex
VDouble0 revertMultidrive; // Reverted due to multiple drivers
// Additional stats
VDouble0 cfgTrivial; // Trivial input CFGs
VDouble0 cfgSp; // Series-paralel input CFGs
VDouble0 cfgDag; // Generic loop free input CFGs
VDouble0 cfgCyclic; // Cyclic input CFGs
VDouble0 joinUsingPathPredicate; // Num control flow joins using full path predicate
VDouble0 joinUsingBranchCondition; // Num Control flow joins using dominating branch cond
} m_synt;
private:
V3DfgSynthesisContext()
: V3DfgSubContext{"Synthesis"} {}
~V3DfgSynthesisContext() {
// Conversion statistics
addStat("conv / input assignments", m_conv.inputAssignments);
addStat("conv / input expressions", m_conv.inputExpressions);
addStat("conv / representable inputs", m_conv.representable);
addStat("conv / non-representable (impure)", m_conv.nonRepImpure);
addStat("conv / non-representable (dtype)", m_conv.nonRepDType);
addStat("conv / non-representable (lhs)", m_conv.nonRepLValue);
addStat("conv / non-representable (varref)", m_conv.nonRepVarRef);
addStat("conv / non-representable (oobsel)", m_conv.nonRepOOBSel);
addStat("conv / non-representable (node)", m_conv.nonRepNode);
addStat("conv / non-representable (unknown)", m_conv.nonRepUnknown);
VDouble0 nConvNonRep;
nConvNonRep += m_conv.nonRepImpure;
nConvNonRep += m_conv.nonRepDType;
nConvNonRep += m_conv.nonRepLValue;
nConvNonRep += m_conv.nonRepVarRef;
nConvNonRep += m_conv.nonRepOOBSel;
nConvNonRep += m_conv.nonRepNode;
nConvNonRep += m_conv.nonRepUnknown;
VDouble0 nConvExpect;
nConvExpect += m_conv.inputAssignments;
nConvExpect += m_conv.inputExpressions;
nConvExpect -= m_conv.representable;
UASSERT(nConvNonRep == nConvExpect, "Inconsistent statistics / conv");
// Synthesis statistics
addStat("synt / always blocks considered", m_synt.inputAlways);
addStat("synt / always blocks synthesized", m_synt.synthAlways);
addStat("synt / continuous assignments considered", m_synt.inputAssign);
addStat("synt / continuous assignments synthesized", m_synt.synthAssign);
addStat("synt / non-synthesizable (conv)", m_synt.nonSynConv);
addStat("synt / non-synthesizable (ext write)", m_synt.nonSynExtWrite);
addStat("synt / non-synthesizable (loop)", m_synt.nonSynLoop);
addStat("synt / non-synthesizable (stmt)", m_synt.nonSynStmt);
addStat("synt / non-synthesizable (multidrive)", m_synt.nonSynMultidrive);
addStat("synt / non-synthesizable (array)", m_synt.nonSynArray);
addStat("synt / non-synthesizable (latch)", m_synt.nonSynLatch);
addStat("synt / non-synthesizable (join input)", m_synt.nonSynJoinInput);
addStat("synt / non-synthesizable (false write)", m_synt.nonSynFalseWrite);
addStat("synt / reverted (non-synthesizable)", m_synt.revertNonSyn);
addStat("synt / reverted (multidrive)", m_synt.revertMultidrive);
VDouble0 nSyntNonSyn;
nSyntNonSyn += m_synt.nonSynConv;
nSyntNonSyn += m_synt.nonSynExtWrite;
nSyntNonSyn += m_synt.nonSynLoop;
nSyntNonSyn += m_synt.nonSynStmt;
nSyntNonSyn += m_synt.nonSynMultidrive;
nSyntNonSyn += m_synt.nonSynArray;
nSyntNonSyn += m_synt.nonSynLatch;
nSyntNonSyn += m_synt.nonSynJoinInput;
nSyntNonSyn += m_synt.nonSynFalseWrite;
VDouble0 nSyntExpect;
nSyntExpect += m_synt.inputAlways;
nSyntExpect += m_synt.inputAssign;
nSyntExpect -= m_synt.synthAlways;
nSyntExpect -= m_synt.synthAssign;
UASSERT(nSyntNonSyn == nSyntExpect, "Inconsistent statistics / synt");
addStat("synt / input CFG trivial", m_synt.cfgTrivial);
addStat("synt / input CFG sp", m_synt.cfgSp);
addStat("synt / input CFG dag", m_synt.cfgDag);
addStat("synt / input CFG cyclic", m_synt.cfgCyclic);
addStat("synt / joins using path predicate", m_synt.joinUsingPathPredicate);
addStat("synt / joins using branch condition", m_synt.joinUsingBranchCondition);
}
};
//######################################################################
// Top level V3DfgContext
class V3DfgContext final {
public:
// STATE
// Sub contexts - keep sorted by type
V3DfgAstToDfgContext m_ast2DfgContext;
V3DfgBinToOneHotContext m_binToOneHotContext;
V3DfgBreakCyclesContext m_breakCyclesContext;
V3DfgCseContext m_cseContext0{"1st"};
V3DfgCseContext m_cseContext1{"2nd"};
V3DfgDfgToAstContext m_dfg2AstContext;
V3DfgPeepholeContext m_peepholeContext;
V3DfgPushDownSelsContext m_pushDownSelsContext;
V3DfgRegularizeContext m_regularizeContext;
V3DfgRemoveSelectsContext m_removeSelectsContext;
V3DfgRemoveUnobservableContext m_removeUnobservableContext;
V3DfgSynthesisContext m_synthContext;
// CONSTRUCTOR
V3DfgContext() = default;
~V3DfgContext() = default;
};
#endif //VERILATOR_V3DFGCONTEXT_H_