verilator/src/V3DfgSynthesize.cpp

1968 lines
84 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Convert DfgLogic into primitive operations
//
// 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
//
//*************************************************************************
//
// Synthesize DfgLogic vertices in as a graph, as created by V3DfgAstToDfg
// into primitive vertices.
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Cfg.h"
#include "V3Const.h"
#include "V3Dfg.h"
#include "V3DfgPasses.h"
#include "V3EmitV.h"
#include "V3Os.h"
#include <algorithm>
#include <iterator>
VL_DEFINE_DEBUG_FUNCTIONS;
namespace {
// Create a DfgVertex out of a AstNodeExpr. For most AstNodeExpr subtypes, this can be done
// automatically. For the few special cases, we provide specializations below
template <typename T_Vertex, typename T_Node>
T_Vertex* makeVertex(const T_Node* nodep, DfgGraph& dfg, const DfgDataType& dtype) {
return new T_Vertex{dfg, nodep->fileline(), dtype};
}
template <>
DfgArraySel* makeVertex<DfgArraySel, AstArraySel>(const AstArraySel* nodep, DfgGraph& dfg,
const DfgDataType& dtype) {
// Some earlier passes create malformed ArraySels, just bail on those...
// See t_bitsel_wire_array_bad
if (VN_IS(nodep->fromp(), Const)) return nullptr;
if (!VN_IS(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) return nullptr;
return new DfgArraySel{dfg, nodep->fileline(), dtype};
}
} // namespace
// Visitor that can convert Ast statements and expressions in Dfg vertices
template <bool T_Scoped>
class AstToDfgConverter final : public VNVisitor {
// NODE STATE
// AstNodeExpr/AstVar/AstVarScope::user2p -> DfgVertex* for this Node
// AstVar::user3() -> int temporary counter for variable
const VNUser3InUse m_user3InUse;
// TYPES
using Variable = std::conditional_t<T_Scoped, AstVarScope, AstVar>;
// STATE
DfgGraph& m_dfg; // The graph being built
V3DfgSynthesisContext& m_ctx; // The context for stats
// Current logic vertex we are synthesizing
DfgLogic* m_logicp = nullptr;
// Variable updates produced by currently converted statement. This almost
// always have a single element, so a vector is ok
std::vector<std::pair<Variable*, DfgVertexVar*>>* m_updatesp = nullptr;
bool m_foundUnhandled = false; // Found node not implemented as DFG or not implemented 'visit'
bool m_converting = false; // We are trying to convert some logic at the moment
size_t m_nUnpack = 0; // Sequence numbers for temporaries
// METHODS
static Variable* getTarget(const AstVarRef* refp) {
// TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return reinterpret_cast<Variable*>(refp->varScopep());
} else {
return reinterpret_cast<Variable*>(refp->varp());
}
}
// Allocate a new non-variable vertex, add it to the currently synthesized logic
template <typename Vertex, typename... Args>
Vertex* make(Args&&... args) {
static_assert(!std::is_base_of<DfgVertexVar, Vertex>::value, "Do not use for variables");
static_assert(std::is_base_of<DfgVertex, Vertex>::value, "'Vertex' must be a 'DfgVertex'");
Vertex* const vtxp = new Vertex{m_dfg, std::forward<Args>(args)...};
m_logicp->synth().emplace_back(vtxp);
return vtxp;
}
// Returns true if the expression cannot (or should not) be represented by DFG
bool unhandled(AstNodeExpr* nodep) {
// Short-circuiting if something was already unhandled
if (m_foundUnhandled) {
// Impure nodes cannot be represented
if (!nodep->isPure()) {
m_foundUnhandled = true;
++m_ctx.m_conv.nonRepImpure;
}
}
return m_foundUnhandled;
}
bool isSupported(const AstVarRef* nodep) {
// Cannot represent cross module references
if (nodep->classOrPackagep()) return false;
// Check target
return V3Dfg::isSupported(getTarget(nodep));
}
// Given an RValue expression, return the equivalent Vertex, or nullptr if not representable.
DfgVertex* convertRValue(AstNodeExpr* nodep) {
UASSERT_OBJ(!m_converting, nodep, "'convertingRValue' should not be called recursively");
VL_RESTORER(m_converting);
VL_RESTORER(m_foundUnhandled);
m_converting = true;
m_foundUnhandled = false;
// Convert the expression
iterate(nodep);
// If falied to convert, return nullptr
if (m_foundUnhandled) return nullptr;
// Traversal set user2p to the equivalent vertex
DfgVertex* const vtxp = nodep->user2u().to<DfgVertex*>();
UASSERT_OBJ(vtxp, nodep, "Missing Dfg vertex after covnersion");
return vtxp;
}
// Given an LValue expression, return the splice node that writes the
// destination, together with the index to use for splicing in the value.
// Returns {nullptr, 0}, if the given LValue expression is not supported.
std::pair<DfgVertexSplice*, uint32_t> convertLValue(AstNodeExpr* nodep) {
if (const AstVarRef* const vrefp = VN_CAST(nodep, VarRef)) {
if (!isSupported(vrefp)) {
++m_ctx.m_conv.nonRepLValue;
return {nullptr, 0};
}
// Get (or create a new) temporary for this variable
const DfgVertexVar* const vtxp = [&]() -> DfgVertexVar* {
// The variable being assigned
Variable* const tgtp = getTarget(vrefp);
// Find existing one, if any
for (const auto& pair : *m_updatesp) {
if (pair.first == tgtp) return pair.second;
}
// Create new one
DfgVertexVar* const newp = createTmp(*m_logicp, tgtp, "SynthAssign");
m_updatesp->emplace_back(tgtp, newp);
// Create the Splice driver for the new temporary
if (newp->is<DfgVarPacked>()) {
newp->srcp(make<DfgSplicePacked>(newp->fileline(), newp->dtype()));
} else if (newp->is<DfgVarArray>()) {
newp->srcp(make<DfgSpliceArray>(newp->fileline(), newp->dtype()));
} else {
nodep->v3fatalSrc("Unhandled DfgVertexVar sub-type");
}
// Use new temporary
return newp;
}();
// Return the Splice driver
return {vtxp->srcp()->as<DfgVertexSplice>(), 0};
}
if (const AstSel* selp = VN_CAST(nodep, Sel)) {
// Only handle constant selects
const AstConst* const lsbp = VN_CAST(selp->lsbp(), Const);
if (!lsbp) {
++m_ctx.m_conv.nonRepLValue;
return {nullptr, 0};
}
uint32_t lsb = lsbp->toUInt();
// Convert the 'fromp' sub-expression
const auto pair = convertLValue(selp->fromp());
if (!pair.first) return {nullptr, 0};
DfgSplicePacked* const splicep = pair.first->template as<DfgSplicePacked>();
// Adjust index.
lsb += pair.second;
// Don't optimize if statically out of bounds. TODO: Maybe later ...
if (lsb + static_cast<uint32_t>(selp->widthConst()) > splicep->size()) {
++m_ctx.m_conv.nonRepOOBSel;
return {nullptr, 0};
}
// AstSel doesn't change type kind (array vs packed), so we can use
// the existing splice driver with adjusted lsb
return {splicep, lsb};
}
if (const AstArraySel* const aselp = VN_CAST(nodep, ArraySel)) {
// Only handle constant selects
const AstConst* const indexp = VN_CAST(aselp->bitp(), Const);
if (!indexp) {
++m_ctx.m_conv.nonRepLValue;
return {nullptr, 0};
}
uint32_t index = indexp->toUInt();
// Convert the 'fromp' sub-expression
const auto pair = convertLValue(aselp->fromp());
if (!pair.first) return {nullptr, 0};
DfgSpliceArray* const splicep = pair.first->template as<DfgSpliceArray>();
// Adjust index. Note pair.second is always 0, but we might handle array slices later..
index += pair.second;
// Don't optimize if statically out of bounds. TODO: Maybe later ...
if (index + 1U > splicep->size()) {
++m_ctx.m_conv.nonRepOOBSel;
return {nullptr, 0};
}
// Ensure the Splice driver exists for this element
if (!splicep->driverAt(index)) {
FileLine* const flp = nodep->fileline();
// This should never fail
const DfgDataType& dtype = *DfgDataType::fromAst(nodep->dtypep());
if (dtype.isPacked()) {
DfgSplicePacked* const newp = make<DfgSplicePacked>(flp, dtype);
const DfgDataType& uaDtype = DfgDataType::array(dtype, 1);
DfgUnitArray* const uap = make<DfgUnitArray>(flp, uaDtype);
uap->srcp(newp);
splicep->addDriver(uap, index, flp);
} else if (dtype.isArray()) {
DfgSpliceArray* const newp = make<DfgSpliceArray>(flp, dtype);
splicep->addDriver(newp, index, flp);
} else {
nodep->v3fatalSrc("Unhandled data type kind");
}
}
// Return the splice driver
DfgVertex* driverp = splicep->driverAt(index);
if (const DfgUnitArray* const uap = driverp->cast<DfgUnitArray>()) {
driverp = uap->srcp();
}
return {driverp->as<DfgVertexSplice>(), 0};
}
++m_ctx.m_conv.nonRepLValue;
return {nullptr, 0};
}
// Given the LHS of an assignment, and the vertex representing the RHS,
// connect up the RHS to drive the targets.
// Returns true on success, false if the LHS is not representable.
bool convertAssignment(FileLine* flp, AstNodeExpr* lhsp, DfgVertex* vtxp) {
// Represents a DFG assignment contributed by the AST assignment with the above 'lhsp'.
// There might be multiple of these if 'lhsp' is a concatenation.
struct Assignment final {
DfgVertexSplice* m_lhsp;
uint32_t m_idx;
DfgVertex* m_rhsp;
Assignment() = delete;
Assignment(DfgVertexSplice* lhsp, uint32_t idx, DfgVertex* rhsp)
: m_lhsp{lhsp}
, m_idx{idx}
, m_rhsp{rhsp} {}
};
// Simplify the LHS, to get rid of things like SEL(CONCAT(_, _), _)
lhsp = VN_AS(V3Const::constifyExpensiveEdit(lhsp), NodeExpr);
// Assigning compound expressions to a concatenated LHS requires a temporary
// to avoid multiple use of the expression
if (VN_IS(lhsp, Concat) && !vtxp->is<DfgVertexVar>() && !vtxp->is<DfgConst>()) {
const size_t n = ++m_nUnpack;
DfgVertexVar* const tmpp = createTmp(*m_logicp, flp, vtxp->dtype(), "Unpack", n);
tmpp->srcp(vtxp);
vtxp = tmpp;
}
// Convert each concatenation LHS separately, gather all assignments
// we need to do into 'assignments', return true if all LValues
// converted successfully.
std::vector<Assignment> assignments;
const std::function<bool(AstNodeExpr*, uint32_t)> convertAllLValues
= [&](AstNodeExpr* subp, uint32_t lsb) -> bool {
// Concatenation on the LHS, convert each part
if (AstConcat* const concatp = VN_CAST(subp, Concat)) {
AstNodeExpr* const cRhsp = concatp->rhsp();
AstNodeExpr* const cLhsp = concatp->lhsp();
// Convert Rigth of concat
if (!convertAllLValues(cRhsp, lsb)) return false;
// Convert Left of concat
return convertAllLValues(cLhsp, lsb + cRhsp->width());
}
// Non-concatenation, convert the LValue
const auto pair = convertLValue(subp);
if (!pair.first) return false;
// If whole lhs, just use it
if (subp == lhsp) {
assignments.emplace_back(pair.first, pair.second, vtxp);
return true;
}
// Otherwise select the relevant bits
const DfgDataType& dtype = *DfgDataType::fromAst(subp->dtypep());
DfgSel* const selp = make<DfgSel>(subp->fileline(), dtype);
selp->fromp(vtxp);
selp->lsb(lsb);
assignments.emplace_back(pair.first, pair.second, selp);
return true;
};
// Convert the given LHS assignment, give up if any LValues failed to convert
if (!convertAllLValues(lhsp, 0)) return false;
// All successful, connect the drivers
for (const Assignment& item : assignments) {
if (DfgSplicePacked* const spp = item.m_lhsp->template cast<DfgSplicePacked>()) {
spp->addDriver(item.m_rhsp, item.m_idx, flp);
} else if (DfgSpliceArray* const sap = item.m_lhsp->template cast<DfgSpliceArray>()) {
// TODO: multi-dimensional arrays will need changes here
const DfgDataType& rDt = item.m_rhsp->dtype();
if (rDt.isPacked()) {
// RHS is assigning an element of this array. Need a DfgUnitArray adapter.
DfgUnitArray* const uap = make<DfgUnitArray>(flp, DfgDataType::array(rDt, 1));
uap->srcp(item.m_rhsp);
sap->addDriver(uap, item.m_idx, flp);
} else {
// RHS is assigning an array (or array slice). Should be the same element type.
sap->addDriver(item.m_rhsp, item.m_idx, flp);
}
} else {
item.m_lhsp->v3fatalSrc("Unhandled DfgVertexSplice sub-type");
}
}
return true;
}
// VISITORS
// Unhandled node
void visit(AstNode* nodep) override {
if (!m_foundUnhandled && m_converting) ++m_ctx.m_conv.nonRepUnknown;
m_foundUnhandled = true;
}
// Expressions - mostly auto generated, but a few special ones
void visit(AstVarRef* nodep) override {
UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting");
UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex");
if (unhandled(nodep)) return;
// This visit method is only called on RValues, where only read refs are supported
if (!nodep->access().isReadOnly() || !isSupported(nodep)) {
m_foundUnhandled = true;
++m_ctx.m_conv.nonRepVarRef;
return;
}
// Variable should have been bound before starting conversion
DfgVertex* const vtxp = getTarget(nodep)->user2u().template to<DfgVertexVar*>();
UASSERT_OBJ(vtxp, nodep, "Referenced variable has no associated DfgVertexVar");
nodep->user2p(vtxp);
}
void visit(AstConst* nodep) override {
UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting");
UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex");
if (unhandled(nodep)) return;
if (nodep->width() != nodep->num().width()) {
// Sometimes the width of the AstConst is not the same as the
// V3Number it holds. Truncate it here. TODO: should this be allowed?
V3Number num{nodep, nodep->width()};
num.opSel(nodep->num(), nodep->width() - 1, 0);
DfgVertex* const vtxp = make<DfgConst>(nodep->fileline(), num);
nodep->user2p(vtxp);
} else {
DfgVertex* const vtxp = make<DfgConst>(nodep->fileline(), nodep->num());
nodep->user2p(vtxp);
}
}
void visit(AstSel* nodep) override {
UASSERT_OBJ(m_converting, nodep, "AstToDfg visit called without m_converting");
UASSERT_OBJ(!nodep->user2p(), nodep, "Already has Dfg vertex");
if (unhandled(nodep)) return;
const DfgDataType* const dtypep = DfgDataType::fromAst(nodep->dtypep());
if (!dtypep) {
m_foundUnhandled = true;
++m_ctx.m_conv.nonRepDType;
return;
}
iterate(nodep->fromp());
if (m_foundUnhandled) return;
FileLine* const flp = nodep->fileline();
DfgVertex* vtxp = nullptr;
if (const AstConst* const constp = VN_CAST(nodep->lsbp(), Const)) {
const uint32_t lsb = constp->toUInt();
const uint32_t msb = lsb + nodep->widthConst() - 1;
DfgVertex* const fromp = nodep->fromp()->user2u().to<DfgVertex*>();
// Unfortunately we can still have out of bounds selects due to how
// indices are truncated for speed reasons in V3Width/V3Unknown.
if (msb >= fromp->size()) {
m_foundUnhandled = true;
++m_ctx.m_conv.nonRepOOBSel;
return;
}
DfgSel* const selp = make<DfgSel>(flp, *dtypep);
selp->fromp(fromp);
selp->lsb(lsb);
vtxp = selp;
} else {
iterate(nodep->lsbp());
if (m_foundUnhandled) return;
DfgMux* const muxp = make<DfgMux>(flp, *dtypep);
muxp->fromp(nodep->fromp()->user2u().to<DfgVertex*>());
muxp->lsbp(nodep->lsbp()->user2u().to<DfgVertex*>());
vtxp = muxp;
}
nodep->user2p(vtxp);
}
// The rest of the visit methods for expressions are generated by 'astgen'
#include "V3Dfg__gen_ast_to_dfg.h"
public:
// PUBLIC METHODS
// Create temporay variable capable of holding the given type
DfgVertexVar* createTmp(DfgLogic& logic, FileLine* flp, const DfgDataType& dtype,
const std::string& prefix, size_t tmpCount) {
const std::string name = m_dfg.makeUniqueName(prefix, tmpCount);
DfgVertexVar* const vtxp = m_dfg.makeNewVar(flp, name, dtype, logic.scopep());
logic.synth().emplace_back(vtxp);
vtxp->varp()->isInternal(true);
vtxp->tmpForp(vtxp->nodep());
return vtxp;
}
// Create a new temporary variable capable of holding 'varp'
DfgVertexVar* createTmp(DfgLogic& logic, Variable* varp, const std::string& prefix) {
AstVar* const astVarp = T_Scoped ? reinterpret_cast<AstVarScope*>(varp)->varp()
: reinterpret_cast<AstVar*>(varp);
FileLine* const flp = astVarp->fileline();
const DfgDataType& dtype = *DfgDataType::fromAst(astVarp->dtypep());
const std::string prfx = prefix + "_" + astVarp->name();
const size_t tmpCount = astVarp->user3Inc();
DfgVertexVar* const vtxp = createTmp(logic, flp, dtype, prfx, tmpCount);
vtxp->tmpForp(varp);
return vtxp;
}
// Convert AstAssign to Dfg, return true if successful.
// Fills 'updates' with bindings for assigned variables.
bool convert(std::vector<std::pair<Variable*, DfgVertexVar*>>& updates, DfgLogic& vtx,
AstAssign* nodep) {
UASSERT_OBJ(updates.empty(), nodep, "'updates' should be empty");
VL_RESTORER(m_updatesp);
VL_RESTORER(m_logicp);
m_updatesp = &updates;
m_logicp = &vtx;
// Assignment with timing control shouldn't make it this far
UASSERT_OBJ(!nodep->timingControlp(), nodep, "Shouldn't make it this far");
// Convert it
++m_ctx.m_conv.inputAssignments;
AstNodeExpr* const lhsp = nodep->lhsp();
AstNodeExpr* const rhsp = nodep->rhsp();
// Check data types are compatible.
const DfgDataType* const lDtypep = DfgDataType::fromAst(lhsp->dtypep());
const DfgDataType* const rDtypep = DfgDataType::fromAst(rhsp->dtypep());
if (!lDtypep || !rDtypep) {
++m_ctx.m_conv.nonRepDType;
return false;
}
// For now, only direct array assignment is supported (e.g. a = b, but not a = _ ? b : c)
if (rDtypep->isArray() && !VN_IS(rhsp, VarRef)) {
++m_ctx.m_conv.nonRepDType;
return false;
}
// Widths should match at this point
UASSERT_OBJ(lhsp->width() == rhsp->width(), nodep, "Mismatched width reached DFG");
// Convert the RHS expression
DfgVertex* const rVtxp = convertRValue(rhsp);
if (!rVtxp) return false;
// Connect the RHS vertex to the LHS targets
const bool success = convertAssignment(nodep->fileline(), lhsp, rVtxp);
if (success) ++m_ctx.m_conv.representable;
return success;
}
// Convert RValue expression to Dfg. Returns nullptr if failed.
DfgVertex* convert(DfgLogic& vtx, AstNodeExpr* nodep) {
VL_RESTORER(m_logicp);
m_logicp = &vtx;
// Convert it
++m_ctx.m_conv.inputExpressions;
DfgVertex* const vtxp = convertRValue(nodep);
if (vtxp) ++m_ctx.m_conv.representable;
return vtxp;
}
// CONSTRUCTOR
AstToDfgConverter(DfgGraph& dfg, V3DfgSynthesisContext& ctx)
: m_dfg{dfg}
, m_ctx{ctx} {}
};
// For debugging, we can stop synthesizing after a certain number of vertices.
// for this we need a global counter (inside the template makes multiple copies)
static size_t s_dfgSynthDebugCount = 0;
// The number of vertices we stop after can be passed in through the environment
// you can then use a bisection search over this value and look at the dumps
// produced with the lowest failing value
static const size_t s_dfgSynthDebugLimit
= std::stoull(V3Os::getenvStr("VERILATOR_DFG_SYNTH_DEBUG", "0"));
template <bool T_Scoped>
class AstToDfgSynthesize final {
// NODE STATE
// AstNodeExpr/AstVar/AstVarScope::user2p -> DfgVertex* for this Node
// TYPES
using Variable = std::conditional_t<T_Scoped, AstVarScope, AstVar>;
// SymTab must be ordered in order to yield stable results
struct VariableComparator final {
bool operator()(const Variable* lhs, const Variable* rhs) const {
return lhs->name() < rhs->name();
}
};
using SymTab = std::map<Variable*, DfgVertexVar*, VariableComparator>;
// Represents a [potentially partial] driver of a variable
struct Driver final {
DfgVertex* m_vtxp = nullptr; // Driving vertex
uint32_t m_lo = 0; // Low index of driven range (internal, not Verilog)
uint32_t m_hi = 0; // High index of driven range (internal, not Verilog)
FileLine* m_flp = nullptr; // Location of driver in source
Driver() = default;
Driver(DfgVertex* vtxp, uint32_t lo, FileLine* flp)
: m_vtxp{vtxp}
, m_lo{lo}
, m_hi{lo + vtxp->size() - 1U}
, m_flp{flp} {}
operator bool() const { return m_vtxp != nullptr; }
bool operator<(const Driver& other) const {
if (m_lo != other.m_lo) return m_lo < other.m_lo;
if (m_hi != other.m_hi) return m_hi < other.m_hi;
return m_flp->operatorCompare(*other.m_flp) < 0;
}
bool operator<=(const Driver& other) const { return !(other < *this); }
};
// STATE - Persistent
DfgGraph& m_dfg; // The graph being built
V3DfgSynthesisContext& m_ctx; // The context for stats
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
CfgBlockMap<SymTab> m_bbToISymTab; // Map from CfgBlock -> input symbol table
CfgBlockMap<SymTab> m_bbToOSymTab; // Map from CfgBlock -> output symbol table
CfgBlockMap<DfgVertexVar*> m_bbToCondp; // Map from CfgBlock -> terminating branch condition
CfgEdgeMap<DfgVertexVar*> m_edgeToPredicatep; // Map CfgGraphEdge -> path predicate to there
CfgDominatorTree m_domTree; // The dominator tree of the current CFG
// STATE - Some debug aid
// We stop after synthesizing s_dfgSynthDebugLimit vertices (if non-zero).
// This is the problematic logic (last one we synthesize), assuming a
// bisection search over s_dfgSynthDebugLimit.
DfgLogic* m_debugLogicp = nullptr;
// Source (upstream) cone of outputs of m_debugLogicp
std::unique_ptr<std::unordered_set<const DfgVertex*>> m_debugOSrcConep{nullptr};
// METHODS
// Dump current graph for debugging ...
void debugDump(const char* name) {
// If we have the debugged logic, compute the vertices feeding its outputs
if (VL_UNLIKELY(m_debugLogicp)) {
std::vector<const DfgVertex*> outputs;
m_debugLogicp->foreachSink([&outputs](const DfgVertex& v) {
outputs.emplace_back(v.singleSink()->as<DfgVertexVar>());
return false;
});
m_debugOSrcConep = m_dfg.sourceCone(outputs);
}
if (VL_UNLIKELY(dumpDfgLevel() >= 9 || m_debugOSrcConep)) {
const auto label = m_ctx.prefix() + name;
m_dfg.dumpDotFilePrefixed(label);
if (m_debugOSrcConep) {
// Dump only the subgraph involving the inputs and outputs of the bad vertex
m_dfg.dumpDotFilePrefixed(label + "-min", [&](const DfgVertex& v) -> bool {
return m_debugOSrcConep->count(&v);
});
}
}
}
static AstVar* getAstVar(Variable* vp) {
// TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return reinterpret_cast<AstVarScope*>(vp)->varp();
} else {
return reinterpret_cast<AstVar*>(vp);
}
}
// Allocate a new non-variable vertex, add it to the currently synthesized logic
template <typename Vertex, typename... Args>
Vertex* make(Args&&... args) {
static_assert(!std::is_base_of<DfgVertexVar, Vertex>::value, "Do not use for variables");
static_assert(std::is_base_of<DfgVertex, Vertex>::value, "'Vertex' must be a 'DfgVertex'");
Vertex* const vtxp = new Vertex{m_dfg, std::forward<Args>(args)...};
if (m_logicp) m_logicp->synth().emplace_back(vtxp);
return vtxp;
}
// Gather all drivers of a resolved variable
static std::vector<Driver> gatherDrivers(DfgVertexSplice* vtxp) {
// Collect them all, check if they are sorted
std::vector<Driver> drivers;
drivers.reserve(vtxp->nInputs());
bool sorted = true;
vtxp->foreachDriver([&](DfgVertex& src, uint32_t lo, FileLine* flp) {
// Collect the driver
drivers.emplace_back(&src, lo, flp);
// Check if drivers are sorted - most often they are
const size_t n = drivers.size();
if (n >= 2 && drivers[n - 1] < drivers[n - 2]) sorted = false;
return false;
});
// Sort if unsorted
if (!sorted) std::stable_sort(drivers.begin(), drivers.end());
// Done
return drivers;
}
// Gather all synthesized drivers of an unresolved variable
static std::vector<Driver> gatherDriversUnresolved(DfgUnresolved* vtxp) {
std::vector<Driver> drivers;
drivers.reserve(vtxp->nInputs());
// For better locations in error reporting, we unpick concatenations
// which are sometimes introduced by combinint assignments in V3Const.
const std::function<void(DfgVertex*, uint32_t, FileLine*)> gather
= [&](DfgVertex* vtxp, uint32_t lo, FileLine* flp) -> void {
if (DfgConcat* const concatp = vtxp->cast<DfgConcat>()) {
DfgVertex* const rhsp = concatp->rhsp();
gather(rhsp, lo, rhsp->fileline());
DfgVertex* const lhsp = concatp->lhsp();
gather(lhsp, lo + rhsp->width(), lhsp->fileline());
return;
}
drivers.emplace_back(vtxp, lo, flp);
};
// Gather all synthesized drivers
vtxp->foreachSource([&](DfgVertex& src) {
// Can ignore the original DfgLogic
if (src.is<DfgLogic>()) return false;
// Synthesized drivers must be a splice at this point
DfgVertexSplice* const splicep = src.as<DfgVertexSplice>();
// Collect the driver
splicep->foreachDriver([&](DfgVertex& src, uint32_t lo, FileLine* flp) {
gather(&src, lo, flp);
return false;
});
return false;
});
// Sort the drivers
std::stable_sort(drivers.begin(), drivers.end());
// Done
return drivers;
}
// Given two drivers, combine the driven sub-ranges into the first one if
// possible. First bool returned indicates successfully combined and there
// are no multi-driven bits. Second bool returned indicates we warned
// already about multi-driven bits.
std::pair<bool, bool> combineDrivers(DfgVertexVar& var, const std::string& sub, //
Driver& a, const Driver& b) {
// We can only combine array drivers ...
if (a.m_vtxp->isPacked()) return {false, false};
// ... that both drive a single element ...
if (a.m_lo != b.m_lo) return {false, false};
const DfgUnitArray* const aUap = a.m_vtxp->template cast<DfgUnitArray>();
if (!aUap) return {false, false};
const DfgUnitArray* const bUap = b.m_vtxp->template cast<DfgUnitArray>();
if (!bUap) return {false, false};
// ... and are themeselves partial
DfgSplicePacked* const aSp = aUap->srcp()->template cast<DfgSplicePacked>();
if (!aSp) return {false, false};
DfgSplicePacked* const bSp = bUap->srcp()->template cast<DfgSplicePacked>();
if (!bSp) return {false, false};
UASSERT_OBJ(aSp->dtype() == bSp->dtype(), &var, "DTypes should match");
// Gather drivers of a
std::vector<Driver> aDrivers = gatherDrivers(aSp);
// Gather drivers of b
std::vector<Driver> bDrivers = gatherDrivers(bSp);
// Merge them
std::vector<Driver> abDrivers;
abDrivers.reserve(aDrivers.size() + bDrivers.size());
std::merge( //
aDrivers.begin(), aDrivers.end(), //
bDrivers.begin(), bDrivers.end(), //
std::back_inserter(abDrivers) //
);
// Attempt to resolve them
if (!normalizeDrivers(var, abDrivers, sub + "[" + std::to_string(a.m_lo) + "]")) {
return {false, true};
}
// Successfully resolved. Needs a new splice and unit.
FileLine* const flp = var.fileline();
DfgSplicePacked* const splicep = make<DfgSplicePacked>(flp, aSp->dtype());
for (const Driver& d : abDrivers) splicep->addDriver(d.m_vtxp, d.m_lo, d.m_flp);
DfgUnitArray* const uap = make<DfgUnitArray>(flp, aUap->dtype());
uap->srcp(splicep);
a.m_vtxp = uap;
return {true, false};
}
// Combine and coalesce the given drivers.
// Returns true iff no multi-driven bits are present.
bool normalizeDrivers(DfgVertexVar& var, std::vector<Driver>& drivers,
const std::string& sub = "") {
if (drivers.empty()) return true;
// What type of values are we combining
const bool isPacked = drivers[0].m_vtxp->isPacked();
// Found a multidriven part ?
bool multiDriven = false;
// Iterate through the sorted drivers. Index 'i' is the driver we are
// resolving driver 'j' agains, and if required, we merge 'j' into 'i'.
size_t i = 0;
for (size_t j = 1; j < drivers.size();) {
UASSERT_OBJ(i < j, &var, "Invalid iteration");
Driver& iD = drivers[i];
Driver& jD = drivers[j];
// If 'j' was moved, step forward
if (!jD) {
++j;
continue;
}
// If 'i' was moved, move 'j' in it's place
if (!iD) {
iD = jD;
jD = Driver{};
++j;
continue;
}
// We have 2 valid drivers now
UASSERT_OBJ(iD <= jD, &var, "Should always be sorted");
UASSERT_OBJ(jD.m_vtxp->isPacked() == isPacked, &var, "Mixed type drivers");
// If no overlap, consider next pair
if (iD.m_hi < jD.m_lo) {
++i;
if (i == j) ++j;
continue;
}
// There is an overlap. Attempt to combine them.
bool combined = false;
bool warned = false;
std::tie(combined, warned) = combineDrivers(var, sub, iD, jD);
// If sucessfully combined, 'j' is no longer needed, it was combined into 'i'
if (combined) {
jD = Driver{};
++j;
continue;
}
// Found overlap that cannot be resolved
multiDriven = true;
// Compare next driver
++j;
// Do not warn again if we warned during 'combineDrivers'
if (warned) continue;
// The variable to warn on
AstNode* const nodep = var.tmpForp() ? var.tmpForp() : var.nodep();
Variable* const varp = reinterpret_cast<Variable*>(nodep);
// Loop index often abused, so suppress
if (getAstVar(varp)->isUsedLoopIdx()) continue;
// Warn the user now
const std::string lo = std::to_string(jD.m_lo);
const std::string hi = std::to_string(std::min(iD.m_hi, jD.m_hi));
const std::string kind = isPacked ? "Bit" : "Element";
const std::string part = hi == lo ? (" [" + lo + "]") : ("s [" + hi + ":" + lo + "]");
varp->v3warn( //
MULTIDRIVEN, //
kind << part << " of signal '" << varp->prettyName() << sub << "'"
<< " have multiple combinational drivers."
<< " This can cause performance degradation.\n"
<< iD.m_flp->warnOther() << "... Location of offending driver\n"
<< iD.m_flp->warnContextPrimary() << '\n'
<< jD.m_flp->warnOther() << "... Location of offending driver\n"
<< jD.m_flp->warnContextSecondary());
}
// Rightsize vector
drivers.resize(i + 1);
// Coalesce adjacent drivers
if (!multiDriven && isPacked) coalesceDrivers(drivers);
return !multiDriven;
}
// Coalesce adjacent drivers into single ones
void coalesceDrivers(std::vector<Driver>& drivers) {
UASSERT(!drivers.empty(), "Can't coalesce 0 drivers");
UASSERT_OBJ(drivers[0].m_vtxp->isPacked(), drivers[0].m_vtxp, "Can only coalesce packed");
size_t i = 0;
for (size_t j = 1; j < drivers.size();) {
UASSERT(i < j, "Invalid iteration");
Driver& iD = drivers[i];
Driver& jD = drivers[j];
// If 'j' was moved, step forward
if (!jD) {
++j;
continue;
}
// If 'i' was moved, move 'j' in it's place
if (!iD) {
iD = jD;
jD = Driver{};
++j;
continue;
}
// We have 2 valid drivers now
UASSERT(iD <= jD, "Should always be sorted");
// If not adjacent, move on
if (iD.m_hi + 1 != jD.m_lo) {
++i;
if (i == j) ++j;
continue;
}
// Coalesce Adjacent ranges,
const DfgDataType& dt = DfgDataType::packed(iD.m_vtxp->width() + jD.m_vtxp->width());
DfgConcat* const concatp = make<DfgConcat>(iD.m_flp, dt);
concatp->rhsp(iD.m_vtxp);
concatp->lhsp(jD.m_vtxp);
iD.m_vtxp = concatp;
iD.m_hi = jD.m_hi;
jD = Driver{};
// Consider next driver
++j;
}
// Rightsize vector
drivers.resize(i + 1);
}
// Make a new splice with the given drivers
DfgVertexSplice* makeSplice(DfgVertexVar& var, const std::vector<Driver>& newDrivers) {
UASSERT_OBJ(!newDrivers.empty(), &var, "'makeSplice' called with no new drivers");
// Create new driver
DfgVertexSplice* splicep = nullptr;
if (var.is<DfgVarPacked>()) {
splicep = make<DfgSplicePacked>(var.fileline(), var.dtype());
} else if (var.is<DfgVarArray>()) {
splicep = make<DfgSpliceArray>(var.fileline(), var.dtype());
} else {
var.v3fatalSrc("Unhandled DfgVertexVar sub-type");
}
for (const Driver& d : newDrivers) splicep->addDriver(d.m_vtxp, d.m_lo, d.m_flp);
return splicep;
}
// Initialzie input symbol table of entry CfgBlock
void initializeEntrySymbolTable(SymTab& iSymTab) {
m_logicp->foreachSource([&](DfgVertex& src) {
DfgVertexVar* const vvp = src.as<DfgVertexVar>();
Variable* const varp = reinterpret_cast<Variable*>(vvp->nodep());
iSymTab[varp] = vvp;
return false;
});
}
// Join variable drivers across a control flow confluence (insert muxes ...)
DfgVertexVar* joinDrivers(Variable* varp, DfgVertexVar* predicatep, //
DfgVertexVar* thenp, DfgVertexVar* elsep) {
AstNode* const thenVarp = thenp->tmpForp() ? thenp->tmpForp() : thenp->nodep();
AstNode* const elseVarp = elsep->tmpForp() ? elsep->tmpForp() : elsep->nodep();
UASSERT_OBJ(thenVarp == elseVarp, varp, "Attempting to join unrelated variables");
// If both bindings are the the same (variable not updated through either path),
// then there is nothing to do, can use the same binding
if (thenp == elsep) return thenp;
// We can't join the input variable just yet, so bail
if (thenp->nodep() == varp) {
++m_ctx.m_synt.nonSynJoinInput;
return nullptr;
}
if (elsep->nodep() == varp) {
++m_ctx.m_synt.nonSynJoinInput;
return nullptr;
}
// Can't do arrays yet
if (!thenp->isPacked()) {
++m_ctx.m_synt.nonSynArray;
return nullptr;
}
// Gather drivers of 'thenp' - only if 'thenp' is not an input to the synthesized block
DfgVertex* const tDefaultp = thenp->defaultp();
std::vector<Driver> tDrivers = gatherDrivers(thenp->srcp()->as<DfgVertexSplice>());
// Gather drivers of 'elsep' - only if 'thenp' is not an input to the synthesized block
DfgVertex* const eDefaultp = elsep->defaultp();
std::vector<Driver> eDrivers = gatherDrivers(elsep->srcp()->as<DfgVertexSplice>());
// Default drivers should be the same or not present on either
UASSERT_OBJ(tDefaultp == eDefaultp, varp, "Different default drivers");
// Location to use for the join vertices
FileLine* const flp = predicatep->fileline();
// Create a fresh temporary for the joined value
DfgVertexVar* const joinp = m_converter.createTmp(*m_logicp, varp, "SynthJoin");
DfgVertexSplice* const joinSplicep = make<DfgSplicePacked>(flp, joinp->dtype());
joinp->srcp(joinSplicep);
// If both paths are fully driven, just create a simple conditional
if (tDrivers.size() == 1 //
&& tDrivers[0].m_lo == 0 //
&& tDrivers[0].m_hi == thenp->width() - 1 //
&& eDrivers.size() == 1 //
&& eDrivers[0].m_lo == 0 //
&& eDrivers[0].m_hi == elsep->width() - 1) {
UASSERT_OBJ(!tDefaultp, varp, "Fully driven variable have default driver");
DfgCond* const condp = make<DfgCond>(flp, joinp->dtype());
condp->condp(predicatep);
condp->thenp(thenp);
condp->elsep(elsep);
joinSplicep->addDriver(condp, 0, tDrivers[0].m_flp);
// Done
return joinp;
}
// Otherwise we need to merge them part by part
// If different bits are driven, then some might not have been assigned.. Latch?
if (tDrivers.size() != eDrivers.size()) {
++m_ctx.m_synt.nonSynLatch;
return nullptr;
}
for (size_t i = 0; i < tDrivers.size(); ++i) {
const Driver& tDriver = tDrivers[i];
const Driver& eDriver = eDrivers[i];
// If different bits are driven, then some might not have been assigned.. Latch?
if (tDriver.m_lo != eDriver.m_lo || tDriver.m_hi != eDriver.m_hi) {
++m_ctx.m_synt.nonSynLatch;
return nullptr;
}
const DfgDataType& dtype = DfgDataType::packed(tDriver.m_hi - tDriver.m_lo + 1);
DfgCond* const condp = make<DfgCond>(flp, dtype);
condp->condp(predicatep);
// We actally need to select the bits from the joined variables, not use the drivers
DfgSel* const thenSelp = make<DfgSel>(flp, tDriver.m_vtxp->dtype());
thenSelp->lsb(tDriver.m_lo);
thenSelp->fromp(thenp);
condp->thenp(thenSelp);
// Same for the 'else' part
DfgSel* const elseSelp = make<DfgSel>(flp, eDriver.m_vtxp->dtype());
elseSelp->lsb(eDriver.m_lo);
elseSelp->fromp(elsep);
condp->elsep(elseSelp);
// Add it as a driver to the join
joinSplicep->addDriver(condp, tDriver.m_lo, tDriver.m_flp);
}
// If there was a default driver, add it to te join
if (tDefaultp) joinp->defaultp(tDefaultp);
// Done
return joinp;
}
// Merge 'thenSymTab' into 'elseSymTab' using the given predicate to join values
bool joinSymbolTables(SymTab& elseSymTab, DfgVertexVar* predicatep, const SymTab& thenSymTab) {
// Give up if something is not assigned on all paths ... Latch?
if (thenSymTab.size() != elseSymTab.size()) {
++m_ctx.m_synt.nonSynLatch;
return false;
}
// Join each symbol
for (std::pair<Variable* const, DfgVertexVar*>& pair : elseSymTab) {
Variable* const varp = pair.first;
// Find same variable on the else path
auto it = thenSymTab.find(varp);
// Give up if something is not assigned on all paths ... Latch?
if (it == thenSymTab.end()) {
++m_ctx.m_synt.nonSynLatch;
return false;
}
// Join paths with the predicate
DfgVertexVar* const thenp = it->second;
DfgVertexVar* const elsep = pair.second;
DfgVertexVar* const newp = joinDrivers(varp, predicatep, thenp, elsep);
if (!newp) return false;
pair.second = newp;
}
// Done
return true;
}
// Given two joining control flow edges, compute how to join their symbols.
// Returns the predicaete to join over, and the 'then' and 'else' blocks.
std::tuple<DfgVertexVar*, const CfgBlock*, const CfgBlock*> //
howToJoin(const CfgEdge* const ap, const CfgEdge* const bp) {
// Find the closest common dominator of the two paths
const CfgBlock* const domp = m_domTree.closestCommonDominator(ap->srcp(), bp->srcp());
// These paths join here, so 'domp' must be a branch, otherwise it's not the closest
UASSERT_OBJ(domp->isBranch(), domp, "closestCommonDominator is not a branch");
// The branches of the common dominator
const CfgEdge* const takenEdgep = domp->takenEdgep();
const CfgEdge* const untknEdgep = domp->untknEdgep();
// We check if the taken branch dominates the path to either blocks,
// and if the untaken branch dominates the path to the other block.
// If so, we can use the branch condition as predicate, otherwise
// we must use the path predicate as there are ways to get from one
// branch of the dominator to the other. We need to be careful if
// either branches are directly to the join block. This is fine,
// it's as if there was an empty block on that critical edge which
// is dominated by that path.
if (takenEdgep == ap || m_domTree.dominates(takenEdgep->dstp(), ap->srcp())) {
if (untknEdgep == bp || m_domTree.dominates(untknEdgep->dstp(), bp->srcp())) {
// Taken path dominates 'ap' and untaken dominates 'bp', use the branch condition
++m_ctx.m_synt.joinUsingBranchCondition;
return std::make_tuple(m_bbToCondp[domp], ap->srcp(), bp->srcp());
}
} else if (takenEdgep == bp || m_domTree.dominates(takenEdgep->dstp(), bp->srcp())) {
if (untknEdgep == ap || m_domTree.dominates(untknEdgep->dstp(), ap->srcp())) {
// Taken path dominates 'bp' and untaken dominates 'ap', use the branch condition
++m_ctx.m_synt.joinUsingBranchCondition;
return std::make_tuple(m_bbToCondp[domp], bp->srcp(), ap->srcp());
}
}
// The branches don't dominate the joined blocks, must use the path predicate
++m_ctx.m_synt.joinUsingPathPredicate;
// TODO: We could do better here: use the path predicate of the closest
// cominating blocks, pick the one from the lower rank, etc, but this
// generic case is very rare, most synthesizable logic has
// series-parallel CFGs which are covered by the earlier cases.
return std::make_tuple(m_edgeToPredicatep[ap], ap->srcp(), bp->srcp());
}
// Combine the output symbol tables of the predecessors of the given
// block to compute the input symtol table for the given block.
bool createInputSymbolTable(const CfgBlock& bb) {
// The input symbol table of the given block, we are computing it now
SymTab& joined = m_bbToISymTab[bb];
// Input symbol table of entry block is special
if (bb.isEnter()) {
initializeEntrySymbolTable(joined);
return true;
}
// Current input symbol table should be empty, we will fill it in here
UASSERT(joined.empty(), "Unprocessed input symbol table should be empty");
// Fast path if there is only one predecessor - TODO: use less copying
if (!bb.isJoin()) {
joined = m_bbToOSymTab[bb.firstPredecessorp()];
return true;
}
// We also have a simpler job if there are 2 predecessors
if (bb.isTwoWayJoin()) {
DfgVertexVar* predicatep = nullptr;
const CfgBlock* thenp = nullptr;
const CfgBlock* elsep = nullptr;
std::tie(predicatep, thenp, elsep)
= howToJoin(bb.firstPredecessorEdgep(), bb.lastPredecessorEdgep());
// Copy from else
joined = m_bbToOSymTab[elsep];
// Join with then
return joinSymbolTables(joined, predicatep, m_bbToOSymTab[*thenp]);
}
// General hard way
// Gather predecessors
struct Predecessor final {
const CfgBlock* m_bbp; // Predeccessor block
DfgVertexVar* m_predicatep; // Predicate predecessor reached this block with
const SymTab* m_oSymTabp; // Output symbol table or predecessor
Predecessor() = delete;
Predecessor(const CfgBlock* bbp, DfgVertexVar* predicatep, const SymTab* oSymTabp)
: m_bbp{bbp}
, m_predicatep{predicatep}
, m_oSymTabp{oSymTabp} {}
};
const std::vector<Predecessor> predecessors = [&]() {
std::vector<Predecessor> res;
for (const V3GraphEdge& edge : bb.inEdges()) {
const CfgEdge& cfgEdge = static_cast<const CfgEdge&>(edge);
const CfgBlock* const predecessorp = cfgEdge.srcp();
DfgVertexVar* const predicatep = m_edgeToPredicatep[cfgEdge];
const SymTab* const oSymTabp = &m_bbToOSymTab[predecessorp];
res.emplace_back(predecessorp, predicatep, oSymTabp);
}
// Sort predecessors reverse topologically. This way earlier blocks
// will come after later blocks, and the entry block is last if present.
std::sort(res.begin(), res.end(), [](const Predecessor& a, const Predecessor& b) { //
return *a.m_bbp > *b.m_bbp;
});
return res;
}();
// Start by copying the bindings from the frist predecessor
joined = *predecessors[0].m_oSymTabp;
// Join over all other predecessors
for (size_t i = 1; i < predecessors.size(); ++i) {
DfgVertexVar* const predicatep = predecessors[i].m_predicatep;
const SymTab& oSymTab = *predecessors[i].m_oSymTabp;
if (!joinSymbolTables(joined, predicatep, oSymTab)) return false;
}
return true;
}
std::vector<Driver> computePropagatedDrivers(const std::vector<Driver>& newDrivers,
DfgVertexVar* oldp) {
// Gather drivers of 'oldp' - they are in incresing range order with no overlaps
std::vector<Driver> oldDrivers = gatherDrivers(oldp->srcp()->as<DfgVertexSplice>());
UASSERT_OBJ(!oldDrivers.empty(), oldp, "Should have a proper driver");
// Additional drivers of 'newp' propagated from 'oldp'
std::vector<Driver> propagatedDrivers;
// Add bits between 'msb' and 'lsb' from 'oldp' to 'pDrivers'
const auto addOldDriver = [&](FileLine* const flp, uint32_t msb, uint32_t lsb) {
UASSERT_OBJ(propagatedDrivers.empty() || lsb > propagatedDrivers.back().m_hi, flp,
"Drivers should be in ascending order");
DfgSel* const selp = make<DfgSel>(flp, DfgDataType::packed(msb - lsb + 1));
selp->lsb(lsb);
selp->fromp(oldp);
propagatedDrivers.emplace_back(selp, lsb, flp);
};
// Incorporate old drivers
for (const Driver& oDriver : oldDrivers) {
FileLine* const flp = oDriver.m_flp;
// Range to consider inserting, we will adjust oldLo as we process drivers
uint32_t oldLo = oDriver.m_lo;
const uint32_t oldHi = oDriver.m_hi;
// Loop for now, can move to bisection search if this is a problem, shouldn't be ...
for (const Driver& nDriver : newDrivers) {
UASSERT_OBJ(oldHi >= oldLo, flp, "Should have stopped iteration");
// If new driver is entirely below old driver, move on to
if (nDriver.m_hi < oldLo) continue;
// If new driver is entirely above old driver, we can stop
if (oldHi < nDriver.m_lo) break;
// There is an overlap between 'oDriver' and 'nDriver'.
// Insert the low bits and adjust the insertion range.
// The rest will take care of itself on subsequent iterations.
if (oldLo < nDriver.m_lo) addOldDriver(flp, nDriver.m_lo - 1, oldLo);
oldLo = nDriver.m_hi + 1;
// Stop if no more bits remaining in the old driver
if (oldLo > oldHi) break;
}
// Insert remaining bits if any
if (oldHi >= oldLo) addOldDriver(flp, oldHi, oldLo);
}
return propagatedDrivers;
}
// Given the drivers of a variable after converting a single statement
// 'newp', add drivers from 'oldp' that were not reassigned be drivers
// in newp. This computes the total result of all previous assignments.
bool incorporatePreviousValue(Variable* varp, DfgVertexVar* newp, DfgVertexVar* oldp) {
UASSERT_OBJ(newp->srcp(), varp, "Assigned variable has no driver");
// Easy if there is no old value...
if (!oldp) return true;
// New driver was not yet coalesced, so should always be a splice
DfgVertexSplice* const nSplicep = newp->srcp()->as<DfgVertexSplice>();
// If the old value is the real variable we just computed the new value for,
// then it is the circular feedback into the synthesized block, add it as default driver.
if (oldp->nodep() == varp) {
if (!nSplicep->wholep()) newp->defaultp(oldp);
return true;
}
UASSERT_OBJ(oldp->srcp(), varp, "Previously assigned variable has no driver");
// Can't do arrays yet
if (!newp->isPacked()) {
++m_ctx.m_synt.nonSynArray;
return false;
}
// Gather drivers of 'newp' - they are in incresing range order with no overlaps
UASSERT_OBJ(!newp->defaultp(), newp, "Converted value should not have default");
std::vector<Driver> nDrivers = gatherDrivers(newp->srcp()->as<DfgVertexSplice>());
UASSERT_OBJ(!nDrivers.empty(), newp, "Should have a proper driver");
// Additional drivers of 'newp' propagated from 'oldp'
std::vector<Driver> pDrivers = computePropagatedDrivers(nDrivers, oldp);
if (!pDrivers.empty()) {
// Need to merge propagated sources, so reset the splice
nSplicep->resetDrivers();
// Merge drivers - they are both sorted and non-overlapping
std::vector<Driver> drivers;
drivers.reserve(nDrivers.size() + pDrivers.size());
std::merge(nDrivers.begin(), nDrivers.end(), pDrivers.begin(), pDrivers.end(),
std::back_inserter(drivers));
// Coalesce adjacent ranges
coalesceDrivers(drivers);
// Reinsert drivers in order
for (const Driver& d : drivers) nSplicep->addDriver(d.m_vtxp, d.m_lo, d.m_flp);
}
// If the old had a default, add to the new one too, unless redundant
if (oldp->defaultp() && !nSplicep->wholep()) newp->defaultp(oldp->defaultp());
// Done
return true;
}
// Synthesize the given statements with the given input symbol table.
// Returns true if successfolly synthesized.
// Populates the given output symbol table.
// Populates the given reference with the condition of the terminator branch, if any.
bool synthesizeBasicBlock(SymTab& oSymTab, DfgVertex*& condpr,
const std::vector<AstNodeStmt*>& stmtps, const SymTab& iSymTab) {
// Use fresh set of vertices in m_converter
const VNUser2InUse user2InUse;
// Initialize Variable -> Vertex bindings available in this block
for (const auto& pair : iSymTab) {
Variable* const varp = pair.first;
DfgVertexVar* const vtxp = pair.second;
varp->user2p(vtxp);
oSymTab[varp] = vtxp;
}
// Synthesize each statement one after the other
std::vector<std::pair<Variable*, DfgVertexVar*>> updates;
for (AstNodeStmt* const stmtp : stmtps) {
// Regular statements
if (AstAssign* const ap = VN_CAST(stmtp, Assign)) {
// Convert this assignment
if (!m_converter.convert(updates, *m_logicp, ap)) {
++m_ctx.m_synt.nonSynConv;
return false;
}
// Apply variable updates from this statement
for (const auto& pair : updates) {
// The target variable that was assigned to
Variable* const varp = pair.first;
// The new, potentially partially assigned value
DfgVertexVar* const newp = pair.second;
// Normalize drivers within this statement, bail if multidriven
DfgVertexSplice* const srcp = newp->srcp()->as<DfgVertexSplice>();
std::vector<Driver> drivers = gatherDrivers(srcp);
const bool single = drivers.size() == 1;
if (!normalizeDrivers(*newp, drivers)) {
getAstVar(varp)->setDfgMultidriven();
++m_ctx.m_synt.nonSynMultidrive;
return false;
}
// If there were more than one driver (often not), re-add in case coalesced
if (!single) {
srcp->resetDrivers();
for (const Driver& d : drivers) srcp->addDriver(d.m_vtxp, d.m_lo, d.m_flp);
}
// The old value, if any
DfgVertexVar* const oldp = varp->user2u().template to<DfgVertexVar*>();
// Inncorporate old value into the new value
if (!incorporatePreviousValue(varp, newp, oldp)) return false;
// Update binding of target variable
varp->user2p(newp);
// Update output symbol table of this block
oSymTab[varp] = newp;
}
updates.clear();
continue;
}
// Terminator branches
if (AstIf* const ifp = VN_CAST(stmtp, If)) {
UASSERT_OBJ(ifp == stmtps.back(), ifp, "Branch should be last statement");
// Convert condition, give up if failed
DfgVertex* condp = m_converter.convert(*m_logicp, ifp->condp());
if (!condp) {
++m_ctx.m_synt.nonSynConv;
return false;
}
// Single bit condition can be use directly, otherwise: use 'condp != 0'
if (condp->width() != 1) {
FileLine* const flp = condp->fileline();
DfgNeq* const neqp = make<DfgNeq>(flp, DfgDataType::packed(1));
neqp->lhsp(make<DfgConst>(flp, condp->width(), 0U));
neqp->rhsp(condp);
condp = neqp;
}
condpr = condp;
continue;
}
// Unhandled
++m_ctx.m_synt.nonSynStmt;
return false;
}
return true;
}
// Assign path predicates to the outgoing control flow edges of the given block
void assignPathPredicates(const CfgBlock& bb) {
// Nothing to do for the exit block
if (bb.isExit()) return;
// Get the predicate of this block
DfgVertex* const predp = [&]() -> DfgVertex* {
// Entry block has no predecessors, use constant true
if (bb.isEnter()) return make<DfgConst>(m_logicp->fileline(), 1U, 1U);
// For any other block, 'or' together all the incoming predicates
const auto& inEdges = bb.inEdges();
auto it = inEdges.begin();
DfgVertex* resp = m_edgeToPredicatep[static_cast<const CfgEdge&>(*it)];
while (++it != inEdges.end()) {
DfgOr* const orp = make<DfgOr>(resp->fileline(), resp->dtype());
orp->rhsp(resp);
orp->lhsp(m_edgeToPredicatep[static_cast<const CfgEdge&>(*it)]);
resp = orp;
}
return resp;
}();
size_t n = m_nPathPred++; // Sequence number for temporaries
const DfgDataType& dtype = predp->dtype();
const auto mkTmp = [&](FileLine* flp, const char* name, DfgVertex* srcp) {
const std::string prefix = "_BB" + std::to_string(bb.id()) + "_" + name;
DfgVertexVar* const tmpp = m_converter.createTmp(*m_logicp, flp, dtype, prefix, n);
tmpp->srcp(srcp);
return tmpp;
};
// Assign it to a variable in case it's used multiple times
DfgVertexVar* const pInp = mkTmp(predp->fileline(), "PathIn", predp);
// For uncondional branches, the successor predicate edge is the same
if (!bb.isBranch()) {
m_edgeToPredicatep[bb.takenEdgep()] = mkTmp(pInp->fileline(), "Goto", pInp);
return;
}
// For branches, we need to factor in the branch condition
DfgVertex* const condp = m_bbToCondp[bb];
FileLine* const flp = condp->fileline();
// Predicate for taken branch: 'predp & condp'
{
DfgAnd* const takenPredp = make<DfgAnd>(flp, dtype);
takenPredp->lhsp(pInp);
takenPredp->rhsp(condp);
m_edgeToPredicatep[bb.takenEdgep()] = mkTmp(flp, "Taken", takenPredp);
}
// Predicate for untaken branch: 'predp & ~condp'
{
DfgAnd* const untknPredp = make<DfgAnd>(flp, dtype);
untknPredp->lhsp(pInp);
DfgNot* const notp = make<DfgNot>(flp, dtype);
notp->srcp(condp);
untknPredp->rhsp(notp);
m_edgeToPredicatep[bb.untknEdgep()] = mkTmp(flp, "Untkn", untknPredp);
}
}
// Returns true if all external updates to volatile variables are observed correctly
bool checkExtWrites() {
for (const DfgVertex* const vtxp : m_logicp->synth()) {
const DfgVertexVar* const varp = vtxp->cast<DfgVertexVar>();
if (!varp) continue;
// If the variable we synthesized this vertex for is volatile, and
// the value of the synthesized temporary is observed, we might be
// missing an external update, so we mut give up.
if (!varp->hasSinks()) continue;
if (!DfgVertexVar::isVolatile(varp->tmpForp())) continue;
++m_ctx.m_synt.nonSynExtWrite;
return false;
}
return true;
}
// Add the synthesized values as drivers to the output variables of the current DfgLogic
bool addSynthesizedOutput(SymTab& oSymTab) {
// It's possible we think a variable is written by the DfgLogic when
// it actauly isn't, e.g.: '{a[0], b[0]}[1] = ...' does not write 'b'.
// These LHS forms can happen after some earlier tranforms. We
// should just run V3Const on them earlier, but we will do belt and
// braces and check here too. We can't touch any output variables if so.
const bool missing = m_logicp->foreachSink([&](const DfgVertex& sink) {
const DfgUnresolved* const unresolvedp = sink.as<DfgUnresolved>();
AstNode* const tgtp = unresolvedp->singleSink()->as<DfgVertexVar>()->nodep();
// cppcheck-suppress constVariablePointer
Variable* const varp = reinterpret_cast<Variable*>(tgtp);
return !oSymTab.count(varp);
});
if (missing) {
++m_ctx.m_synt.nonSynFalseWrite;
return false;
}
// Add sinks to read the computed values for the target variables
return !m_logicp->foreachSink([&](DfgVertex& sink) {
DfgUnresolved* const unresolvedp = sink.as<DfgUnresolved>();
const DfgVertexVar* const varp = unresolvedp->singleSink()->as<DfgVertexVar>();
DfgVertexVar* const resp = oSymTab.at(reinterpret_cast<Variable*>(varp->nodep()));
UASSERT_OBJ(resp->srcp(), resp, "Undriven result");
// If the output is not used further in the synthesized logic itself,
// then resp will be deleted before we return, so we can just use
// its splice directly without ending up with a multi-use operation.
if (!resp->hasSinks()) {
unresolvedp->addDriver(resp->srcp()->as<DfgVertexSplice>());
return false; // OK, continue.
}
// TODO: computePropagatedDrivers cannot handle arrays, should
// never happen with simple continous assignments
if (!resp->isPacked()) {
++m_ctx.m_synt.nonSynArray;
return true; // Not OK, give up
}
// We need to add a new splice to avoid multi-use of the original splice
DfgSplicePacked* const splicep
= new DfgSplicePacked{m_dfg, resp->fileline(), resp->dtype()};
// Drivers are the same
const std::vector<Driver> drivers = computePropagatedDrivers({}, resp);
for (const Driver& d : drivers) splicep->addDriver(d.m_vtxp, d.m_lo, d.m_flp);
unresolvedp->addDriver(splicep);
return false; // OK, continue
});
}
// Synthesize the given AstAssignW. Returns true on success.
bool synthesizeAssignW(AstAssignW* nodep) {
++m_ctx.m_synt.inputAssign;
// Construct an equivalent AstAssign
AstNodeExpr* const lhsp = nodep->lhsp()->cloneTree(false);
AstNodeExpr* const rhsp = nodep->rhsp()->cloneTree(false);
AstAssign* const assignp = new AstAssign{nodep->fileline(), lhsp, rhsp};
// The input and output symbol tables
SymTab iSymTab;
SymTab oSymTab;
// Initialzie input symbol table
initializeEntrySymbolTable(iSymTab);
// Synthesize as if it was in a single CfgBlock CFG
DfgVertex* condp = nullptr;
const bool success = synthesizeBasicBlock(oSymTab, condp, {assignp}, iSymTab);
UASSERT_OBJ(!condp, nodep, "Conditional AstAssignW ???");
// Delete auxiliary AstAssign
VL_DO_DANGLING(assignp->deleteTree(), assignp);
if (!success) return false;
// Check exernal writes are observed correctly
if (!checkExtWrites()) return false;
// Add resolved output variable drivers
return addSynthesizedOutput(oSymTab);
}
// Synthesize the given AstAlways. Returns true on success.
bool synthesizeCfg(CfgGraph& cfg) {
++m_ctx.m_synt.inputAlways;
// If there is a backward edge (loop), we can't synthesize it
if (cfg.containsLoop()) {
++m_ctx.m_synt.nonSynLoop;
++m_ctx.m_synt.cfgCyclic;
return false;
}
// If it's a trivial CFG we can save on some work
if (cfg.nBlocks() == 1) {
++m_ctx.m_synt.cfgTrivial;
} else {
// Insert two-way join blocks to aid multiplexer ordering
if (cfg.insertTwoWayJoins()) {
++m_ctx.m_synt.cfgSp;
} else {
++m_ctx.m_synt.cfgDag;
}
// Initialize maps needed for non-trivial CFGs
m_domTree = CfgDominatorTree{cfg};
m_edgeToPredicatep = cfg.makeEdgeMap<DfgVertexVar*>();
}
// Initialize CfgMaps
m_bbToISymTab = cfg.makeBlockMap<SymTab>();
m_bbToOSymTab = cfg.makeBlockMap<SymTab>();
m_bbToCondp = cfg.makeBlockMap<DfgVertexVar*>();
// Synthesize all blocks
for (const V3GraphVertex& vtx : cfg.vertices()) {
const CfgBlock& bb = static_cast<const CfgBlock&>(vtx);
// Prepare the input symbol table of this block (enter, or join predecessor blocks)
if (!createInputSymbolTable(bb)) return false;
// Synthesize this block
DfgVertex* condp = nullptr;
if (!synthesizeBasicBlock(m_bbToOSymTab[bb], condp, bb.stmtps(), m_bbToISymTab[bb])) {
return false;
}
// Create a temporary for the branch condition as it might be used multiple times
if (condp) {
FileLine* const flp = condp->fileline();
const DfgDataType& dtype = condp->dtype();
const std::string prefix = "_BB" + std::to_string(bb.id()) + "_Cond";
const size_t n = m_nBranchCond++;
DfgVertexVar* const vp = m_converter.createTmp(*m_logicp, flp, dtype, prefix, n);
vp->srcp(condp);
m_bbToCondp[bb] = vp;
}
// Set the path predicates on the successor edges
assignPathPredicates(bb);
}
// Check exernal writes are observed correctly
if (!checkExtWrites()) return false;
// Add resolved output variable drivers
return addSynthesizedOutput(m_bbToOSymTab[cfg.exit()]);
}
// Synthesize a DfgLogic into regular vertices. Returns ture on success.
bool synthesize(DfgLogic& vtx) {
VL_RESTORER(m_logicp);
m_logicp = &vtx;
if (AstAssignW* const ap = VN_CAST(vtx.nodep()->stmtsp(), AssignW)) {
if (ap->nextp()) return false;
if (!synthesizeAssignW(ap)) return false;
++m_ctx.m_synt.synthAssign;
return true;
}
if (!synthesizeCfg(vtx.cfg())) return false;
++m_ctx.m_synt.synthAlways;
return true;
}
// 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;
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;
}
// 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() {
//-------------------------------------------------------------------
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;
++s_dfgSynthDebugCount;
if (s_dfgSynthDebugCount == s_dfgSynthDebugLimit) {
// This is the breaking logic
m_debugLogicp = logicp;
// Dump it
UINFOTREE(0, logicp->nodep(), "Problematic DfgLogic: " << logicp, " ");
V3EmitV::debugVerilogForTree(logicp->nodep(), std::cout);
debugDump("synth-lastok");
}
}
// 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 can't know if synthesis failed
revert(m_ctx.m_synt.revertNonSyn);
debugDump("synth-reverted");
//-------------------------------------------------------------------
UINFO(5, "Step 3: Resolve synthesized drivers of original (non-temporary) variables");
// List of multi-driven variables
std::vector<DfgVertexVar*> multidrivenps;
// Map from variable to its resolved driver
std::unordered_map<const DfgVertexVar*, DfgVertexSplice*> resolvedDrivers;
// Compute resolved drivers of all variablees
for (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
// Resolve the synthesized drivers
DfgVertexSplice* const resolvedp = [&]() -> DfgVertexSplice* {
// All synthesized drivers were normalized already,
// so if there is only one, it can be used directly
if (unresolvedp->nInputs() == 1) {
return unresolvedp->inputp(0)->as<DfgVertexSplice>();
}
// Otherwise gather the synthesized drivers
std::vector<Driver> drivers = gatherDriversUnresolved(unresolvedp);
// Normalize them, make resolved driver if all good
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
if (!resolvedp) continue;
// Add to map for next loop
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
revert(m_ctx.m_synt.revertMultidrive);
// Replace all DfgUnresolved with the resolved drivers
for (const DfgVertexVar& var : m_dfg.varVertices()) {
if (!var.srcp()) continue;
DfgUnresolved* const srcp = var.srcp()->cast<DfgUnresolved>();
if (!srcp) break; // Stop when reached the synthesized temporaries
// Replace it
srcp->replaceWith(resolvedDrivers.at(&var));
VL_DO_DANGLING(srcp->unlinkDelete(m_dfg), srcp);
}
debugDump("synth-resolved");
//-------------------------------------------------------------------
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");
// Process only DfgLogic
DfgLogic* const logicp = vtxp->cast<DfgLogic>();
if (!logicp) continue;
// Earlier pass should have removed all sinks
UASSERT_OBJ(!logicp->hasSinks(), logicp, "DfgLogic sink remains");
if (!logicp->synth().empty()) {
// If synthesized, delete the corresponding AstNode. It is now in Dfg.
logicp->nodep()->unlinkFrBack()->deleteTree();
} else {
// Not synthesized. Logic stays in Ast. Mark source variables
//as read in module. Outputs already marked by revertTransivelyAndRemove.
logicp->foreachSource([](DfgVertex& src) { //
src.as<DfgVertexVar>()->setHasModRdRefs();
return false;
});
}
// Delete this DfgLogic
VL_DO_DANGLING(logicp->unlinkDelete(m_dfg), logicp);
}
// Reset the debug pointer, we have deleted it in the loop above ...
m_debugLogicp = nullptr;
debugDump("synth-rmlogics");
//-------------------------------------------------------------------
UINFO(5, "Step 5: Remove unnecessary splices");
for (DfgVertex* const vtxp : m_dfg.opVertices().unlinkable()) {
DfgVertexSplice* const splicep = vtxp->cast<DfgVertexSplice>();
if (!splicep) continue;
// Might not have a sink if the driving logic was revered, remove
if (!splicep->hasSinks()) {
VL_DO_DANGLING(splicep->unlinkDelete(m_dfg), splicep);
continue;
}
// It should alway have drivers
UASSERT_OBJ(splicep->nInputs(), splicep, "Splice with no drivers");
// If redundant, remove it
if (DfgVertex* const wholep = splicep->wholep()) {
if (DfgVertexVar* const varp = splicep->singleSink()->cast<DfgVertexVar>()) {
varp->driverFileLine(splicep->driverFileLine(0));
}
splicep->replaceWith(wholep);
VL_DO_DANGLING(splicep->unlinkDelete(m_dfg), splicep);
}
}
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(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 : 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");
}
}
V3DfgPasses::typeCheck(dfg);
}
}
};
// 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()) {
for (DfgVertex& vtx : dfg.opVertices()) {
if (DfgLogic* const logicp = vtx.cast<DfgLogic>()) logicp->setSelectedForSynthesis();
}
return;
}
// 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);
for (DfgVertexVar& var : dfg.varVertices()) {
if (!scc.at(var)) continue;
// This is a circular variable
circularVarps.emplace_back(&var);
}
}
// 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 continuous assignments 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 (logicp->nodep()->keyword() == VAlwaysKwd::CONT_ASSIGN) {
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;
});
});
}
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, ctx.m_synthContext);
} else {
AstToDfgSynthesize</* T_Scoped: */ true>::apply(dfg, ctx.m_synthContext);
}
}