302 lines
12 KiB
C++
302 lines
12 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Convert AstModule to DfgGraph
|
|
//
|
|
// 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
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Convert and AstModule (before V3Scope), or the entire AstNetlist
|
|
// (after V3Scope) to an initial DfgGraph composed onlyof DfgLogic,
|
|
// DfgUnresolved and DfgVertexVar vertices. This will later be synthesized
|
|
// into primitive operations by V3DfgPasses::synthesize.
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
#include "V3Cfg.h"
|
|
#include "V3Const.h"
|
|
#include "V3Dfg.h"
|
|
#include "V3DfgPasses.h"
|
|
|
|
#include <iterator>
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
template <bool T_Scoped>
|
|
class AstToDfgVisitor final : public VNVisitor {
|
|
// NODE STATE
|
|
// AstVar/AstVarScope::user2() -> DfgVertexVar* : the corresponding variable vertex
|
|
// AstVar/AstVarScope::user3() -> bool : Already gathered - used fine grained below
|
|
const VNUser2InUse m_user2InUse;
|
|
|
|
// TYPES
|
|
using RootType = std::conditional_t<T_Scoped, AstNetlist, AstModule>;
|
|
using Variable = std::conditional_t<T_Scoped, AstVarScope, AstVar>;
|
|
|
|
// STATE
|
|
DfgGraph& m_dfg; // The graph being built
|
|
V3DfgAstToDfgContext& m_ctx; // The context for stats
|
|
AstScope* m_scopep = nullptr; // The current scope, iff T_Scoped
|
|
|
|
// 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());
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<std::vector<Variable*>> getLiveVariables(const CfgGraph& cfg) {
|
|
// TODO: remove the useless reinterpret_casts when C++17 'if constexpr' actually works
|
|
if VL_CONSTEXPR_CXX17 (T_Scoped) {
|
|
std::unique_ptr<std::vector<AstVarScope*>> result = V3Cfg::liveVarScopes(cfg);
|
|
const auto resultp = reinterpret_cast<std::vector<Variable*>*>(result.release());
|
|
return std::unique_ptr<std::vector<Variable*>>{resultp};
|
|
} else {
|
|
std::unique_ptr<std::vector<AstVar*>> result = V3Cfg::liveVars(cfg);
|
|
const auto resultp = reinterpret_cast<std::vector<Variable*>*>(result.release());
|
|
return std::unique_ptr<std::vector<Variable*>>{resultp};
|
|
}
|
|
}
|
|
|
|
// Mark variables referenced under node
|
|
static void markReferenced(const AstNode* nodep) {
|
|
nodep->foreach([](const AstVarRef* refp) {
|
|
Variable* const tgtp = getTarget(refp);
|
|
// Mark as read from non-DFG logic
|
|
if (refp->access().isReadOrRW()) DfgVertexVar::setHasModRdRefs(tgtp);
|
|
// Mark as written from non-DFG logic
|
|
if (refp->access().isWriteOrRW()) DfgVertexVar::setHasModWrRefs(tgtp);
|
|
});
|
|
}
|
|
|
|
DfgVertexVar* getVarVertex(Variable* varp) {
|
|
if (!varp->user2p()) {
|
|
// TODO: fix this up when removing the different flavours of DfgVar
|
|
const AstNodeDType* const dtypep = varp->dtypep()->skipRefp();
|
|
DfgVertexVar* const vtxp
|
|
= VN_IS(dtypep, UnpackArrayDType)
|
|
? static_cast<DfgVertexVar*>(new DfgVarArray{m_dfg, varp})
|
|
: static_cast<DfgVertexVar*>(new DfgVarPacked{m_dfg, varp});
|
|
varp->user2p(vtxp);
|
|
}
|
|
return varp->user2u().template to<DfgVertexVar*>();
|
|
}
|
|
|
|
// Gather variables written by the given logic node.
|
|
// Return nullptr if any are not supported.
|
|
std::unique_ptr<std::vector<DfgVertexVar*>> gatherWritten(const AstNode* nodep) {
|
|
const VNUser3InUse user3InUse;
|
|
std::unique_ptr<std::vector<DfgVertexVar*>> resp{new std::vector<DfgVertexVar*>{}};
|
|
// We can ignore AstVarXRef here. The only thing we can do with DfgLogic is
|
|
// synthesize it into regular vertices, which will fail on a VarXRef at that point.
|
|
const bool abort = nodep->exists([&](const AstNodeVarRef* vrefp) -> bool {
|
|
if (VN_IS(vrefp, VarXRef)) return true;
|
|
if (vrefp->access().isReadOnly()) return false;
|
|
Variable* const varp = getTarget(VN_AS(vrefp, VarRef));
|
|
if (!V3Dfg::isSupported(varp)) return true;
|
|
if (!varp->user3SetOnce()) resp->emplace_back(getVarVertex(varp));
|
|
return false;
|
|
});
|
|
if (abort) {
|
|
++m_ctx.m_nonRepVar;
|
|
return nullptr;
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
// Gather variables read by the given logic node.
|
|
// Return nullptr if any are not supported.
|
|
std::unique_ptr<std::vector<DfgVertexVar*>> gatherRead(const AstNode* nodep) {
|
|
const VNUser3InUse user3InUse;
|
|
std::unique_ptr<std::vector<DfgVertexVar*>> resp{new std::vector<DfgVertexVar*>{}};
|
|
// We can ignore AstVarXRef here. The only thing we can do with DfgLogic is
|
|
// synthesize it into regular vertices, which will fail on a VarXRef at that point.
|
|
const bool abort = nodep->exists([&](const AstNodeVarRef* vrefp) -> bool {
|
|
if (VN_IS(vrefp, VarXRef)) return true;
|
|
if (vrefp->access().isWriteOnly()) return false;
|
|
Variable* const varp = getTarget(VN_AS(vrefp, VarRef));
|
|
if (!V3Dfg::isSupported(varp)) return true;
|
|
if (!varp->user3SetOnce()) resp->emplace_back(getVarVertex(varp));
|
|
return false;
|
|
});
|
|
if (abort) {
|
|
++m_ctx.m_nonRepVar;
|
|
return nullptr;
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
// Gather variables live in to the given CFG.
|
|
// Return nullptr if any are not supported.
|
|
std::unique_ptr<std::vector<DfgVertexVar*>> gatherLive(const CfgGraph& cfg) {
|
|
// Run analysis
|
|
std::unique_ptr<std::vector<Variable*>> varps = getLiveVariables(cfg);
|
|
if (!varps) {
|
|
++m_ctx.m_nonRepLive;
|
|
return nullptr;
|
|
}
|
|
|
|
// Convert to vertics
|
|
const VNUser3InUse user3InUse;
|
|
std::unique_ptr<std::vector<DfgVertexVar*>> resp{new std::vector<DfgVertexVar*>{}};
|
|
resp->reserve(varps->size());
|
|
for (Variable* const varp : *varps) {
|
|
if (!V3Dfg::isSupported(varp)) {
|
|
++m_ctx.m_nonRepVar;
|
|
return nullptr;
|
|
}
|
|
UASSERT_OBJ(!varp->user3SetOnce(), varp, "Live variables should be unique");
|
|
resp->emplace_back(getVarVertex(varp));
|
|
}
|
|
return resp;
|
|
}
|
|
|
|
// Connect inputs and outputs of a DfgLogic
|
|
void connect(DfgLogic& vtx, const std::vector<DfgVertexVar*>& iVarps,
|
|
const std::vector<DfgVertexVar*>& oVarps) {
|
|
// Connect inputs
|
|
for (DfgVertexVar* const iVarp : iVarps) vtx.addInput(iVarp);
|
|
// Connect outputs
|
|
for (DfgVertexVar* const oVarp : oVarps) {
|
|
if (!oVarp->srcp()) oVarp->srcp(new DfgUnresolved{m_dfg, oVarp});
|
|
oVarp->srcp()->as<DfgUnresolved>()->addDriver(&vtx);
|
|
}
|
|
}
|
|
|
|
// Convert AstAssignW to DfgLogic, return true if successful.
|
|
bool convert(AstAssignW* nodep) {
|
|
// Cannot handle assignment with timing control
|
|
if (nodep->timingControlp()) return false;
|
|
|
|
// Potentially convertible block
|
|
++m_ctx.m_inputs;
|
|
// Gather written variables, give up if any are not supported
|
|
const std::unique_ptr<std::vector<DfgVertexVar*>> oVarpsp = gatherWritten(nodep);
|
|
if (!oVarpsp) return false;
|
|
// Gather read variables, give up if any are not supported
|
|
const std::unique_ptr<std::vector<DfgVertexVar*>> iVarpsp = gatherRead(nodep);
|
|
if (!iVarpsp) return false;
|
|
// Create the DfgLogic
|
|
DfgLogic* const logicp = new DfgLogic{m_dfg, nodep, m_scopep};
|
|
// Connect it up
|
|
connect(*logicp, *iVarpsp, *oVarpsp);
|
|
// Done
|
|
++m_ctx.m_representable;
|
|
return true;
|
|
}
|
|
|
|
// Convert AstAlways to DfgLogic, return true if successful.
|
|
bool convert(AstAlways* nodep) {
|
|
// Can only handle combinational logic
|
|
if (nodep->sentreep()) return false;
|
|
const VAlwaysKwd kwd = nodep->keyword();
|
|
if (kwd != VAlwaysKwd::ALWAYS && kwd != VAlwaysKwd::ALWAYS_COMB) return false;
|
|
|
|
// Potentially convertible block
|
|
++m_ctx.m_inputs;
|
|
// Attempt to build CFG of AstAlways, give up if failed
|
|
std::unique_ptr<CfgGraph> cfgp = CfgGraph::build(nodep->stmtsp());
|
|
if (!cfgp) {
|
|
++m_ctx.m_nonRepCfg;
|
|
return false;
|
|
}
|
|
// Gather written variables, give up if any are not supported
|
|
const std::unique_ptr<std::vector<DfgVertexVar*>> oVarpsp = gatherWritten(nodep);
|
|
if (!oVarpsp) return false;
|
|
// Gather read variables, give up if any are not supported
|
|
const std::unique_ptr<std::vector<DfgVertexVar*>> iVarpsp = gatherLive(*cfgp);
|
|
if (!iVarpsp) return false;
|
|
// Create the DfgLogic
|
|
DfgLogic* const logicp = new DfgLogic{m_dfg, nodep, m_scopep, std::move(cfgp)};
|
|
// Connect it up
|
|
connect(*logicp, *iVarpsp, *oVarpsp);
|
|
// Done
|
|
++m_ctx.m_representable;
|
|
return true;
|
|
}
|
|
|
|
// VISITORS
|
|
// Unhandled node
|
|
void visit(AstNode* nodep) override { markReferenced(nodep); }
|
|
|
|
// Containers to descend through to find logic constructs
|
|
void visit(AstNetlist* nodep) override { iterateAndNextNull(nodep->modulesp()); }
|
|
void visit(AstModule* nodep) override { iterateAndNextNull(nodep->stmtsp()); }
|
|
void visit(AstIface* nodep) override {
|
|
if (!nodep->hasVirtualRef()) {
|
|
iterateAndNextNull(nodep->stmtsp());
|
|
} else {
|
|
markReferenced(nodep);
|
|
}
|
|
}
|
|
void visit(AstTopScope* nodep) override { iterate(nodep->scopep()); }
|
|
void visit(AstScope* nodep) override {
|
|
VL_RESTORER(m_scopep);
|
|
m_scopep = nodep;
|
|
iterateChildren(nodep);
|
|
}
|
|
void visit(AstActive* nodep) override {
|
|
if (nodep->hasCombo()) {
|
|
iterateChildren(nodep);
|
|
} else {
|
|
markReferenced(nodep);
|
|
}
|
|
}
|
|
|
|
// Non-representable constructs
|
|
void visit(AstCell* nodep) override { markReferenced(nodep); }
|
|
void visit(AstNodeProcedure* nodep) override { markReferenced(nodep); }
|
|
|
|
// Potentially representable constructs
|
|
void visit(AstAssignW* nodep) override {
|
|
if (!convert(nodep)) markReferenced(nodep);
|
|
}
|
|
void visit(AstAlways* nodep) override {
|
|
if (!convert(nodep)) markReferenced(nodep);
|
|
}
|
|
|
|
// CONSTRUCTOR
|
|
AstToDfgVisitor(DfgGraph& dfg, RootType& root, V3DfgAstToDfgContext& ctx)
|
|
: m_dfg{dfg}
|
|
, m_ctx{ctx} {
|
|
iterate(&root);
|
|
}
|
|
VL_UNCOPYABLE(AstToDfgVisitor);
|
|
VL_UNMOVABLE(AstToDfgVisitor);
|
|
|
|
public:
|
|
static void apply(DfgGraph& dfg, RootType& root, V3DfgAstToDfgContext& ctx) {
|
|
// Convert all logic under 'root'
|
|
AstToDfgVisitor{dfg, root, ctx};
|
|
// Remove unread and undriven variables (created when something failed to convert)
|
|
for (DfgVertexVar* const varp : dfg.varVertices().unlinkable()) {
|
|
if (!varp->srcp() && !varp->hasSinks()) VL_DO_DANGLING(varp->unlinkDelete(dfg), varp);
|
|
}
|
|
}
|
|
};
|
|
|
|
std::unique_ptr<DfgGraph> V3DfgPasses::astToDfg(AstModule& module, V3DfgContext& ctx) {
|
|
DfgGraph* const dfgp = new DfgGraph{&module, module.name()};
|
|
AstToDfgVisitor</* T_Scoped: */ false>::apply(*dfgp, module, ctx.m_ast2DfgContext);
|
|
return std::unique_ptr<DfgGraph>{dfgp};
|
|
}
|
|
|
|
std::unique_ptr<DfgGraph> V3DfgPasses::astToDfg(AstNetlist& netlist, V3DfgContext& ctx) {
|
|
DfgGraph* const dfgp = new DfgGraph{nullptr, "netlist"};
|
|
AstToDfgVisitor</* T_Scoped: */ true>::apply(*dfgp, netlist, ctx.m_ast2DfgContext);
|
|
return std::unique_ptr<DfgGraph>{dfgp};
|
|
}
|