mirror of
https://github.com/verilator/verilator.git
synced 2026-10-05 17:43:31 +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.
292 lines
12 KiB
C++
292 lines
12 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Generate C language constructors and AstCReset nodes.
|
|
//
|
|
// 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
|
|
//
|
|
//*************************************************************************
|
|
// V3CCtors's Transformations:
|
|
// Iterates over all modules and
|
|
// for all AstVar, create a creates a AstCReset node in an _ctor_var_reset AstCFunc.
|
|
// for all AstCoverDecl, move the declaration into a _configure_coverage AstCFunc.
|
|
// For each variable that needs reset, add a AstCReset node.
|
|
//
|
|
// For primary inputs, add _eval_debug_assertions.
|
|
//
|
|
// This transformation honors outputSplitCFuncs.
|
|
//*************************************************************************
|
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
#include "V3CCtors.h"
|
|
|
|
#include "V3EmitCBase.h"
|
|
|
|
#include <list>
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
class VCtorType final {
|
|
public:
|
|
enum en : uint8_t { MODULE, CLASS, COVERAGE };
|
|
|
|
private:
|
|
const enum en m_e;
|
|
|
|
public:
|
|
// cppcheck-suppress noExplicitConstructor
|
|
constexpr VCtorType(en _e)
|
|
: m_e{_e} {}
|
|
bool isClass() const { return m_e == CLASS; }
|
|
bool isCoverage() const { return m_e == COVERAGE; }
|
|
};
|
|
|
|
class V3CCtorsBuilder final {
|
|
AstNodeModule* const m_modp; // Current module/class
|
|
const string m_basename;
|
|
const VCtorType m_type; // What kind of constructor are we creating
|
|
std::list<AstCFunc*> m_newFunctions; // Created functions, latest is at back
|
|
int m_numStmts = 0; // Number of statements output
|
|
|
|
AstCFunc* makeNewFunc() {
|
|
const int funcNum = m_newFunctions.size();
|
|
const string funcName = m_basename + "_" + cvtToStr(funcNum);
|
|
AstCFunc* const funcp = new AstCFunc{m_modp->fileline(), funcName, nullptr, "void"};
|
|
funcp->isStatic(false);
|
|
funcp->isLoose(!m_type.isClass());
|
|
funcp->keepIfEmpty(true); // TODO relax
|
|
funcp->declPrivate(true);
|
|
funcp->slow(!m_type.isClass()); // Only classes construct on fast path
|
|
if (!m_type.isCoverage()) m_modp->ctorVarReset(true);
|
|
string preventUnusedStmt;
|
|
if (m_type.isClass()) {
|
|
funcp->argTypes(EmitCUtil::symClassVar());
|
|
preventUnusedStmt = "(void)vlSymsp; // Prevent unused variable warning";
|
|
} else if (m_type.isCoverage()) {
|
|
funcp->argTypes("bool first");
|
|
preventUnusedStmt = "(void)first; // Prevent unused variable warning";
|
|
}
|
|
if (!preventUnusedStmt.empty()) {
|
|
funcp->addStmtsp(new AstCStmt{m_modp->fileline(), preventUnusedStmt});
|
|
}
|
|
m_modp->addStmtsp(funcp);
|
|
m_numStmts = 0;
|
|
return funcp;
|
|
}
|
|
|
|
public:
|
|
void add(AstNode* nodep) {
|
|
if (m_newFunctions.empty()) m_newFunctions.push_back(makeNewFunc());
|
|
if (v3Global.opt.outputSplitCFuncs() && m_numStmts > v3Global.opt.outputSplitCFuncs()) {
|
|
m_newFunctions.push_back(makeNewFunc());
|
|
}
|
|
m_newFunctions.back()->addStmtsp(nodep);
|
|
m_numStmts += 1;
|
|
}
|
|
|
|
V3CCtorsBuilder(AstNodeModule* nodep, const string& basename, VCtorType type)
|
|
: m_modp{nodep}
|
|
, m_basename{basename}
|
|
, m_type{type} {
|
|
// Expect coverage function to always exist, so must always create at least one.
|
|
if (m_type.isCoverage()) m_newFunctions.push_back(makeNewFunc());
|
|
}
|
|
|
|
~V3CCtorsBuilder() {
|
|
if (m_newFunctions.size() == 0) {
|
|
} else if (m_newFunctions.size() == 1) {
|
|
// No split was necessary, rename the one function to the basename
|
|
m_newFunctions.front()->name(m_basename);
|
|
} else {
|
|
// Split was necessary, create root function and call all others from that
|
|
AstCFunc* const rootFuncp = makeNewFunc();
|
|
rootFuncp->name(m_basename);
|
|
for (AstCFunc* const funcp : m_newFunctions) {
|
|
AstCCall* const callp = new AstCCall{m_modp->fileline(), funcp};
|
|
callp->dtypeSetVoid();
|
|
if (m_type.isClass()) {
|
|
callp->argTypes("vlSymsp");
|
|
} else {
|
|
if (m_type.isCoverage()) callp->argTypes("first");
|
|
callp->selfPointer(VSelfPointerText{VSelfPointerText::This{}});
|
|
}
|
|
rootFuncp->addStmtsp(callp->makeStmt());
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
VL_UNCOPYABLE(V3CCtorsBuilder);
|
|
};
|
|
|
|
//######################################################################
|
|
|
|
// Link state, as a visitor of each AstNode
|
|
|
|
class CCtorsVisitor final : public VNVisitor {
|
|
// NODE STATE
|
|
|
|
// STATE - for current visit position (use VL_RESTORER)
|
|
AstNodeModule* m_modp = nullptr; // Current module
|
|
AstCFunc* m_cfuncp = nullptr; // Current function
|
|
V3CCtorsBuilder* m_varResetp = nullptr; // Builder of _ctor_var_reset
|
|
std::map<AstCStmt*, const AstNodeModule*> m_ctorCalls; // Calls to _ctor_var_reset
|
|
|
|
// METHODS
|
|
static void insertSc(AstCFunc* cfuncp, const AstNodeModule* modp, VSystemCSectionType type) {
|
|
const auto txtAndFlp = EmitCBaseVisitorConst::scSection(modp, type);
|
|
if (txtAndFlp.first.empty()) return;
|
|
// Use an AstCStmtUser as this is from user input
|
|
AstCStmtUser* const cstmtp = new AstCStmtUser{txtAndFlp.second};
|
|
cstmtp->add(txtAndFlp.first);
|
|
cfuncp->addStmtsp(cstmtp);
|
|
}
|
|
|
|
// VISITORS
|
|
void visit(AstNodeModule* nodep) override {
|
|
if (nodep->isConstPool()) return; // Special emit rules
|
|
if (const AstClass* const classp = VN_CAST(nodep, Class)) {
|
|
// Interface class may only have pure virtuals and params which do not need cctor reset
|
|
if (classp->isInterfaceClass()) return;
|
|
}
|
|
VL_RESTORER(m_modp);
|
|
VL_RESTORER(m_varResetp);
|
|
m_modp = nodep;
|
|
V3CCtorsBuilder var_reset{nodep, "_ctor_var_reset",
|
|
VN_IS(nodep, Class) ? VCtorType::CLASS : VCtorType::MODULE};
|
|
// cppcheck-suppress danglingLifetime
|
|
m_varResetp = &var_reset;
|
|
iterateChildren(nodep);
|
|
|
|
if (v3Global.opt.coverage()) {
|
|
V3CCtorsBuilder configure_coverage{nodep, "_configure_coverage", VCtorType::COVERAGE};
|
|
for (AstNode *np = nodep->stmtsp(), *nextp; np; np = nextp) {
|
|
nextp = np->nextp();
|
|
if (AstNodeCoverDecl* const coverp = VN_CAST(np, NodeCoverDecl)) {
|
|
// ... else we don't have a static VlSym to be able to coverage insert
|
|
UASSERT_OBJ(!VN_IS(nodep, Class), coverp,
|
|
"NodeCoverDecl should be in class's package, not class itself");
|
|
configure_coverage.add(coverp->unlinkFrBack());
|
|
}
|
|
}
|
|
}
|
|
if (AstClass* const classp = VN_CAST(nodep, Class)) {
|
|
AstCFunc* const funcp = new AstCFunc{classp->fileline(), "~", nullptr, ""};
|
|
funcp->isDestructor(true);
|
|
funcp->isStatic(false);
|
|
// If can be referred to by base pointer, need virtual delete
|
|
funcp->isVirtual(classp->isExtended());
|
|
funcp->slow(false);
|
|
insertSc(funcp, classp, VSystemCSectionType::DTOR);
|
|
classp->addStmtsp(funcp);
|
|
}
|
|
}
|
|
|
|
void visit(AstCFunc* nodep) override {
|
|
VL_RESTORER(m_varResetp);
|
|
VL_RESTORER(m_cfuncp);
|
|
m_varResetp = nullptr;
|
|
m_cfuncp = nodep;
|
|
iterateChildren(nodep);
|
|
if (nodep->name() == "new") insertSc(nodep, m_modp, VSystemCSectionType::CTOR);
|
|
}
|
|
void visit(AstCStmt* nodep) override {
|
|
if (nodep->stmtType() == VCStmtType::CTOR_VAR_RESET_CALL) {
|
|
UASSERT_OBJ(m_modp, nodep, "ctor_var_reset call not under module");
|
|
m_ctorCalls.emplace(nodep, m_modp);
|
|
}
|
|
iterateChildren(nodep);
|
|
}
|
|
void visit(AstVar* nodep) override {
|
|
if (nodep->needsCReset()) {
|
|
AstNode* const crstp = new AstAssign{
|
|
nodep->fileline(), new AstVarRef{nodep->fileline(), nodep, VAccess::WRITE},
|
|
new AstCReset{nodep->fileline(), nodep, true}};
|
|
if (m_varResetp) {
|
|
m_varResetp->add(crstp);
|
|
} else if (m_cfuncp) {
|
|
nodep->addNextHere(crstp);
|
|
} else {
|
|
nodep->v3fatalSrc("Var needs CReset but nowhere to place it");
|
|
}
|
|
}
|
|
}
|
|
|
|
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
explicit CCtorsVisitor(AstNode* nodep) { iterate(nodep); }
|
|
~CCtorsVisitor() override {
|
|
// Remove CStmts to ctor_var_resets that are no longer needed
|
|
for (auto& itr : m_ctorCalls) {
|
|
AstCStmt* const nodep = itr.first;
|
|
const AstNodeModule* const modp = itr.second;
|
|
if (!modp->ctorVarReset()) VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
|
|
}
|
|
}
|
|
};
|
|
|
|
//######################################################################
|
|
|
|
void V3CCtors::evalAsserts() {
|
|
AstNodeModule* const modp = v3Global.rootp()->modulesp(); // Top module wrapper
|
|
AstCFunc* const funcp
|
|
= new AstCFunc{modp->fileline(), "_eval_debug_assertions", nullptr, "void"};
|
|
funcp->declPrivate(true);
|
|
funcp->isStatic(false);
|
|
funcp->isLoose(true);
|
|
funcp->keepIfEmpty(true);
|
|
funcp->slow(false);
|
|
funcp->ifdef("VL_DEBUG");
|
|
modp->addStmtsp(funcp);
|
|
for (AstNode* np = modp->stmtsp(); np; np = np->nextp()) {
|
|
if (AstVar* const varp = VN_CAST(np, Var)) {
|
|
if (varp->isPrimaryInish() && !varp->isSc()) {
|
|
if (const AstBasicDType* const basicp
|
|
= VN_CAST(varp->dtypeSkipRefp(), BasicDType)) {
|
|
const int storedWidth = basicp->widthAlignBytes() * 8;
|
|
const int lastWordWidth = varp->width() % storedWidth;
|
|
if (lastWordWidth != 0) {
|
|
// if (signal & CONST(upper_non_clean_mask)) { fail; }
|
|
AstVarRef* const vrefp
|
|
= new AstVarRef{varp->fileline(), varp, VAccess::READ};
|
|
vrefp->selfPointer(VSelfPointerText{VSelfPointerText::This{}});
|
|
AstNodeExpr* newp = vrefp;
|
|
if (varp->isWide()) {
|
|
newp = new AstWordSel{
|
|
varp->fileline(), newp,
|
|
new AstConst(varp->fileline(), varp->widthWords() - 1)};
|
|
}
|
|
const uint64_t value = VL_MASK_Q(storedWidth) & ~VL_MASK_Q(lastWordWidth);
|
|
newp = new AstAnd{varp->fileline(), newp,
|
|
new AstConst(varp->fileline(), AstConst::WidthedValue{},
|
|
storedWidth, value)};
|
|
AstNodeIf* const ifp = new AstIf{
|
|
varp->fileline(), newp,
|
|
new AstCStmt{varp->fileline(), "Verilated::overWidthError(\""
|
|
+ varp->prettyName() + "\");"}};
|
|
ifp->branchPred(VBranchPred::BP_UNLIKELY);
|
|
funcp->addStmtsp(ifp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void V3CCtors::cctorsAll() {
|
|
UINFO(2, __FUNCTION__ << ":");
|
|
evalAsserts();
|
|
{ CCtorsVisitor{v3Global.rootp()}; }
|
|
V3Global::dumpCheckGlobalTree("cctors", 0, dumpTreeEitherLevel() >= 3);
|
|
}
|