mirror of
https://github.com/verilator/verilator.git
synced 2026-10-07 02:23:19 +02:00
The constant pool is now an ordinary package, created with the netlist and instantiated under $root like any other package, and most special handling has been removed, including AstConstPool. Lookups go through V3ConstPool, which has a singleton instance owned by V3Global. Static methods on V3Common form the public interfce to add/find constant pool entries. With that, the constant pool is usable at any stage during compilation, so enum and dimension tables created by V3Width, and enum value tables created by V3Randomize now also live in the constant pool instead of $unit, so identical tables are shared. Associative array constants are handled separately from unpacked tables, which used to be broken but unused. Emitted constant pool variables use direct initialization, and `constinit` with C++20 where the type allows it. This ensures we don't change a run-time in a way that would result in unintended code size increase. -fno-merge-const-pool, which was introduced years ago but never prompted a bug report is deprecated and has no effect. This is also prep for future work.
345 lines
14 KiB
C++
345 lines
14 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Convert DfgGraph to AstModule
|
|
//
|
|
// 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
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Convert DfgGraph back to AstModule. We recursively construct AstNodeExpr expressions for each
|
|
// DfgVertex which represents a storage location (e.g.: DfgVarPacked), or has multiple sinks
|
|
// without driving a storage location (and hence needs a temporary variable to duplication). The
|
|
// recursion stops when we reach a DfgVertex representing a storage location (e.g.: DfgVarPacked),
|
|
// or a vertex that that has multiple sinks (as these nodes will have a [potentially new temporary]
|
|
// corresponding// storage location). Redundant variables (those whose source vertex drives
|
|
// multiple variables) are eliminated when possible. Vertices driving multiple variables are
|
|
// rendered once, driving an arbitrarily (but deterministically) chosen canonical variable, and the
|
|
// corresponding redundant variables are assigned from the canonical variable.
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
#include "V3Dfg.h"
|
|
#include "V3DfgPasses.h"
|
|
#include "V3UniqueNames.h"
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
namespace {
|
|
|
|
// Create an AstNodeExpr out of a DfgVertex. For most AstNodeExpr subtypes, this can be done
|
|
// automatically. For the few special cases, we provide specializations below
|
|
template <typename T_Node, typename T_Vertex, typename... Ops>
|
|
T_Node* makeNode(const T_Vertex* vtxp, Ops... ops) {
|
|
T_Node* const nodep = new T_Node{vtxp->fileline(), ops...};
|
|
UASSERT_OBJ(nodep->width() == static_cast<int>(vtxp->width()), vtxp,
|
|
"Incorrect width in AstNode created from DfgVertex "
|
|
<< vtxp->typeName() << ": " << nodep->width() << " vs " << vtxp->width());
|
|
return nodep;
|
|
}
|
|
|
|
//======================================================================
|
|
// Vertices needing special conversion
|
|
|
|
template <>
|
|
AstCountOnes* makeNode<AstCountOnes, DfgCountOnes, AstNodeExpr*>( //
|
|
const DfgCountOnes* vtxp, AstNodeExpr* op1) {
|
|
AstCountOnes* const nodep = new AstCountOnes{vtxp->fileline(), op1};
|
|
nodep->dtypeSetLogicSized(vtxp->width(), VSigning::UNSIGNED);
|
|
return nodep;
|
|
}
|
|
|
|
template <>
|
|
AstExtend* makeNode<AstExtend, DfgExtend, AstNodeExpr*>( //
|
|
const DfgExtend* vtxp, AstNodeExpr* op1) {
|
|
return new AstExtend{vtxp->fileline(), op1, static_cast<int>(vtxp->width())};
|
|
}
|
|
|
|
template <>
|
|
AstExtendS* makeNode<AstExtendS, DfgExtendS, AstNodeExpr*>( //
|
|
const DfgExtendS* vtxp, AstNodeExpr* op1) {
|
|
return new AstExtendS{vtxp->fileline(), op1, static_cast<int>(vtxp->width())};
|
|
}
|
|
|
|
template <>
|
|
AstShiftL* makeNode<AstShiftL, DfgShiftL, AstNodeExpr*, AstNodeExpr*>( //
|
|
const DfgShiftL* vtxp, AstNodeExpr* op1, AstNodeExpr* op2) {
|
|
return new AstShiftL{vtxp->fileline(), op1, op2, static_cast<int>(vtxp->width())};
|
|
}
|
|
|
|
template <>
|
|
AstShiftR* makeNode<AstShiftR, DfgShiftR, AstNodeExpr*, AstNodeExpr*>( //
|
|
const DfgShiftR* vtxp, AstNodeExpr* op1, AstNodeExpr* op2) {
|
|
return new AstShiftR{vtxp->fileline(), op1, op2, static_cast<int>(vtxp->width())};
|
|
}
|
|
|
|
template <>
|
|
AstShiftRS* makeNode<AstShiftRS, DfgShiftRS, AstNodeExpr*, AstNodeExpr*>( //
|
|
const DfgShiftRS* vtxp, AstNodeExpr* op1, AstNodeExpr* op2) {
|
|
return new AstShiftRS{vtxp->fileline(), op1, op2, static_cast<int>(vtxp->width())};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class DfgToAstVisitor final : DfgVisitor {
|
|
// NODE STATE
|
|
|
|
// AstScope::user2p // The combinational AstActive under this scope
|
|
const VNUser2InUse m_user2InUse;
|
|
|
|
// STATE
|
|
V3DfgDfgToAstContext& m_ctx; // The context for stats
|
|
AstNodeExpr* m_resultp = nullptr; // The result node of the current traversal
|
|
AstAlways* m_alwaysp = nullptr; // Process to add assignments to, if have a default driver
|
|
AstActive* m_activep = nullptr; // The AstNodeModule or AstActive to insert assigns into
|
|
|
|
// METHODS
|
|
static AstActive* getCombActive(AstScope* scopep) {
|
|
if (!scopep->user2p()) {
|
|
// Try to find the existing combinational AstActive
|
|
for (AstNode* nodep = scopep->blocksp(); nodep; nodep = nodep->nextp()) {
|
|
AstActive* const activep = VN_CAST(nodep, Active);
|
|
if (!activep) continue;
|
|
if (activep->hasCombo()) {
|
|
scopep->user2p(activep);
|
|
break;
|
|
}
|
|
}
|
|
// If there isn't one, create a new one
|
|
if (!scopep->user2p()) {
|
|
FileLine* const flp = scopep->fileline();
|
|
AstSenTree* const senTreep
|
|
= new AstSenTree{flp, new AstSenItem{flp, AstSenItem::Combo{}}};
|
|
AstActive* const activep = new AstActive{flp, "", senTreep};
|
|
activep->senTreeStorep(senTreep);
|
|
scopep->addBlocksp(activep);
|
|
scopep->user2p(activep);
|
|
}
|
|
}
|
|
return VN_AS(scopep->user2p(), Active);
|
|
}
|
|
|
|
AstNodeExpr* convertDfgVertexToAstNodeExpr(DfgVertex* vtxp) {
|
|
UASSERT_OBJ(!m_resultp, vtxp, "Result already computed");
|
|
UASSERT_OBJ(vtxp->is<DfgVertexVar>() || vtxp->is<DfgConst>() //
|
|
|| !vtxp->hasMultipleSinks() || vtxp->isCheaperThanLoad(), //
|
|
vtxp, "Intermediate DFG value with multiple uses");
|
|
iterate(vtxp);
|
|
UASSERT_OBJ(m_resultp, vtxp, "Missing result");
|
|
AstNodeExpr* const resultp = m_resultp;
|
|
m_resultp = nullptr;
|
|
return resultp;
|
|
}
|
|
|
|
void createAssignment(FileLine* flp, AstNodeExpr* lhsp, DfgVertex* driverp) {
|
|
// Keep track of statisticss
|
|
++m_ctx.m_resultEquations;
|
|
// Render the driver
|
|
AstNodeExpr* const rhsp = convertDfgVertexToAstNodeExpr(driverp);
|
|
// Update LHS locations to reflect the location of the original driver
|
|
lhsp->foreach([&](AstNode* nodep) { nodep->fileline(flp); });
|
|
|
|
// If using a process, add Assign there
|
|
if (m_alwaysp) {
|
|
m_alwaysp->addStmtsp(new AstAssign{flp, lhsp, rhsp});
|
|
return;
|
|
}
|
|
|
|
// Otherwise create an AssignW
|
|
AstAssignW* const ap = new AstAssignW{flp, lhsp, rhsp};
|
|
m_activep->addStmtsp(new AstAlways{ap});
|
|
}
|
|
|
|
void convertDriver(FileLine* flp, AstNodeExpr* lhsp, DfgVertex* driverp) {
|
|
if (DfgSplicePacked* const sPackedp = driverp->cast<DfgSplicePacked>()) {
|
|
// Partial assignment of packed value
|
|
sPackedp->foreachDriver([&](DfgVertex& src, uint32_t lo, FileLine* dflp) {
|
|
// Create Sel
|
|
AstConst* const lsbp = new AstConst{dflp, lo};
|
|
const int width = static_cast<int>(src.width());
|
|
AstSel* const nLhsp = new AstSel{dflp, lhsp->cloneTreePure(false), lsbp, width};
|
|
// Convert source
|
|
convertDriver(dflp, nLhsp, &src);
|
|
// Delete Sel - was cloned
|
|
VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp);
|
|
return false;
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (DfgSpliceArray* const sArrayp = driverp->cast<DfgSpliceArray>()) {
|
|
// Partial assignment of array variable
|
|
sArrayp->foreachDriver([&](DfgVertex& src, uint32_t lo, FileLine* dflp) {
|
|
UASSERT_OBJ(src.size() == 1, &src, "We only handle single elements");
|
|
// Create ArraySel
|
|
AstConst* const idxp = new AstConst{dflp, lo};
|
|
AstArraySel* const nLhsp = new AstArraySel{dflp, lhsp->cloneTreePure(false), idxp};
|
|
// Convert source
|
|
if (const DfgUnitArray* const uap = src.cast<DfgUnitArray>()) {
|
|
convertDriver(dflp, nLhsp, uap->srcp());
|
|
} else {
|
|
convertDriver(dflp, nLhsp, &src);
|
|
}
|
|
// Delete ArraySel - was cloned
|
|
VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp);
|
|
return false;
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (const DfgUnitArray* const uap = driverp->cast<DfgUnitArray>()) {
|
|
// Single element array being assigned a unit array. Needs an ArraySel.
|
|
AstConst* const idxp = new AstConst{flp, 0};
|
|
AstArraySel* const nLhsp = new AstArraySel{flp, lhsp->cloneTreePure(false), idxp};
|
|
// Convert source
|
|
convertDriver(flp, nLhsp, uap->srcp());
|
|
// Delete ArraySel - was cloned
|
|
VL_DO_DANGLING(nLhsp->deleteTree(), nLhsp);
|
|
return;
|
|
}
|
|
|
|
// Base case: assign vertex to current lhs
|
|
createAssignment(flp, lhsp->cloneTreePure(false), driverp);
|
|
}
|
|
|
|
// VISITORS
|
|
void visit(DfgVertex* vtxp) override { // LCOV_EXCL_START
|
|
vtxp->v3fatalSrc("Unhandled DfgVertex: " << vtxp->typeName());
|
|
} // LCOV_EXCL_STOP
|
|
|
|
void visit(DfgVarPacked* vtxp) override {
|
|
m_resultp = new AstVarRef{vtxp->fileline(), vtxp->vscp(), VAccess::READ};
|
|
}
|
|
|
|
void visit(DfgVarArray* vtxp) override {
|
|
m_resultp = new AstVarRef{vtxp->fileline(), vtxp->vscp(), VAccess::READ};
|
|
}
|
|
|
|
void visit(DfgPrev* vtxp) override {
|
|
m_resultp = new AstVarRef{vtxp->fileline(), vtxp->vscp(), VAccess::READ};
|
|
}
|
|
|
|
void visit(DfgConst* vtxp) override { //
|
|
m_resultp = new AstConst{vtxp->fileline(), vtxp->num()};
|
|
}
|
|
void visit(DfgCReset* vtxp) override {
|
|
DfgVertex* const sinkp = vtxp->singleSink();
|
|
UASSERT_OBJ(sinkp, vtxp, "CReset should only have one sink");
|
|
UASSERT_OBJ(sinkp->is<DfgVertexVar>(), sinkp, "CReset should drive a variable");
|
|
AstVar* const varp = sinkp->as<DfgVertexVar>()->vscp()->varp();
|
|
m_resultp = new AstCReset{vtxp->fileline(), varp, false};
|
|
}
|
|
void visit(DfgMatchMasked* vtxp) override {
|
|
FileLine* const flp = vtxp->fileline();
|
|
AstNodeExpr* const lhsp = convertDfgVertexToAstNodeExpr(vtxp->lhsp());
|
|
AstVarScope* const matchp = vtxp->matchp()->as<DfgVertexVar>()->vscp();
|
|
m_resultp = new AstMatchMasked{flp, lhsp, new AstVarRef{flp, matchp, VAccess::READ}};
|
|
}
|
|
|
|
void visit(DfgRep* vtxp) override {
|
|
FileLine* const flp = vtxp->fileline();
|
|
AstNodeExpr* const srcp = convertDfgVertexToAstNodeExpr(vtxp->srcp());
|
|
m_resultp = new AstReplicate{flp, srcp, vtxp->count()};
|
|
}
|
|
|
|
void visit(DfgSel* vtxp) override {
|
|
FileLine* const flp = vtxp->fileline();
|
|
AstNodeExpr* const fromp = convertDfgVertexToAstNodeExpr(vtxp->fromp());
|
|
AstConst* const lsbp = new AstConst{flp, vtxp->lsb()};
|
|
m_resultp = new AstSel{flp, fromp, lsbp, static_cast<int>(vtxp->width())};
|
|
}
|
|
|
|
void visit(DfgMux* vtxp) override {
|
|
FileLine* const flp = vtxp->fileline();
|
|
AstNodeExpr* const fromp = convertDfgVertexToAstNodeExpr(vtxp->fromp());
|
|
AstNodeExpr* const lsbp = convertDfgVertexToAstNodeExpr(vtxp->lsbp());
|
|
m_resultp = new AstSel{flp, fromp, lsbp, static_cast<int>(vtxp->width())};
|
|
}
|
|
|
|
// The rest of the 'visit' methods are generated by 'astgen'
|
|
#include "V3Dfg__gen_dfg_to_ast.h"
|
|
|
|
// Constructor
|
|
DfgToAstVisitor(DfgGraph& dfg, V3DfgDfgToAstContext& ctx)
|
|
: m_ctx{ctx} {
|
|
if (v3Global.opt.debugCheck()) V3DfgPasses::typeCheck(dfg);
|
|
|
|
// Convert the graph back to combinational assignments. The graph must have been
|
|
// regularized, so we only need to render variable assignments and update Ast references.
|
|
|
|
// Render variable assignments
|
|
for (DfgVertexVar& vtx : dfg.varVertices()) {
|
|
// If there is no driver (this vertex is an input to the graph), then nothing to do.
|
|
if (!vtx.srcp()) {
|
|
UASSERT_OBJ(!vtx.defaultp(), &vtx, "Only default driver on variable");
|
|
continue;
|
|
}
|
|
|
|
++m_ctx.m_outputVariables;
|
|
|
|
// Render variable assignments
|
|
FileLine* const flp = vtx.driverFileLine() ? vtx.driverFileLine() : vtx.fileline();
|
|
AstVarRef* const lhsp = new AstVarRef{flp, vtx.vscp(), VAccess::WRITE};
|
|
|
|
// Add it to the scope holding the target variable
|
|
VL_RESTORER(m_activep);
|
|
m_activep = getCombActive(vtx.vscp()->scopep());
|
|
|
|
// If there is a default value, render all drivers under an AstAlways
|
|
VL_RESTORER(m_alwaysp);
|
|
if (DfgVertex* const defaultp = vtx.defaultp()) {
|
|
++m_ctx.m_outputVariablesWithDefault;
|
|
m_alwaysp = new AstAlways{vtx.fileline(), VAlwaysKwd::ALWAYS_COMB, nullptr};
|
|
m_activep->addStmtsp(m_alwaysp);
|
|
// The default assignment needs to go first
|
|
createAssignment(vtx.fileline(), lhsp->cloneTreePure(false), defaultp);
|
|
}
|
|
|
|
// Render the drivers
|
|
convertDriver(flp, lhsp, vtx.srcp());
|
|
|
|
// convetDriver always clones lhsp
|
|
VL_DO_DANGLING(lhsp->deleteTree(), lhsp);
|
|
}
|
|
|
|
// Update Ast References
|
|
for (DfgVertexAst& vtx : dfg.astVertices()) {
|
|
if (DfgAstRd* const rVtxp = vtx.cast<DfgAstRd>()) {
|
|
// Render the driver
|
|
AstNodeExpr* const exprp = convertDfgVertexToAstNodeExpr(rVtxp->srcp());
|
|
// If it's the same as the reference, do not replace it so FileLines are preserved
|
|
if (exprp->sameTree(rVtxp->exprp())) {
|
|
VL_DO_DANGLING(exprp->deleteTree(), exprp);
|
|
continue;
|
|
}
|
|
// Replace the reference with the expression
|
|
if (VN_IS(exprp, VarRef)) {
|
|
exprp->fileline(rVtxp->exprp()->fileline());
|
|
++m_ctx.m_varRefsSubstituted;
|
|
} else {
|
|
++m_ctx.m_expressionsInlined;
|
|
}
|
|
rVtxp->exprp()->replaceWith(exprp);
|
|
rVtxp->exprp()->deleteTree();
|
|
rVtxp->exprp(exprp);
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
static void apply(DfgGraph& dfg, V3DfgDfgToAstContext& ctx) { DfgToAstVisitor{dfg, ctx}; }
|
|
};
|
|
|
|
void V3DfgPasses::dfgToAst(DfgGraph& dfg, V3DfgContext& ctx) {
|
|
DfgToAstVisitor::apply(dfg, ctx.m_dfg2AstContext);
|
|
}
|