forked from github/verilator
970 lines
37 KiB
C++
970 lines
37 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Emit C++ for tree
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Copyright 2003-2021 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
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "config_build.h"
|
|
#include "verilatedos.h"
|
|
|
|
#include "V3Global.h"
|
|
#include "V3EmitC.h"
|
|
#include "V3EmitCBase.h"
|
|
#include "V3LanguageWords.h"
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
//######################################################################
|
|
// Symbol table emitting
|
|
|
|
class EmitCSyms final : EmitCBaseVisitor {
|
|
// NODE STATE
|
|
// Cleared on Netlist
|
|
// AstNodeModule::user1() -> bool. Set true __Vconfigure called
|
|
AstUser1InUse m_inuser1;
|
|
|
|
// TYPES
|
|
struct ScopeData {
|
|
string m_symName;
|
|
string m_prettyName;
|
|
int m_timeunit;
|
|
string m_type;
|
|
ScopeData(const string& symName, const string& prettyName, int timeunit,
|
|
const string& type)
|
|
: m_symName{symName}
|
|
, m_prettyName{prettyName}
|
|
, m_timeunit{timeunit}
|
|
, m_type{type} {}
|
|
};
|
|
struct ScopeFuncData {
|
|
AstScopeName* m_scopep;
|
|
AstCFunc* m_cfuncp;
|
|
AstNodeModule* m_modp;
|
|
ScopeFuncData(AstScopeName* scopep, AstCFunc* funcp, AstNodeModule* modp)
|
|
: m_scopep{scopep}
|
|
, m_cfuncp{funcp}
|
|
, m_modp{modp} {}
|
|
};
|
|
struct ScopeVarData {
|
|
string m_scopeName;
|
|
string m_varBasePretty;
|
|
AstVar* m_varp;
|
|
AstNodeModule* m_modp;
|
|
AstScope* m_scopep;
|
|
ScopeVarData(const string& scopeName, const string& varBasePretty, AstVar* varp,
|
|
AstNodeModule* modp, AstScope* scopep)
|
|
: m_scopeName{scopeName}
|
|
, m_varBasePretty{varBasePretty}
|
|
, m_varp{varp}
|
|
, m_modp{modp}
|
|
, m_scopep{scopep} {}
|
|
};
|
|
using ScopeNames = std::map<const std::string, ScopeData>;
|
|
using ScopeModPair = std::pair<AstScope*, AstNodeModule*>;
|
|
using ModVarPair = std::pair<AstNodeModule*, AstVar*>;
|
|
using ScopeNameList = std::vector<std::string>;
|
|
using ScopeNameHierarchy = std::map<const std::string, ScopeNameList>;
|
|
struct CmpName {
|
|
bool operator()(const ScopeModPair& lhsp, const ScopeModPair& rhsp) const {
|
|
return lhsp.first->name() < rhsp.first->name();
|
|
}
|
|
};
|
|
struct CmpDpi {
|
|
bool operator()(const AstCFunc* lhsp, const AstCFunc* rhsp) const {
|
|
if (lhsp->dpiImport() != rhsp->dpiImport()) {
|
|
// cppcheck-suppress comparisonOfFuncReturningBoolError
|
|
return lhsp->dpiImport() < rhsp->dpiImport();
|
|
}
|
|
return lhsp->name() < rhsp->name();
|
|
}
|
|
};
|
|
|
|
// STATE
|
|
AstCFunc* m_cfuncp = nullptr; // Current function
|
|
AstNodeModule* m_modp = nullptr; // Current module
|
|
std::vector<ScopeModPair> m_scopes; // Every scope by module
|
|
std::vector<AstCFunc*> m_dpis; // DPI functions
|
|
std::vector<ModVarPair> m_modVars; // Each public {mod,var}
|
|
ScopeNames m_scopeNames; // Each unique AstScopeName
|
|
std::map<const std::string, ScopeFuncData> m_scopeFuncs; // Each {scope,dpi-export-func}
|
|
std::map<const std::string, ScopeVarData> m_scopeVars; // Each {scope,public-var}
|
|
ScopeNames m_vpiScopeCandidates; // All scopes for VPI
|
|
ScopeNameHierarchy m_vpiScopeHierarchy; // The actual hierarchy of scopes
|
|
int m_coverBins = 0; // Coverage bin number
|
|
bool m_dpiHdrOnly; // Only emit the DPI header
|
|
int m_numStmts = 0; // Number of statements output
|
|
int m_funcNum = 0; // CFunc split function number
|
|
V3OutCFile* m_ofpBase = nullptr; // Base (not split) C file
|
|
std::unordered_map<int, bool> m_usesVfinal; // Split method uses __Vfinal
|
|
|
|
// METHODS
|
|
void emitSymHdr();
|
|
void checkSplit(bool usesVfinal);
|
|
void closeSplit();
|
|
void emitSymImpPreamble();
|
|
void emitScopeHier(bool destroy);
|
|
void emitSymImp();
|
|
void emitDpiHdr();
|
|
void emitDpiImp();
|
|
|
|
static void nameCheck(AstNode* nodep) {
|
|
// Prevent GCC compile time error; name check all things that reach C++ code
|
|
if (nodep->name() != ""
|
|
&& !(VN_IS(nodep, CFunc)
|
|
&& (VN_CAST(nodep, CFunc)->isConstructor()
|
|
|| VN_CAST(nodep, CFunc)->isDestructor()))) {
|
|
string rsvd = V3LanguageWords::isKeyword(nodep->name());
|
|
if (rsvd != "") {
|
|
// Generally V3Name should find all of these and throw SYMRSVDWORD.
|
|
// We'll still check here because the compiler errors
|
|
// resulting if we miss this warning are SO nasty
|
|
nodep->v3error("Symbol matching " + rsvd
|
|
+ " reserved word reached emitter,"
|
|
" should have hit SYMRSVDWORD: "
|
|
<< nodep->prettyNameQ());
|
|
}
|
|
}
|
|
}
|
|
|
|
static string scopeSymString(const string& scpname) {
|
|
string out = scpname;
|
|
string::size_type pos;
|
|
while ((pos = out.find("__PVT__")) != string::npos) out.replace(pos, 7, "");
|
|
if (out.substr(0, 10) == "TOP__DOT__") out.replace(0, 10, "");
|
|
if (out.substr(0, 4) == "TOP.") out.replace(0, 4, "");
|
|
while ((pos = out.find('.')) != string::npos) out.replace(pos, 1, "__");
|
|
while ((pos = out.find("__DOT__")) != string::npos) out.replace(pos, 7, "__");
|
|
return out;
|
|
}
|
|
|
|
static string scopeDecodeIdentifier(const string& scpname) {
|
|
string out = scpname;
|
|
// Remove hierarchy
|
|
string::size_type pos = out.rfind('.');
|
|
if (pos != std::string::npos) out.erase(0, pos + 1);
|
|
// Decode all escaped characters
|
|
while ((pos = out.find("__0")) != string::npos) {
|
|
unsigned int x;
|
|
std::stringstream ss;
|
|
ss << std::hex << out.substr(pos + 3, 2);
|
|
ss >> x;
|
|
out.replace(pos, 5, 1, (char)x);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
void varHierarchyScopes(string scp) {
|
|
while (!scp.empty()) {
|
|
const auto scpit = m_vpiScopeCandidates.find(scp);
|
|
if ((scpit != m_vpiScopeCandidates.end())
|
|
&& (m_scopeNames.find(scp) == m_scopeNames.end())) {
|
|
auto scopeNameit = m_scopeNames.find(scpit->second.m_symName);
|
|
if (scopeNameit == m_scopeNames.end()) {
|
|
m_scopeNames.emplace(scpit->second.m_symName, scpit->second);
|
|
} else {
|
|
scopeNameit->second.m_type = scpit->second.m_type;
|
|
}
|
|
}
|
|
string::size_type pos = scp.rfind("__DOT__");
|
|
if (pos == string::npos) {
|
|
pos = scp.rfind('.');
|
|
if (pos == string::npos) break;
|
|
}
|
|
scp.resize(pos);
|
|
}
|
|
}
|
|
|
|
void varsExpand() {
|
|
// We didn't have all m_scopes loaded when we encountered variables, so expand them now
|
|
// It would be less code if each module inserted its own variables.
|
|
// Someday. For now public isn't common.
|
|
for (std::vector<ScopeModPair>::iterator itsc = m_scopes.begin(); itsc != m_scopes.end();
|
|
++itsc) {
|
|
AstScope* scopep = itsc->first;
|
|
AstNodeModule* smodp = itsc->second;
|
|
for (std::vector<ModVarPair>::iterator it = m_modVars.begin(); it != m_modVars.end();
|
|
++it) {
|
|
AstNodeModule* modp = it->first;
|
|
AstVar* varp = it->second;
|
|
if (modp == smodp) {
|
|
// Need to split the module + var name into the
|
|
// original-ish full scope and variable name under that scope.
|
|
// The module instance name is included later, when we
|
|
// know the scopes this module is under
|
|
string whole = scopep->name() + "__DOT__" + varp->name();
|
|
string scpName;
|
|
string varBase;
|
|
if (whole.substr(0, 10) == "__DOT__TOP") whole.replace(0, 10, "");
|
|
string::size_type dpos = whole.rfind("__DOT__");
|
|
if (dpos != string::npos) {
|
|
scpName = whole.substr(0, dpos);
|
|
varBase = whole.substr(dpos + strlen("__DOT__"));
|
|
} else {
|
|
varBase = whole;
|
|
}
|
|
// UINFO(9,"For "<<scopep->name()<<" - "<<varp->name()<<" Scp "<<scpName<<"
|
|
// Var "<<varBase<<endl);
|
|
string varBasePretty = AstNode::prettyName(varBase);
|
|
string scpPretty = AstNode::prettyName(scpName);
|
|
string scpSym = scopeSymString(scpName);
|
|
// UINFO(9," scnameins sp "<<scpName<<" sp "<<scpPretty<<" ss "<<scpSym<<endl);
|
|
if (v3Global.opt.vpi()) varHierarchyScopes(scpName);
|
|
if (m_scopeNames.find(scpSym) == m_scopeNames.end()) {
|
|
m_scopeNames.insert(std::make_pair(
|
|
scpSym, ScopeData(scpSym, scpPretty, 0, "SCOPE_OTHER")));
|
|
}
|
|
m_scopeVars.insert(
|
|
std::make_pair(scpSym + " " + varp->name(),
|
|
ScopeVarData(scpSym, varBasePretty, varp, modp, scopep)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void buildVpiHierarchy() {
|
|
for (ScopeNames::const_iterator it = m_scopeNames.begin(); it != m_scopeNames.end();
|
|
++it) {
|
|
if (it->second.m_type != "SCOPE_MODULE") continue;
|
|
|
|
string symName = it->second.m_symName;
|
|
string above = symName;
|
|
if (above.substr(0, 4) == "TOP.") above.replace(0, 4, "");
|
|
|
|
while (!above.empty()) {
|
|
string::size_type pos = above.rfind("__");
|
|
if (pos == string::npos) break;
|
|
above.resize(pos);
|
|
if (m_vpiScopeHierarchy.find(above) != m_vpiScopeHierarchy.end()) {
|
|
m_vpiScopeHierarchy[above].push_back(symName);
|
|
break;
|
|
}
|
|
}
|
|
m_vpiScopeHierarchy[symName] = std::vector<string>();
|
|
}
|
|
}
|
|
|
|
// VISITORS
|
|
virtual void visit(AstNetlist* nodep) override {
|
|
// Collect list of scopes
|
|
iterateChildren(nodep);
|
|
varsExpand();
|
|
|
|
if (v3Global.opt.vpi()) buildVpiHierarchy();
|
|
|
|
// Sort by names, so line/process order matters less
|
|
stable_sort(m_scopes.begin(), m_scopes.end(), CmpName());
|
|
stable_sort(m_dpis.begin(), m_dpis.end(), CmpDpi());
|
|
|
|
// Output
|
|
if (!m_dpiHdrOnly) {
|
|
// Must emit implementation first to determine number of splits
|
|
emitSymImp();
|
|
emitSymHdr();
|
|
}
|
|
if (v3Global.dpi()) {
|
|
emitDpiHdr();
|
|
if (!m_dpiHdrOnly) emitDpiImp();
|
|
}
|
|
}
|
|
virtual void visit(AstNodeModule* nodep) override {
|
|
nameCheck(nodep);
|
|
VL_RESTORER(m_modp);
|
|
{
|
|
m_modp = nodep;
|
|
iterateChildren(nodep);
|
|
}
|
|
}
|
|
virtual void visit(AstCellInline* nodep) override {
|
|
if (v3Global.opt.vpi()) {
|
|
string type = (nodep->origModName() == "__BEGIN__") ? "SCOPE_OTHER" : "SCOPE_MODULE";
|
|
string name = nodep->scopep()->name() + "__DOT__" + nodep->name();
|
|
string name_dedot = AstNode::dedotName(name);
|
|
int timeunit = m_modp->timeunit().powerOfTen();
|
|
m_vpiScopeCandidates.insert(
|
|
std::make_pair(name, ScopeData(scopeSymString(name), name_dedot, timeunit, type)));
|
|
}
|
|
}
|
|
virtual void visit(AstScope* nodep) override {
|
|
if (VN_IS(m_modp, Class)) return; // The ClassPackage is what is visible
|
|
nameCheck(nodep);
|
|
|
|
m_scopes.emplace_back(std::make_pair(nodep, m_modp));
|
|
|
|
if (v3Global.opt.vpi() && !nodep->isTop()) {
|
|
string type = VN_IS(nodep->modp(), Package) ? "SCOPE_OTHER" : "SCOPE_MODULE";
|
|
string name_dedot = AstNode::dedotName(nodep->shortName());
|
|
int timeunit = m_modp->timeunit().powerOfTen();
|
|
m_vpiScopeCandidates.insert(
|
|
std::make_pair(nodep->name(), ScopeData(scopeSymString(nodep->name()), name_dedot,
|
|
timeunit, type)));
|
|
}
|
|
}
|
|
virtual void visit(AstScopeName* nodep) override {
|
|
string name = nodep->scopeSymName();
|
|
// UINFO(9,"scnameins sp "<<nodep->name()<<" sp "<<nodep->scopePrettySymName()
|
|
// <<" ss"<<name<<endl);
|
|
int timeunit = m_modp ? m_modp->timeunit().powerOfTen() : 0;
|
|
if (m_scopeNames.find(name) == m_scopeNames.end()) {
|
|
m_scopeNames.emplace(
|
|
name, ScopeData(name, nodep->scopePrettySymName(), timeunit, "SCOPE_OTHER"));
|
|
}
|
|
if (nodep->dpiExport()) {
|
|
UASSERT_OBJ(m_cfuncp, nodep, "ScopeName not under DPI function");
|
|
m_scopeFuncs.insert(std::make_pair(name + " " + m_cfuncp->name(),
|
|
ScopeFuncData(nodep, m_cfuncp, m_modp)));
|
|
} else {
|
|
if (m_scopeNames.find(nodep->scopeDpiName()) == m_scopeNames.end()) {
|
|
m_scopeNames.insert(
|
|
std::make_pair(nodep->scopeDpiName(),
|
|
ScopeData(nodep->scopeDpiName(), nodep->scopePrettyDpiName(),
|
|
timeunit, "SCOPE_OTHER")));
|
|
}
|
|
}
|
|
}
|
|
virtual void visit(AstVar* nodep) override {
|
|
nameCheck(nodep);
|
|
iterateChildren(nodep);
|
|
if (nodep->isSigUserRdPublic() && !m_cfuncp)
|
|
m_modVars.emplace_back(std::make_pair(m_modp, nodep));
|
|
}
|
|
virtual void visit(AstCoverDecl* nodep) override {
|
|
// Assign numbers to all bins, so we know how big of an array to use
|
|
if (!nodep->dataDeclNullp()) { // else duplicate we don't need code for
|
|
nodep->binNum(m_coverBins++);
|
|
}
|
|
}
|
|
virtual void visit(AstCFunc* nodep) override {
|
|
nameCheck(nodep);
|
|
if (nodep->dpiImport() || nodep->dpiExportWrapper()) m_dpis.push_back(nodep);
|
|
VL_RESTORER(m_cfuncp);
|
|
{
|
|
m_cfuncp = nodep;
|
|
iterateChildren(nodep);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------
|
|
virtual void visit(AstConst*) override {}
|
|
virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
|
|
|
public:
|
|
explicit EmitCSyms(AstNetlist* nodep, bool dpiHdrOnly)
|
|
: m_dpiHdrOnly{dpiHdrOnly} {
|
|
iterate(nodep);
|
|
}
|
|
};
|
|
|
|
void EmitCSyms::emitSymHdr() {
|
|
UINFO(6, __FUNCTION__ << ": " << endl);
|
|
string filename = v3Global.opt.makeDir() + "/" + symClassName() + ".h";
|
|
newCFile(filename, true /*slow*/, false /*source*/);
|
|
|
|
if (v3Global.opt.systemC()) {
|
|
m_ofp = new V3OutScFile(filename);
|
|
} else {
|
|
m_ofp = new V3OutCFile(filename);
|
|
}
|
|
|
|
ofp()->putsHeader();
|
|
puts("// DESCR"
|
|
"IPTION: Verilator output: Symbol table internal header\n");
|
|
puts("//\n");
|
|
puts("// Internal details; most calling programs do not need this header,\n");
|
|
puts("// unless using verilator public meta comments.\n");
|
|
|
|
ofp()->putsGuard();
|
|
|
|
puts("\n");
|
|
ofp()->putsIntTopInclude();
|
|
if (v3Global.needHeavy()) {
|
|
puts("#include \"verilated_heavy.h\"\n");
|
|
} else {
|
|
puts("#include \"verilated.h\"\n");
|
|
}
|
|
|
|
puts("\n// INCLUDE MODULE CLASSES\n");
|
|
for (AstNodeModule* nodep = v3Global.rootp()->modulesp(); nodep;
|
|
nodep = VN_CAST(nodep->nextp(), NodeModule)) {
|
|
if (VN_IS(nodep, Class)) continue; // Class included earlier
|
|
puts("#include \"" + prefixNameProtect(nodep) + ".h\"\n");
|
|
}
|
|
|
|
if (v3Global.dpi()) {
|
|
puts("\n// DPI TYPES for DPI Export callbacks (Internal use)\n");
|
|
std::map<const string, int> types; // Remove duplicates and sort
|
|
for (const auto& itr : m_scopeFuncs) {
|
|
AstCFunc* funcp = itr.second.m_cfuncp;
|
|
if (funcp->dpiExport()) {
|
|
string cbtype = protect(v3Global.opt.prefix() + "__Vcb_" + funcp->cname() + "_t");
|
|
types["using " + cbtype + " = void (*) (" + cFuncArgs(funcp) + ");\n"] = 1;
|
|
}
|
|
}
|
|
for (const auto& i : types) puts(i.first);
|
|
}
|
|
|
|
puts("\n// SYMS CLASS\n");
|
|
puts(string("class ") + symClassName() + " : public VerilatedSyms {\n");
|
|
ofp()->putsPrivate(false); // public:
|
|
|
|
puts("\n// LOCAL STATE\n");
|
|
// Must be before subcells, as constructor order needed before _vlCoverInsert.
|
|
puts("const char* __Vm_namep;\n");
|
|
|
|
if (v3Global.needTraceDumper()) {
|
|
// __Vm_dumperp is local, otherwise we wouldn't know what design's eval()
|
|
// should call a global dumpperp
|
|
puts("bool __Vm_dumping; // Dumping is active\n");
|
|
puts("VerilatedMutex __Vm_dumperMutex; // Protect __Vm_dumperp\n");
|
|
puts(v3Global.opt.traceClassLang()
|
|
+ "* __Vm_dumperp VL_GUARDED_BY(__Vm_dumperMutex); /// Trace class for $dump*\n");
|
|
}
|
|
if (v3Global.opt.trace()) {
|
|
puts("bool __Vm_activity; ///< Used by trace routines to determine change occurred\n");
|
|
puts("uint32_t __Vm_baseCode; "
|
|
"///< Used by trace routines when tracing multiple models\n");
|
|
}
|
|
puts("bool __Vm_didInit;\n");
|
|
|
|
puts("\n// SUBCELL STATE\n");
|
|
for (const auto& i : m_scopes) {
|
|
AstScope* scopep = i.first;
|
|
AstNodeModule* modp = i.second;
|
|
if (VN_IS(modp, Class)) continue;
|
|
if (modp->isTop()) {
|
|
ofp()->printf("%-30s ", (prefixNameProtect(modp) + "*").c_str());
|
|
puts(protectIf(scopep->nameDotless() + "p", scopep->protect()) + ";\n");
|
|
} else {
|
|
ofp()->printf("%-30s ", (prefixNameProtect(modp) + "").c_str());
|
|
puts(protectIf(scopep->nameDotless(), scopep->protect()) + ";\n");
|
|
}
|
|
}
|
|
|
|
if (m_coverBins) {
|
|
puts("\n// COVERAGE\n");
|
|
puts(v3Global.opt.threads() ? "std::atomic<uint32_t>" : "uint32_t");
|
|
puts(" __Vcoverage[");
|
|
puts(cvtToStr(m_coverBins));
|
|
puts("];\n");
|
|
}
|
|
|
|
if (!m_scopeNames.empty()) { // Scope names
|
|
puts("\n// SCOPE NAMES\n");
|
|
for (const auto& itr : m_scopeNames) {
|
|
puts("VerilatedScope " + protect("__Vscope_" + itr.second.m_symName) + ";\n");
|
|
}
|
|
}
|
|
|
|
if (v3Global.opt.vpi()) {
|
|
puts("\n// SCOPE HIERARCHY\n");
|
|
puts("VerilatedHierarchy __Vhier;\n");
|
|
}
|
|
|
|
puts("\n// CREATORS\n");
|
|
puts(symClassName() + "(VerilatedContext* contextp, " + topClassName()
|
|
+ "* topp, const char* namep);\n");
|
|
puts(string("~") + symClassName() + "();\n");
|
|
|
|
for (const auto& i : m_usesVfinal) {
|
|
puts("void " + symClassName() + "_" + cvtToStr(i.first) + "(");
|
|
if (i.second) {
|
|
puts("int __Vfinal");
|
|
} else {
|
|
puts(topClassName() + "* topp");
|
|
}
|
|
puts(");\n");
|
|
}
|
|
|
|
puts("\n// METHODS\n");
|
|
puts("inline const char* name() { return __Vm_namep; }\n");
|
|
if (v3Global.opt.savable()) {
|
|
puts("void " + protect("__Vserialize") + "(VerilatedSerialize& os);\n");
|
|
puts("void " + protect("__Vdeserialize") + "(VerilatedDeserialize& os);\n");
|
|
}
|
|
puts("\n");
|
|
puts("} VL_ATTR_ALIGNED(VL_CACHE_LINE_BYTES);\n");
|
|
|
|
ofp()->putsEndGuard();
|
|
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
|
}
|
|
|
|
void EmitCSyms::closeSplit() {
|
|
if (!m_ofp || m_ofp == m_ofpBase) return;
|
|
|
|
puts("}\n");
|
|
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
|
}
|
|
|
|
void EmitCSyms::checkSplit(bool usesVfinal) {
|
|
if (m_ofp
|
|
&& (!v3Global.opt.outputSplitCFuncs() || m_numStmts < v3Global.opt.outputSplitCFuncs())) {
|
|
return;
|
|
}
|
|
|
|
// Splitting file, so using parallel build.
|
|
v3Global.useParallelBuild(true);
|
|
|
|
m_numStmts = 0;
|
|
string filename
|
|
= v3Global.opt.makeDir() + "/" + symClassName() + "__" + cvtToStr(++m_funcNum) + ".cpp";
|
|
AstCFile* cfilep = newCFile(filename, true /*slow*/, true /*source*/);
|
|
cfilep->support(true);
|
|
m_usesVfinal[m_funcNum] = usesVfinal;
|
|
closeSplit();
|
|
|
|
if (v3Global.opt.systemC()) {
|
|
m_ofp = new V3OutScFile(filename);
|
|
} else {
|
|
m_ofp = new V3OutCFile(filename);
|
|
}
|
|
|
|
m_ofpBase->puts(symClassName() + "_" + cvtToStr(m_funcNum) + "(");
|
|
if (usesVfinal) {
|
|
m_ofpBase->puts("__Vfinal");
|
|
} else {
|
|
m_ofpBase->puts("topp");
|
|
}
|
|
m_ofpBase->puts(");\n");
|
|
|
|
emitSymImpPreamble();
|
|
puts("void " + symClassName() + "::" + symClassName() + "_" + cvtToStr(m_funcNum) + "(");
|
|
if (usesVfinal) {
|
|
puts("int __Vfinal");
|
|
} else {
|
|
puts(topClassName() + "* topp");
|
|
}
|
|
puts(") {\n");
|
|
}
|
|
|
|
void EmitCSyms::emitSymImpPreamble() {
|
|
ofp()->putsHeader();
|
|
puts("// DESCR"
|
|
"IPTION: Verilator output: Symbol table implementation internals\n");
|
|
puts("\n");
|
|
|
|
// Includes
|
|
puts("#include \"" + symClassName() + ".h\"\n");
|
|
for (AstNodeModule* nodep = v3Global.rootp()->modulesp(); nodep;
|
|
nodep = VN_CAST(nodep->nextp(), NodeModule)) {
|
|
if (VN_IS(nodep, Class)) continue; // Class included earlier
|
|
puts("#include \"" + prefixNameProtect(nodep) + ".h\"\n");
|
|
}
|
|
}
|
|
|
|
void EmitCSyms::emitScopeHier(bool destroy) {
|
|
if (v3Global.opt.vpi()) {
|
|
string verb = destroy ? "Tear down" : "Set up";
|
|
string method = destroy ? "remove" : "add";
|
|
puts("\n// " + verb + " scope hierarchy\n");
|
|
for (ScopeNames::const_iterator it = m_scopeNames.begin(); it != m_scopeNames.end();
|
|
++it) {
|
|
string name = it->second.m_prettyName;
|
|
if (it->first == "TOP") continue;
|
|
if ((name.find('.') == string::npos) && (it->second.m_type == "SCOPE_MODULE")) {
|
|
puts("__Vhier." + method + "(0, &" + protect("__Vscope_" + it->second.m_symName)
|
|
+ ");\n");
|
|
}
|
|
}
|
|
|
|
for (auto it = m_vpiScopeHierarchy.cbegin(); it != m_vpiScopeHierarchy.cend(); ++it) {
|
|
for (ScopeNameList::const_iterator lit = it->second.begin(); lit != it->second.end();
|
|
++lit) {
|
|
string fromname = scopeSymString(it->first);
|
|
string toname = scopeSymString(*lit);
|
|
const auto from = vlstd::as_const(m_scopeNames).find(fromname);
|
|
const auto to = vlstd::as_const(m_scopeNames).find(toname);
|
|
UASSERT(from != m_scopeNames.end(), fromname + " not in m_scopeNames");
|
|
UASSERT(to != m_scopeNames.end(), toname + " not in m_scopeNames");
|
|
puts("__Vhier." + method + "(");
|
|
puts("&" + protect("__Vscope_" + from->second.m_symName) + ", ");
|
|
puts("&" + protect("__Vscope_" + to->second.m_symName) + ");\n");
|
|
}
|
|
}
|
|
puts("\n");
|
|
}
|
|
}
|
|
|
|
void EmitCSyms::emitSymImp() {
|
|
UINFO(6, __FUNCTION__ << ": " << endl);
|
|
string filename = v3Global.opt.makeDir() + "/" + symClassName() + ".cpp";
|
|
AstCFile* cfilep = newCFile(filename, true /*slow*/, true /*source*/);
|
|
cfilep->support(true);
|
|
|
|
if (v3Global.opt.systemC()) {
|
|
m_ofp = new V3OutScFile(filename);
|
|
} else {
|
|
m_ofp = new V3OutCFile(filename);
|
|
}
|
|
|
|
m_ofpBase = m_ofp;
|
|
emitSymImpPreamble();
|
|
|
|
// puts("\n// GLOBALS\n");
|
|
|
|
puts("\n");
|
|
|
|
if (v3Global.opt.savable()) {
|
|
puts("\n");
|
|
for (int de = 0; de < 2; ++de) {
|
|
string classname = de ? "VerilatedDeserialize" : "VerilatedSerialize";
|
|
string funcname = de ? "__Vdeserialize" : "__Vserialize";
|
|
string op = de ? ">>" : "<<";
|
|
// NOLINTNEXTLINE(performance-inefficient-string-concatenation)
|
|
puts("void " + symClassName() + "::" + protect(funcname) + "(" + classname
|
|
+ "& os) {\n");
|
|
puts("// LOCAL STATE\n");
|
|
// __Vm_namep presumably already correct
|
|
if (v3Global.opt.trace()) puts("os" + op + "__Vm_activity;\n");
|
|
puts("os" + op + "__Vm_didInit;\n");
|
|
puts("// SUBCELL STATE\n");
|
|
for (std::vector<ScopeModPair>::iterator it = m_scopes.begin(); it != m_scopes.end();
|
|
++it) {
|
|
AstScope* scopep = it->first;
|
|
AstNodeModule* modp = it->second;
|
|
if (!modp->isTop()) {
|
|
puts(protectIf(scopep->nameDotless(), scopep->protect()) + "."
|
|
+ protect(funcname) + "(os);\n");
|
|
}
|
|
}
|
|
puts("}\n");
|
|
}
|
|
}
|
|
|
|
puts("\n");
|
|
|
|
puts("\n// FUNCTIONS\n");
|
|
puts(symClassName() + "::~" + symClassName() + "()\n");
|
|
puts("{\n");
|
|
emitScopeHier(true);
|
|
puts("}\n\n");
|
|
puts(symClassName() + "::" + symClassName() + "(VerilatedContext* contextp, " + topClassName()
|
|
+ "* topp, const char* namep)\n");
|
|
puts(" // Setup locals\n");
|
|
puts(" : VerilatedSyms{contextp}\n");
|
|
puts(" , __Vm_namep(namep)\n"); // No leak, as gets destroyed when the top is destroyed
|
|
if (v3Global.needTraceDumper()) {
|
|
puts(" , __Vm_dumping(false)\n");
|
|
puts(" , __Vm_dumperp(nullptr)\n");
|
|
}
|
|
if (v3Global.opt.trace()) {
|
|
puts(" , __Vm_activity(false)\n");
|
|
puts(" , __Vm_baseCode(0)\n");
|
|
}
|
|
puts(" , __Vm_didInit(false)\n");
|
|
puts(" // Setup submodule names\n");
|
|
char comma = ',';
|
|
for (const auto& i : m_scopes) {
|
|
AstScope* scopep = i.first;
|
|
AstNodeModule* modp = i.second;
|
|
if (modp->isTop()) {
|
|
} else {
|
|
puts(string(" ") + comma + " " + protect(scopep->nameDotless()));
|
|
puts("(Verilated::catName(topp->name(), ");
|
|
// The "." is added by catName
|
|
putsQuoted(protectWordsIf(scopep->prettyName(), scopep->protect()));
|
|
puts("))\n");
|
|
comma = ',';
|
|
++m_numStmts;
|
|
}
|
|
}
|
|
puts("{\n");
|
|
|
|
puts("// Pointer to top level\n");
|
|
puts("TOPp = topp;\n");
|
|
puts("// Setup each module's pointers to their submodules\n");
|
|
for (const auto& i : m_scopes) {
|
|
AstScope* scopep = i.first;
|
|
AstNodeModule* modp = i.second;
|
|
if (!modp->isTop()) {
|
|
checkSplit(false);
|
|
string arrow = scopep->name();
|
|
string::size_type pos;
|
|
while ((pos = arrow.find('.')) != string::npos) arrow.replace(pos, 1, "->");
|
|
if (arrow.substr(0, 5) == "TOP->") arrow.replace(0, 5, "TOPp->");
|
|
ofp()->puts(protectWordsIf(arrow, scopep->protect()));
|
|
puts(" = &");
|
|
puts(protectIf(scopep->nameDotless(), scopep->protect()) + ";\n");
|
|
++m_numStmts;
|
|
}
|
|
}
|
|
|
|
puts("// Setup each module's pointer back to symbol table (for public functions)\n");
|
|
puts("TOPp->" + protect("__Vconfigure") + "(this, true);\n");
|
|
for (const auto& i : m_scopes) {
|
|
AstScope* scopep = i.first;
|
|
AstNodeModule* modp = i.second;
|
|
if (!modp->isTop()) {
|
|
checkSplit(false);
|
|
// first is used by AstCoverDecl's call to __vlCoverInsert
|
|
bool first = !modp->user1();
|
|
modp->user1(true);
|
|
puts(protectIf(scopep->nameDotless(), scopep->protect()) + "."
|
|
+ protect("__Vconfigure") + "(this, " + (first ? "true" : "false") + ");\n");
|
|
++m_numStmts;
|
|
}
|
|
}
|
|
|
|
if (!m_scopeNames.empty()) { // Setup scope names
|
|
puts("// Setup scopes\n");
|
|
for (ScopeNames::iterator it = m_scopeNames.begin(); it != m_scopeNames.end(); ++it) {
|
|
checkSplit(false);
|
|
puts(protect("__Vscope_" + it->second.m_symName) + ".configure(this, name(), ");
|
|
putsQuoted(protectWordsIf(it->second.m_prettyName, true));
|
|
puts(", ");
|
|
putsQuoted(protect(scopeDecodeIdentifier(it->second.m_prettyName)));
|
|
puts(", ");
|
|
puts(cvtToStr(it->second.m_timeunit));
|
|
puts(", VerilatedScope::" + it->second.m_type + ");\n");
|
|
++m_numStmts;
|
|
}
|
|
}
|
|
|
|
emitScopeHier(false);
|
|
|
|
// Everything past here is in the __Vfinal loop, so start a new split file if needed
|
|
closeSplit();
|
|
|
|
if (v3Global.dpi()) {
|
|
m_ofpBase->puts("// Setup export functions\n");
|
|
m_ofpBase->puts("for (int __Vfinal=0; __Vfinal<2; __Vfinal++) {\n");
|
|
for (auto it = m_scopeFuncs.begin(); it != m_scopeFuncs.end(); ++it) {
|
|
AstScopeName* scopep = it->second.m_scopep;
|
|
AstCFunc* funcp = it->second.m_cfuncp;
|
|
AstNodeModule* modp = it->second.m_modp;
|
|
if (funcp->dpiExport()) {
|
|
checkSplit(true);
|
|
puts(protect("__Vscope_" + scopep->scopeSymName()) + ".exportInsert(__Vfinal, ");
|
|
putsQuoted(funcp->cname()); // Not protected - user asked for import/export
|
|
puts(", (void*)(&");
|
|
puts(prefixNameProtect(modp));
|
|
puts("::");
|
|
puts(funcp->nameProtect());
|
|
puts("));\n");
|
|
++m_numStmts;
|
|
}
|
|
}
|
|
// It would be less code if each module inserted its own variables.
|
|
// Someday. For now public isn't common.
|
|
for (auto it = m_scopeVars.begin(); it != m_scopeVars.end(); ++it) {
|
|
checkSplit(true);
|
|
AstNodeModule* modp = it->second.m_modp;
|
|
AstScope* scopep = it->second.m_scopep;
|
|
AstVar* varp = it->second.m_varp;
|
|
//
|
|
int pwidth = 1;
|
|
int pdim = 0;
|
|
int udim = 0;
|
|
string bounds;
|
|
if (AstBasicDType* basicp = varp->basicp()) {
|
|
// Range is always first, it's not in "C" order
|
|
if (basicp->isRanged()) {
|
|
bounds += " ,";
|
|
bounds += cvtToStr(basicp->hi());
|
|
bounds += ",";
|
|
bounds += cvtToStr(basicp->lo());
|
|
pdim++;
|
|
pwidth *= basicp->elements();
|
|
}
|
|
for (AstNodeDType* dtypep = varp->dtypep(); dtypep;) {
|
|
dtypep
|
|
= dtypep->skipRefp(); // Skip AstRefDType/AstTypedef, or return same node
|
|
if (const AstNodeArrayDType* adtypep = VN_CAST(dtypep, NodeArrayDType)) {
|
|
bounds += " ,";
|
|
bounds += cvtToStr(adtypep->left());
|
|
bounds += ",";
|
|
bounds += cvtToStr(adtypep->right());
|
|
if (VN_IS(dtypep, PackArrayDType)) {
|
|
pdim++;
|
|
pwidth *= adtypep->elementsConst();
|
|
} else {
|
|
udim++;
|
|
}
|
|
dtypep = adtypep->subDTypep();
|
|
} else {
|
|
break; // AstBasicDType - nothing below, 1
|
|
}
|
|
}
|
|
}
|
|
// TODO: actually expose packed arrays as vpiRegArray
|
|
if (pdim > 1 && udim == 0) {
|
|
bounds = ", ";
|
|
bounds += cvtToStr(pwidth - 1);
|
|
bounds += ",0";
|
|
pdim = 1;
|
|
}
|
|
if (pdim > 1 || udim > 1) {
|
|
puts("//UNSUP "); // VerilatedImp can't deal with >2d or packed arrays
|
|
}
|
|
puts(protect("__Vscope_" + it->second.m_scopeName) + ".varInsert(__Vfinal,");
|
|
putsQuoted(protect(it->second.m_varBasePretty));
|
|
|
|
std::string varName;
|
|
if (modp->isTop()) {
|
|
varName += (protectIf(scopep->nameDotless() + "p", scopep->protect()) + "->");
|
|
} else {
|
|
varName += (protectIf(scopep->nameDotless(), scopep->protect()) + ".");
|
|
}
|
|
|
|
if (varp->isParam()) {
|
|
varName += protect("var_" + varp->name());
|
|
} else {
|
|
varName += protect(varp->name());
|
|
}
|
|
|
|
if (varp->isParam()) {
|
|
if (varp->vlEnumType() == "VLVT_STRING") {
|
|
puts(", const_cast<void*>(static_cast<const void*>(");
|
|
puts(varName);
|
|
puts(".c_str())), ");
|
|
} else {
|
|
puts(", const_cast<void*>(static_cast<const void*>(&(");
|
|
puts(varName);
|
|
puts("))), ");
|
|
}
|
|
} else {
|
|
puts(", &(");
|
|
puts(varName);
|
|
puts("), ");
|
|
}
|
|
|
|
puts(varp->isParam() ? "true" : "false");
|
|
puts(", ");
|
|
puts(varp->vlEnumType()); // VLVT_UINT32 etc
|
|
puts(",");
|
|
puts(varp->vlEnumDir()); // VLVD_IN etc
|
|
puts(",");
|
|
puts(cvtToStr(pdim + udim));
|
|
puts(bounds);
|
|
puts(");\n");
|
|
++m_numStmts;
|
|
}
|
|
m_ofpBase->puts("}\n");
|
|
}
|
|
|
|
m_ofpBase->puts("}\n");
|
|
closeSplit();
|
|
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
|
}
|
|
|
|
//######################################################################
|
|
|
|
void EmitCSyms::emitDpiHdr() {
|
|
UINFO(6, __FUNCTION__ << ": " << endl);
|
|
string filename = v3Global.opt.makeDir() + "/" + topClassName() + "__Dpi.h";
|
|
AstCFile* cfilep = newCFile(filename, false /*slow*/, false /*source*/);
|
|
cfilep->support(true);
|
|
V3OutCFile hf(filename);
|
|
m_ofp = &hf;
|
|
|
|
m_ofp->putsHeader();
|
|
puts("// DESCR"
|
|
"IPTION: Verilator output: Prototypes for DPI import and export functions.\n");
|
|
puts("//\n");
|
|
puts("// Verilator includes this file in all generated .cpp files that use DPI functions.\n");
|
|
puts("// Manually include this file where DPI .c import functions are declared to ensure\n");
|
|
puts("// the C functions match the expectations of the DPI imports.\n");
|
|
puts("\n");
|
|
puts("#include \"svdpi.h\"\n");
|
|
puts("\n");
|
|
puts("#ifdef __cplusplus\n");
|
|
puts("extern \"C\" {\n");
|
|
puts("#endif\n");
|
|
puts("\n");
|
|
|
|
int firstExp = 0;
|
|
int firstImp = 0;
|
|
for (AstCFunc* nodep : m_dpis) {
|
|
if (nodep->dpiExportWrapper()) {
|
|
if (!firstExp++) puts("\n// DPI EXPORTS\n");
|
|
putsDecoration("// DPI export" + ifNoProtect(" at " + nodep->fileline()->ascii())
|
|
+ "\n");
|
|
puts("extern " + nodep->rtnTypeVoid() + " " + nodep->nameProtect() + "("
|
|
+ cFuncArgs(nodep) + ");\n");
|
|
} else if (nodep->dpiImport()) {
|
|
if (!firstImp++) puts("\n// DPI IMPORTS\n");
|
|
putsDecoration("// DPI import" + ifNoProtect(" at " + nodep->fileline()->ascii())
|
|
+ "\n");
|
|
puts("extern " + nodep->rtnTypeVoid() + " " + nodep->nameProtect() + "("
|
|
+ cFuncArgs(nodep) + ");\n");
|
|
}
|
|
}
|
|
|
|
puts("\n");
|
|
puts("#ifdef __cplusplus\n");
|
|
puts("}\n");
|
|
puts("#endif\n");
|
|
}
|
|
|
|
//######################################################################
|
|
|
|
void EmitCSyms::emitDpiImp() {
|
|
UINFO(6, __FUNCTION__ << ": " << endl);
|
|
string filename = v3Global.opt.makeDir() + "/" + topClassName() + "__Dpi.cpp";
|
|
AstCFile* cfilep = newCFile(filename, false /*slow*/, true /*source*/);
|
|
cfilep->support(true);
|
|
V3OutCFile hf(filename);
|
|
m_ofp = &hf;
|
|
|
|
m_ofp->putsHeader();
|
|
puts("// DESCR"
|
|
"IPTION: Verilator output: Implementation of DPI export functions.\n");
|
|
puts("//\n");
|
|
puts("// Verilator compiles this file in when DPI functions are used.\n");
|
|
puts("// If you have multiple Verilated designs with the same DPI exported\n");
|
|
puts("// function names, you will get multiple definition link errors from here.\n");
|
|
puts("// This is an unfortunate result of the DPI specification.\n");
|
|
puts("// To solve this, either\n");
|
|
puts("// 1. Call " + topClassName() + "::{export_function} instead,\n");
|
|
puts("// and do not even bother to compile this file\n");
|
|
puts("// or 2. Compile all __Dpi.cpp files in the same compiler run,\n");
|
|
puts("// and #ifdefs already inserted here will sort everything out.\n");
|
|
puts("\n");
|
|
|
|
puts("#include \"" + topClassName() + "__Dpi.h\"\n");
|
|
puts("#include \"" + topClassName() + ".h\"\n");
|
|
puts("\n");
|
|
|
|
for (AstCFunc* nodep : m_dpis) {
|
|
if (nodep->dpiExportWrapper()) {
|
|
// Prevent multi-definition if used by multiple models
|
|
puts("#ifndef VL_DPIDECL_" + nodep->name() + "_\n");
|
|
puts("#define VL_DPIDECL_" + nodep->name() + "_\n");
|
|
puts(nodep->rtnTypeVoid() + " " + nodep->name() + "(" + cFuncArgs(nodep) + ") {\n");
|
|
puts("// DPI export" + ifNoProtect(" at " + nodep->fileline()->ascii()) + "\n");
|
|
puts("return " + topClassName() + "::" + nodep->name() + "(");
|
|
string args;
|
|
for (AstNode* stmtp = nodep->argsp(); stmtp; stmtp = stmtp->nextp()) {
|
|
if (const AstVar* portp = VN_CAST(stmtp, Var)) {
|
|
if (portp->isIO() && !portp->isFuncReturn()) {
|
|
if (args != "") args += ", ";
|
|
args += portp->name();
|
|
}
|
|
}
|
|
}
|
|
puts(args + ");\n");
|
|
puts("}\n");
|
|
puts("#endif\n");
|
|
puts("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
//######################################################################
|
|
// EmitC class functions
|
|
|
|
void V3EmitC::emitcSyms(bool dpiHdrOnly) {
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
|
EmitCSyms(v3Global.rootp(), dpiHdrOnly);
|
|
}
|