forked from github/verilator
Remove magic code fragments form EmitCTrace, so Emit need not be aware that a function is tracing related or not (apart from the purpose of file name generation). All necessary code is now generated via text nodes in V3TraceDecl and V3Trace. No functional change intended.
1044 lines
41 KiB
C++
1044 lines
41 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 "V3EmitCFunc.h"
|
|
|
|
#include <vector>
|
|
#include <unordered_set>
|
|
|
|
//######################################################################
|
|
// Internal EmitC implementation
|
|
|
|
class EmitCImp final : EmitCFunc {
|
|
// MEMBERS
|
|
AstNodeModule* m_fileModp = nullptr; // Files (names, headers) constructed using this module
|
|
bool m_slow = false; // Creating __Slow file
|
|
bool m_fast = false; // Creating non __Slow file (or both)
|
|
|
|
//---------------------------------------
|
|
// METHODS
|
|
|
|
V3OutCFile* newOutCFile(bool slow, bool source, int filenum = 0) {
|
|
m_lazyDecls.reset(); // Need to emit new lazy declarations
|
|
|
|
string filenameNoExt = v3Global.opt.makeDir() + "/" + prefixNameProtect(m_fileModp);
|
|
if (filenum) filenameNoExt += "__" + cvtToStr(filenum);
|
|
filenameNoExt += (slow ? "__Slow" : "");
|
|
V3OutCFile* ofp = nullptr;
|
|
if (v3Global.opt.lintOnly()) {
|
|
// Unfortunately we have some lint checks here, so we can't just skip processing.
|
|
// We should move them to a different stage.
|
|
const string filename = VL_DEV_NULL;
|
|
newCFile(filename, slow, source);
|
|
ofp = new V3OutCFile(filename);
|
|
} else if (optSystemC()) {
|
|
const string filename = filenameNoExt + (source ? ".cpp" : ".h");
|
|
newCFile(filename, slow, source);
|
|
ofp = new V3OutScFile(filename);
|
|
} else {
|
|
const string filename = filenameNoExt + (source ? ".cpp" : ".h");
|
|
newCFile(filename, slow, source);
|
|
ofp = new V3OutCFile(filename);
|
|
}
|
|
|
|
ofp->putsHeader();
|
|
if (source) {
|
|
ofp->puts("// DESCRIPTION: Verilator output: Design implementation internals\n");
|
|
} else {
|
|
ofp->puts("// DESCRIPTION: Verilator output: Design internal header\n");
|
|
}
|
|
ofp->puts("// See " + topClassName() + ".h for the primary calling header\n");
|
|
return ofp;
|
|
}
|
|
|
|
//---------------------------------------
|
|
// VISITORS
|
|
using EmitCFunc::visit; // Suppress hidden overloaded virtual function warning
|
|
virtual void visit(AstCFunc* nodep) override {
|
|
// TRACE_* and DPI handled elsewhere
|
|
if (nodep->isTrace()) return;
|
|
if (nodep->dpiImportPrototype()) return;
|
|
if (nodep->dpiExportDispatcher()) return;
|
|
if (!(nodep->slow() ? m_slow : m_fast)) return;
|
|
|
|
maybeSplit();
|
|
|
|
EmitCFunc::visit(nodep);
|
|
}
|
|
|
|
//---------------------------------------
|
|
// ACCESSORS
|
|
|
|
// METHODS
|
|
// Low level
|
|
void emitTypedefs(AstNode* firstp) {
|
|
bool first = true;
|
|
for (AstNode* loopp = firstp; loopp; loopp = loopp->nextp()) {
|
|
if (const AstTypedef* nodep = VN_CAST(loopp, Typedef)) {
|
|
if (nodep->attrPublic()) {
|
|
if (first) {
|
|
first = false;
|
|
puts("\n// TYPEDEFS\n");
|
|
puts("// That were declared public\n");
|
|
} else {
|
|
puts("\n");
|
|
}
|
|
if (const AstEnumDType* adtypep
|
|
= VN_CAST(nodep->dtypep()->skipRefToEnump(), EnumDType)) {
|
|
if (adtypep->width() > 64) {
|
|
putsDecoration("// enum " + nodep->nameProtect()
|
|
+ " // Ignored: Too wide for C++\n");
|
|
} else {
|
|
puts("enum " + nodep->name() + " {\n");
|
|
for (AstEnumItem* itemp = adtypep->itemsp(); itemp;
|
|
itemp = VN_CAST(itemp->nextp(), EnumItem)) {
|
|
puts(itemp->nameProtect());
|
|
puts(" = ");
|
|
iterateAndNextNull(itemp->valuep());
|
|
if (VN_IS(itemp->nextp(), EnumItem)) puts(",");
|
|
puts("\n");
|
|
}
|
|
puts("};\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void emitParams(AstNodeModule* modp, bool init, bool* firstp, string& sectionr) {
|
|
bool anyi = false;
|
|
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
if (const AstVar* varp = VN_CAST(nodep, Var)) {
|
|
if (varp->isParam() && (varp->isUsedParam() || varp->isSigPublic())) {
|
|
if (!init && sectionr != "") {
|
|
puts(sectionr);
|
|
sectionr = "";
|
|
}
|
|
UASSERT_OBJ(varp->valuep(), nodep, "No init for a param?");
|
|
// These should be static const values, however older MSVC++ did't
|
|
// support them; should be ok now under C++11, need to refactor.
|
|
if (varp->isWide()) { // Unsupported for output
|
|
if (!init) {
|
|
putsDecoration("// enum WData " + varp->nameProtect() + " //wide");
|
|
}
|
|
} else if (varp->isString()) {
|
|
if (init) {
|
|
puts("const std::string ");
|
|
puts(prefixNameProtect(modp) + "::" + protect("var_" + varp->name())
|
|
+ "(");
|
|
iterateAndNextNull(varp->valuep());
|
|
puts(");\n");
|
|
anyi = true;
|
|
} else {
|
|
puts("static const std::string " + protect("var_" + varp->name())
|
|
+ ";\n");
|
|
}
|
|
} else if (!VN_IS(varp->valuep(), Const)) { // Unsupported for output
|
|
// putsDecoration("// enum ..... "+varp->nameProtect()
|
|
// +"not simple value, see variable above instead");
|
|
} else if (VN_IS(varp->dtypep(), BasicDType)
|
|
&& VN_CAST(varp->dtypep(), BasicDType)
|
|
->isOpaque()) { // Can't put out e.g. doubles
|
|
} else {
|
|
if (init) {
|
|
puts(varp->isQuad() ? "const QData " : "const IData ");
|
|
puts(prefixNameProtect(modp) + "::" + protect("var_" + varp->name())
|
|
+ "(");
|
|
iterateAndNextNull(varp->valuep());
|
|
puts(");\n");
|
|
anyi = true;
|
|
} else {
|
|
// enum
|
|
puts(varp->isQuad() ? "enum _QData" : "enum _IData");
|
|
puts("" + varp->nameProtect() + " { " + varp->nameProtect() + " = ");
|
|
iterateAndNextNull(varp->valuep());
|
|
puts("};\n");
|
|
// var
|
|
puts(varp->isQuad() ? "static const QData " : "static const IData ");
|
|
puts(protect("var_" + varp->name()) + ";\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (anyi) puts("\n");
|
|
}
|
|
void emitIntFuncDecls(AstNodeModule* modp, bool inClassBody) {
|
|
std::vector<const AstCFunc*> funcsp;
|
|
|
|
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
if (const AstCFunc* funcp = VN_CAST(nodep, CFunc)) {
|
|
if (funcp->dpiImportPrototype()) // Declared in __Dpi.h
|
|
continue;
|
|
if (funcp->dpiExportDispatcher()) // Declared in __Dpi.h
|
|
continue;
|
|
if (funcp->isMethod() != inClassBody) // Only methods go inside class
|
|
continue;
|
|
if (funcp->isMethod() && funcp->isLoose()) // Loose methods are declared lazily
|
|
continue;
|
|
funcsp.push_back(funcp);
|
|
}
|
|
}
|
|
|
|
stable_sort(funcsp.begin(), funcsp.end(), [](const AstNode* ap, const AstNode* bp) { //
|
|
return ap->name() < bp->name();
|
|
});
|
|
|
|
for (const AstCFunc* funcp : funcsp) {
|
|
if (inClassBody) ofp()->putsPrivate(funcp->declPrivate());
|
|
emitCFuncDecl(funcp, modp);
|
|
}
|
|
}
|
|
|
|
// Medium level
|
|
void emitCtorImp(AstNodeModule* modp);
|
|
void emitConfigureImp(AstNodeModule* modp);
|
|
void emitCoverageDecl(AstNodeModule* modp);
|
|
void emitCoverageImp(AstNodeModule* modp);
|
|
void emitDestructorImp(AstNodeModule* modp);
|
|
void emitSavableImp(AstNodeModule* modp);
|
|
void emitTextSection(AstType type);
|
|
// High level
|
|
void emitImpTop();
|
|
void emitImp(AstNodeModule* modp);
|
|
void emitIntTop(const AstNodeModule* modp);
|
|
void emitInt(AstNodeModule* modp);
|
|
void maybeSplit();
|
|
|
|
public:
|
|
EmitCImp() {}
|
|
virtual ~EmitCImp() override = default;
|
|
void mainImp(AstNodeModule* modp, bool slow);
|
|
void mainInt(AstNodeModule* modp);
|
|
void mainDoFunc(AstCFunc* nodep) { iterate(nodep); }
|
|
};
|
|
|
|
//######################################################################
|
|
// Internal EmitC
|
|
|
|
void EmitCImp::emitCoverageDecl(AstNodeModule*) {
|
|
if (v3Global.opt.coverage()) {
|
|
ofp()->putsPrivate(false); // Accessed from loose methods
|
|
putsDecoration("// Coverage\n");
|
|
puts("void __vlCoverInsert(");
|
|
puts(v3Global.opt.threads() ? "std::atomic<uint32_t>" : "uint32_t");
|
|
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
|
|
puts("const char* hierp, const char* pagep, const char* commentp, const char* "
|
|
"linescovp);\n");
|
|
}
|
|
}
|
|
|
|
void EmitCImp::emitCtorImp(AstNodeModule* modp) {
|
|
puts("\n");
|
|
bool first = true;
|
|
string section;
|
|
emitParams(modp, true, &first, section /*ref*/);
|
|
|
|
const string modName = prefixNameProtect(modp);
|
|
|
|
puts("\n");
|
|
m_lazyDecls.emit("void " + modName + "__", protect("_ctor_var_reset"),
|
|
"(" + modName + "* vlSelf);");
|
|
puts("\n");
|
|
|
|
if (VN_IS(modp, Class)) {
|
|
modp->v3fatalSrc("constructors should be AstCFuncs instead");
|
|
} else {
|
|
puts(modName + "::" + modName + "(const char* _vcname__)\n");
|
|
puts(" : VerilatedModule(_vcname__)\n");
|
|
first = false; // printed the first ':'
|
|
}
|
|
emitVarCtors(&first);
|
|
|
|
puts(" {\n");
|
|
|
|
putsDecoration("// Reset structure values\n");
|
|
puts(modName + "__" + protect("_ctor_var_reset") + "(this);\n");
|
|
emitTextSection(AstType::atScCtor);
|
|
|
|
puts("}\n");
|
|
}
|
|
|
|
void EmitCImp::emitConfigureImp(AstNodeModule* modp) {
|
|
const string modName = prefixNameProtect(modp);
|
|
|
|
if (v3Global.opt.coverage()) {
|
|
puts("\n");
|
|
m_lazyDecls.emit("void " + modName + "__", protect("_configure_coverage"),
|
|
"(" + modName + "* vlSelf, bool first);");
|
|
}
|
|
|
|
puts("\nvoid " + modName + "::" + protect("__Vconfigure") + "(" + symClassName()
|
|
+ "* _vlSymsp, bool first) {\n");
|
|
puts("if (false && first) {} // Prevent unused\n");
|
|
puts("this->vlSymsp = _vlSymsp;\n"); // First, as later stuff needs it.
|
|
if (v3Global.opt.coverage()) {
|
|
puts(modName + "__" + protect("_configure_coverage") + "(this, first);\n");
|
|
}
|
|
puts("}\n");
|
|
splitSizeInc(10);
|
|
}
|
|
|
|
void EmitCImp::emitCoverageImp(AstNodeModule*) {
|
|
if (v3Global.opt.coverage()) {
|
|
puts("\n// Coverage\n");
|
|
// Rather than putting out VL_COVER_INSERT calls directly, we do it via this function
|
|
// This gets around gcc slowness constructing all of the template arguments.
|
|
puts("void " + prefixNameProtect(m_modp) + "::__vlCoverInsert(");
|
|
puts(v3Global.opt.threads() ? "std::atomic<uint32_t>" : "uint32_t");
|
|
puts("* countp, bool enable, const char* filenamep, int lineno, int column,\n");
|
|
puts("const char* hierp, const char* pagep, const char* commentp, const char* linescovp) "
|
|
"{\n");
|
|
if (v3Global.opt.threads()) {
|
|
puts("assert(sizeof(uint32_t) == sizeof(std::atomic<uint32_t>));\n");
|
|
puts("uint32_t* count32p = reinterpret_cast<uint32_t*>(countp);\n");
|
|
} else {
|
|
puts("uint32_t* count32p = countp;\n");
|
|
}
|
|
// static doesn't need save-restore as is constant
|
|
puts("static uint32_t fake_zero_count = 0;\n");
|
|
// Used for second++ instantiation of identical bin
|
|
puts("if (!enable) count32p = &fake_zero_count;\n");
|
|
puts("*count32p = 0;\n");
|
|
puts("VL_COVER_INSERT(vlSymsp->_vm_contextp__->coveragep(), count32p,");
|
|
puts(" \"filename\",filenamep,");
|
|
puts(" \"lineno\",lineno,");
|
|
puts(" \"column\",column,\n");
|
|
// Need to move hier into scopes and back out if do this
|
|
// puts( "\"hier\",std::string(vlSymsp->name())+hierp,");
|
|
puts("\"hier\",std::string(name())+hierp,");
|
|
puts(" \"page\",pagep,");
|
|
puts(" \"comment\",commentp,");
|
|
puts(" (linescovp[0] ? \"linescov\" : \"\"), linescovp);\n");
|
|
puts("}\n");
|
|
splitSizeInc(10);
|
|
}
|
|
}
|
|
|
|
void EmitCImp::emitDestructorImp(AstNodeModule* modp) {
|
|
puts("\n");
|
|
puts(prefixNameProtect(modp) + "::~" + prefixNameProtect(modp) + "() {\n");
|
|
emitTextSection(AstType::atScDtor);
|
|
puts("}\n");
|
|
splitSizeInc(10);
|
|
}
|
|
|
|
void EmitCImp::emitSavableImp(AstNodeModule* modp) {
|
|
if (v3Global.opt.savable()) {
|
|
puts("\n// Savable\n");
|
|
for (int de = 0; de < 2; ++de) {
|
|
const string classname = de ? "VerilatedDeserialize" : "VerilatedSerialize";
|
|
const string funcname = de ? "__Vdeserialize" : "__Vserialize";
|
|
const string op = de ? ">>" : "<<";
|
|
// NOLINTNEXTLINE(performance-inefficient-string-concatenation)
|
|
puts("void " + prefixNameProtect(modp) + "::" + protect(funcname) + "(" + classname
|
|
+ "& os) {\n");
|
|
// Place a computed checksum to ensure proper structure save/restore formatting
|
|
// OK if this hash includes some things we won't dump, since
|
|
// just looking for loading the wrong model
|
|
VHashSha256 hash;
|
|
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
if (const AstVar* varp = VN_CAST(nodep, Var)) {
|
|
hash.insert(varp->name());
|
|
hash.insert(varp->dtypep()->width());
|
|
}
|
|
}
|
|
ofp()->printf("vluint64_t __Vcheckval = 0x%" VL_PRI64 "xULL;\n",
|
|
static_cast<vluint64_t>(hash.digestUInt64()));
|
|
if (de) {
|
|
puts("os.readAssert(__Vcheckval);\n");
|
|
} else {
|
|
puts("os << __Vcheckval;\n");
|
|
}
|
|
|
|
// Save context
|
|
// If multiple models save the same context we'll save it multiple
|
|
// times, but is harmless, and doing it otherwise would break
|
|
// backwards compatibility.
|
|
puts("os " + op + " vlSymsp->_vm_contextp__;\n");
|
|
|
|
// Save all members
|
|
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
if (const AstVar* varp = VN_CAST(nodep, Var)) {
|
|
if (varp->isIO() && modp->isTop() && optSystemC()) {
|
|
// System C top I/O doesn't need loading, as the
|
|
// lower level subinst code does it.
|
|
} else if (varp->isParam()) {
|
|
} else if (varp->isStatic() && varp->isConst()) {
|
|
} else {
|
|
int vects = 0;
|
|
AstNodeDType* elementp = varp->dtypeSkipRefp();
|
|
for (AstUnpackArrayDType* arrayp = VN_CAST(elementp, UnpackArrayDType);
|
|
arrayp; arrayp = VN_CAST(elementp, UnpackArrayDType)) {
|
|
const int vecnum = vects++;
|
|
UASSERT_OBJ(arrayp->hi() >= arrayp->lo(), varp,
|
|
"Should have swapped msb & lsb earlier.");
|
|
const string ivar = string("__Vi") + cvtToStr(vecnum);
|
|
puts("for (int __Vi" + cvtToStr(vecnum) + "=" + cvtToStr(0));
|
|
puts("; " + ivar + "<" + cvtToStr(arrayp->elementsConst()));
|
|
puts("; ++" + ivar + ") {\n");
|
|
elementp = arrayp->subDTypep()->skipRefp();
|
|
}
|
|
const AstBasicDType* const basicp = elementp->basicp();
|
|
// Do not save MTask state, only matters within an evaluation
|
|
if (basicp && basicp->keyword().isMTaskState()) continue;
|
|
// Want to detect types that are represented as arrays
|
|
// (i.e. packed types of more than 64 bits).
|
|
if (elementp->isWide()
|
|
&& !(basicp && basicp->keyword() == AstBasicDTypeKwd::STRING)) {
|
|
const int vecnum = vects++;
|
|
const string ivar = string("__Vi") + cvtToStr(vecnum);
|
|
puts("for (int __Vi" + cvtToStr(vecnum) + "=" + cvtToStr(0));
|
|
puts("; " + ivar + "<" + cvtToStr(elementp->widthWords()));
|
|
puts("; ++" + ivar + ") {\n");
|
|
}
|
|
puts("os" + op + varp->nameProtect());
|
|
for (int v = 0; v < vects; ++v) puts("[__Vi" + cvtToStr(v) + "]");
|
|
puts(";\n");
|
|
for (int v = 0; v < vects; ++v) puts("}\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
puts("}\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
void EmitCImp::emitTextSection(AstType type) {
|
|
int last_line = -999;
|
|
for (AstNode* nodep = m_modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
if (const AstNodeText* textp = VN_CAST(nodep, NodeText)) {
|
|
if (nodep->type() == type) {
|
|
if (last_line != nodep->fileline()->lineno()) {
|
|
if (last_line < 0) {
|
|
puts("\n//*** Below code from `systemc in Verilog file\n");
|
|
}
|
|
putsDecoration(
|
|
ifNoProtect("// From `systemc at " + nodep->fileline()->ascii() + "\n"));
|
|
last_line = nodep->fileline()->lineno();
|
|
}
|
|
ofp()->putsNoTracking(textp->text());
|
|
last_line++;
|
|
}
|
|
}
|
|
}
|
|
if (last_line > 0) puts("//*** Above code from `systemc in Verilog file\n\n");
|
|
}
|
|
|
|
void EmitCImp::emitIntTop(const AstNodeModule* modp) {
|
|
// Always have this first; gcc has short circuiting if #ifdef is first in a file
|
|
ofp()->putsGuard();
|
|
puts("\n");
|
|
|
|
ofp()->putsIntTopInclude();
|
|
puts("#include \"verilated_heavy.h\"\n");
|
|
if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n");
|
|
if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n");
|
|
if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n");
|
|
}
|
|
|
|
void EmitCImp::emitInt(AstNodeModule* modp) {
|
|
puts("\n//==========\n\n");
|
|
|
|
if (AstClass* classp = VN_CAST(modp, Class)) {
|
|
if (classp->extendsp())
|
|
puts("#include \"" + prefixNameProtect(classp->extendsp()->classp()->classOrPackagep())
|
|
+ ".h\"\n");
|
|
}
|
|
|
|
emitModCUse(modp, VUseType::INT_INCLUDE);
|
|
|
|
// Declare foreign instances up front to make C++ happy
|
|
puts("class " + symClassName() + ";\n");
|
|
emitModCUse(modp, VUseType::INT_FWD_CLASS);
|
|
|
|
puts("\n//----------\n\n");
|
|
emitTextSection(AstType::atScHdr);
|
|
|
|
if (AstClass* classp = VN_CAST(modp, Class)) {
|
|
puts("class " + prefixNameProtect(modp));
|
|
if (classp->extendsp())
|
|
puts(" : public " + prefixNameProtect(classp->extendsp()->classp()));
|
|
puts(" {\n");
|
|
} else {
|
|
puts("VL_MODULE(" + prefixNameProtect(modp) + ") {\n");
|
|
}
|
|
ofp()->resetPrivate();
|
|
ofp()->putsPrivate(false); // public:
|
|
|
|
{ // Instantiated cells
|
|
bool did = false;
|
|
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
if (AstCell* cellp = VN_CAST(nodep, Cell)) {
|
|
if (!did) {
|
|
did = true;
|
|
putsDecoration("// CELLS\n");
|
|
}
|
|
puts(prefixNameProtect(cellp->modp()) + "* " + cellp->nameProtect() + ";\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
emitTypedefs(modp->stmtsp());
|
|
|
|
string section;
|
|
section = "\n// PORTS\n";
|
|
emitVarList(modp->stmtsp(), EVL_CLASS_IO, "", section /*ref*/);
|
|
|
|
section = "\n// LOCAL SIGNALS\n";
|
|
emitVarList(modp->stmtsp(), EVL_CLASS_SIG, "", section /*ref*/);
|
|
|
|
section = "\n// LOCAL VARIABLES\n";
|
|
emitVarList(modp->stmtsp(), EVL_CLASS_TEMP, "", section /*ref*/);
|
|
|
|
puts("\n// INTERNAL VARIABLES\n");
|
|
if (!VN_IS(modp, Class)) { // Avoid clang unused error (& don't want in every object)
|
|
ofp()->putsPrivate(false); // public: so loose methods can pick it up
|
|
puts(symClassName() + "* vlSymsp; // Symbol table\n");
|
|
}
|
|
ofp()->putsPrivate(false); // public:
|
|
emitCoverageDecl(modp); // may flip public/private
|
|
|
|
section = "\n// PARAMETERS\n";
|
|
ofp()->putsPrivate(false); // public:
|
|
emitVarList(modp->stmtsp(), EVL_CLASS_PAR, "",
|
|
section /*ref*/); // Only those that are non-CONST
|
|
bool first = true;
|
|
emitParams(modp, false, &first, section /*ref*/);
|
|
|
|
if (!VN_IS(modp, Class)) {
|
|
puts("\n// CONSTRUCTORS\n");
|
|
ofp()->resetPrivate();
|
|
// We don't need a private copy constructor, as VerilatedModule has one for us.
|
|
ofp()->putsPrivate(true);
|
|
puts("VL_UNCOPYABLE(" + prefixNameProtect(modp) + "); ///< Copying not allowed\n");
|
|
}
|
|
|
|
if (VN_IS(modp, Class)) {
|
|
// CFuncs with isConstructor/isDestructor used instead
|
|
} else {
|
|
ofp()->putsPrivate(false); // public:
|
|
puts(prefixNameProtect(modp) + "(const char* name);\n");
|
|
puts("~" + prefixNameProtect(modp) + "();\n");
|
|
}
|
|
|
|
emitTextSection(AstType::atScInt);
|
|
|
|
puts("\n// INTERNAL METHODS\n");
|
|
|
|
if (!VN_IS(modp, Class)) {
|
|
ofp()->putsPrivate(false); // public:
|
|
puts("void " + protect("__Vconfigure") + "(" + symClassName() + "* symsp, bool first);\n");
|
|
}
|
|
|
|
ofp()->putsPrivate(false); // public:
|
|
emitIntFuncDecls(modp, true);
|
|
|
|
if (v3Global.opt.savable()) {
|
|
ofp()->putsPrivate(false); // public:
|
|
puts("void " + protect("__Vserialize") + "(VerilatedSerialize& os);\n");
|
|
puts("void " + protect("__Vdeserialize") + "(VerilatedDeserialize& os);\n");
|
|
}
|
|
|
|
puts("}");
|
|
if (!VN_IS(modp, Class)) puts(" VL_ATTR_ALIGNED(VL_CACHE_LINE_BYTES)");
|
|
puts(";\n");
|
|
|
|
puts("\n//----------\n\n");
|
|
emitIntFuncDecls(modp, false);
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
void EmitCImp::emitImpTop() {
|
|
puts("\n");
|
|
puts("#include \"" + prefixNameProtect(m_fileModp) + ".h\"\n");
|
|
puts("#include \"" + symClassName() + ".h\"\n");
|
|
|
|
if (v3Global.dpi()) {
|
|
puts("\n");
|
|
puts("#include \"verilated_dpi.h\"\n");
|
|
}
|
|
|
|
emitModCUse(m_fileModp, VUseType::IMP_INCLUDE);
|
|
emitModCUse(m_fileModp, VUseType::IMP_FWD_CLASS);
|
|
|
|
emitTextSection(AstType::atScImpHdr);
|
|
}
|
|
|
|
void EmitCImp::emitImp(AstNodeModule* modp) {
|
|
puts("\n//==========\n");
|
|
if (m_slow) {
|
|
string section;
|
|
emitVarList(modp->stmtsp(), EVL_CLASS_ALL, prefixNameProtect(modp), section /*ref*/);
|
|
if (!VN_IS(modp, Class)) emitCtorImp(modp);
|
|
if (!VN_IS(modp, Class)) emitConfigureImp(modp);
|
|
if (!VN_IS(modp, Class)) emitDestructorImp(modp);
|
|
emitSavableImp(modp);
|
|
emitCoverageImp(modp);
|
|
}
|
|
|
|
if (m_fast) { emitTextSection(AstType::atScImp); }
|
|
|
|
// Blocks
|
|
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
if (AstCFunc* funcp = VN_CAST(nodep, CFunc)) { mainDoFunc(funcp); }
|
|
}
|
|
}
|
|
|
|
//######################################################################
|
|
|
|
void EmitCImp::maybeSplit() {
|
|
if (!splitNeeded()) return;
|
|
|
|
// Splitting file, so using parallel build.
|
|
v3Global.useParallelBuild(true);
|
|
// Close old file
|
|
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
|
// Open a new file
|
|
m_ofp = newOutCFile(!m_fast, true /*source*/, splitFilenumInc());
|
|
emitImpTop();
|
|
}
|
|
|
|
void EmitCImp::mainInt(AstNodeModule* modp) {
|
|
m_modp = modp;
|
|
m_fileModp = modp;
|
|
m_slow = true;
|
|
m_fast = true;
|
|
|
|
UINFO(5, " Emitting " << prefixNameProtect(modp) << endl);
|
|
|
|
m_ofp = newOutCFile(false /*slow*/, false /*source*/);
|
|
emitIntTop(modp);
|
|
emitInt(modp);
|
|
if (AstClassPackage* packagep = VN_CAST(modp, ClassPackage)) {
|
|
// Put the non-static class implementation in same h file for speed
|
|
m_modp = packagep->classp();
|
|
emitInt(packagep->classp());
|
|
m_modp = modp;
|
|
}
|
|
ofp()->putsEndGuard();
|
|
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
|
}
|
|
|
|
void EmitCImp::mainImp(AstNodeModule* modp, bool slow) {
|
|
// Output a module
|
|
m_modp = modp;
|
|
m_fileModp = modp;
|
|
m_slow = slow;
|
|
m_fast = !slow;
|
|
|
|
UINFO(5, " Emitting " << prefixNameProtect(modp) << endl);
|
|
|
|
m_ofp = newOutCFile(!m_fast, true /*source*/);
|
|
emitImpTop();
|
|
emitImp(modp);
|
|
|
|
if (AstClassPackage* packagep = VN_CAST(modp, ClassPackage)) {
|
|
// Put the non-static class implementation in same C++ files as
|
|
// often optimizations are possible when both are seen by the
|
|
// compiler together
|
|
m_modp = packagep->classp();
|
|
emitImp(packagep->classp());
|
|
m_modp = modp;
|
|
}
|
|
|
|
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
|
}
|
|
|
|
//######################################################################
|
|
// Tracing routines
|
|
|
|
class EmitCTrace final : EmitCFunc {
|
|
// NODE STATE/TYPES
|
|
// Cleared on netlist
|
|
// AstNode::user1() -> int. Enum number
|
|
AstUser1InUse m_inuser1;
|
|
|
|
// MEMBERS
|
|
const bool m_slow; // Making slow file
|
|
int m_enumNum = 0; // Enumeration number (whole netlist)
|
|
|
|
// METHODS
|
|
void newOutCFile(int filenum) {
|
|
m_lazyDecls.reset(); // Need to emit new lazy declarations
|
|
|
|
string filename
|
|
= (v3Global.opt.makeDir() + "/" + topClassName() + "_" + protect("_Trace"));
|
|
if (filenum) filename += "__" + cvtToStr(filenum);
|
|
filename += (m_slow ? "__Slow" : "");
|
|
filename += ".cpp";
|
|
|
|
AstCFile* cfilep = newCFile(filename, m_slow, true /*source*/);
|
|
cfilep->support(true);
|
|
|
|
if (m_ofp) v3fatalSrc("Previous file not closed");
|
|
if (optSystemC()) {
|
|
m_ofp = new V3OutScFile(filename);
|
|
} else {
|
|
m_ofp = new V3OutCFile(filename);
|
|
}
|
|
m_ofp->putsHeader();
|
|
m_ofp->puts("// DESCR"
|
|
"IPTION: Verilator output: Tracing implementation internals\n");
|
|
|
|
// Includes
|
|
puts("#include \"" + v3Global.opt.traceSourceLang() + ".h\"\n");
|
|
puts("#include \"" + symClassName() + ".h\"\n");
|
|
puts("\n");
|
|
}
|
|
|
|
bool emitTraceIsScBv(AstTraceInc* nodep) {
|
|
const AstVarRef* varrefp = VN_CAST(nodep->declp()->valuep(), VarRef);
|
|
if (!varrefp) return false;
|
|
AstVar* varp = varrefp->varp();
|
|
return varp->isSc() && varp->isScBv();
|
|
}
|
|
|
|
bool emitTraceIsScBigUint(AstTraceInc* nodep) {
|
|
const AstVarRef* varrefp = VN_CAST(nodep->declp()->valuep(), VarRef);
|
|
if (!varrefp) return false;
|
|
AstVar* varp = varrefp->varp();
|
|
return varp->isSc() && varp->isScBigUint();
|
|
}
|
|
|
|
bool emitTraceIsScUint(AstTraceInc* nodep) {
|
|
const AstVarRef* varrefp = VN_CAST(nodep->declp()->valuep(), VarRef);
|
|
if (!varrefp) return false;
|
|
AstVar* varp = varrefp->varp();
|
|
return varp->isSc() && varp->isScUint();
|
|
}
|
|
|
|
void emitTraceInitOne(AstTraceDecl* nodep, int enumNum) {
|
|
if (nodep->dtypep()->basicp()->isDouble()) {
|
|
puts("tracep->declDouble");
|
|
} else if (nodep->isWide()) {
|
|
puts("tracep->declArray");
|
|
} else if (nodep->isQuad()) {
|
|
puts("tracep->declQuad");
|
|
} else if (nodep->bitRange().ranged()) {
|
|
puts("tracep->declBus");
|
|
} else {
|
|
puts("tracep->declBit");
|
|
}
|
|
|
|
puts("(c+" + cvtToStr(nodep->code()));
|
|
if (nodep->arrayRange().ranged()) puts("+i*" + cvtToStr(nodep->widthWords()));
|
|
puts(",");
|
|
if (nodep->isScoped()) puts("Verilated::catName(scopep,");
|
|
putsQuoted(VIdProtect::protectWordsIf(nodep->showname(), nodep->protect()));
|
|
if (nodep->isScoped()) puts(",(int)scopet,\" \")");
|
|
// Direction
|
|
if (v3Global.opt.traceFormat().fst()) {
|
|
puts("," + cvtToStr(enumNum));
|
|
// fstVarDir
|
|
if (nodep->declDirection().isInoutish()) {
|
|
puts(",FST_VD_INOUT");
|
|
} else if (nodep->declDirection().isWritable()) {
|
|
puts(",FST_VD_OUTPUT");
|
|
} else if (nodep->declDirection().isNonOutput()) {
|
|
puts(",FST_VD_INPUT");
|
|
} else {
|
|
puts(", FST_VD_IMPLICIT");
|
|
}
|
|
//
|
|
// fstVarType
|
|
const AstVarType vartype = nodep->varType();
|
|
const AstBasicDTypeKwd kwd = nodep->declKwd();
|
|
string fstvt;
|
|
// Doubles have special decoding properties, so must indicate if a double
|
|
if (nodep->dtypep()->basicp()->isDouble()) {
|
|
if (vartype == AstVarType::GPARAM || vartype == AstVarType::LPARAM) {
|
|
fstvt = "FST_VT_VCD_REAL_PARAMETER";
|
|
} else {
|
|
fstvt = "FST_VT_VCD_REAL";
|
|
}
|
|
}
|
|
// clang-format off
|
|
else if (vartype == AstVarType::GPARAM) { fstvt = "FST_VT_VCD_PARAMETER"; }
|
|
else if (vartype == AstVarType::LPARAM) { fstvt = "FST_VT_VCD_PARAMETER"; }
|
|
else if (vartype == AstVarType::SUPPLY0) { fstvt = "FST_VT_VCD_SUPPLY0"; }
|
|
else if (vartype == AstVarType::SUPPLY1) { fstvt = "FST_VT_VCD_SUPPLY1"; }
|
|
else if (vartype == AstVarType::TRI0) { fstvt = "FST_VT_VCD_TRI0"; }
|
|
else if (vartype == AstVarType::TRI1) { fstvt = "FST_VT_VCD_TRI1"; }
|
|
else if (vartype == AstVarType::TRIWIRE) { fstvt = "FST_VT_VCD_TRI"; }
|
|
else if (vartype == AstVarType::WIRE) { fstvt = "FST_VT_VCD_WIRE"; }
|
|
else if (vartype == AstVarType::PORT) { fstvt = "FST_VT_VCD_WIRE"; }
|
|
//
|
|
else if (kwd == AstBasicDTypeKwd::INTEGER) { fstvt = "FST_VT_VCD_INTEGER"; }
|
|
else if (kwd == AstBasicDTypeKwd::BIT) { fstvt = "FST_VT_SV_BIT"; }
|
|
else if (kwd == AstBasicDTypeKwd::LOGIC) { fstvt = "FST_VT_SV_LOGIC"; }
|
|
else if (kwd == AstBasicDTypeKwd::INT) { fstvt = "FST_VT_SV_INT"; }
|
|
else if (kwd == AstBasicDTypeKwd::SHORTINT) { fstvt = "FST_VT_SV_SHORTINT"; }
|
|
else if (kwd == AstBasicDTypeKwd::LONGINT) { fstvt = "FST_VT_SV_LONGINT"; }
|
|
else if (kwd == AstBasicDTypeKwd::BYTE) { fstvt = "FST_VT_SV_BYTE"; }
|
|
else { fstvt = "FST_VT_SV_BIT"; }
|
|
// clang-format on
|
|
//
|
|
// Not currently supported
|
|
// FST_VT_VCD_EVENT
|
|
// FST_VT_VCD_PORT
|
|
// FST_VT_VCD_SHORTREAL
|
|
// FST_VT_VCD_REALTIME
|
|
// FST_VT_VCD_SPARRAY
|
|
// FST_VT_VCD_TRIAND
|
|
// FST_VT_VCD_TRIOR
|
|
// FST_VT_VCD_TRIREG
|
|
// FST_VT_VCD_WAND
|
|
// FST_VT_VCD_WOR
|
|
// FST_VT_SV_ENUM
|
|
// FST_VT_GEN_STRING
|
|
puts("," + fstvt);
|
|
}
|
|
// Range
|
|
if (nodep->arrayRange().ranged()) {
|
|
puts(", true,(i+" + cvtToStr(nodep->arrayRange().lo()) + ")");
|
|
} else {
|
|
puts(", false,-1");
|
|
}
|
|
if (!nodep->dtypep()->basicp()->isDouble() && nodep->bitRange().ranged()) {
|
|
puts(", " + cvtToStr(nodep->bitRange().left()) + ","
|
|
+ cvtToStr(nodep->bitRange().right()));
|
|
}
|
|
puts(");");
|
|
}
|
|
|
|
int emitTraceDeclDType(AstNodeDType* nodep) {
|
|
// Return enum number or -1 for none
|
|
if (v3Global.opt.traceFormat().fst()) {
|
|
// Skip over refs-to-refs, but stop before final ref so can get data type name
|
|
// Alternatively back in V3Width we could push enum names from upper typedefs
|
|
if (AstEnumDType* enump = VN_CAST(nodep->skipRefToEnump(), EnumDType)) {
|
|
int enumNum = enump->user1();
|
|
if (!enumNum) {
|
|
enumNum = ++m_enumNum;
|
|
enump->user1(enumNum);
|
|
int nvals = 0;
|
|
puts("{\n");
|
|
puts("const char* " + protect("__VenumItemNames") + "[]\n");
|
|
puts("= {");
|
|
for (AstEnumItem* itemp = enump->itemsp(); itemp;
|
|
itemp = VN_CAST(itemp->nextp(), EnumItem)) {
|
|
if (++nvals > 1) puts(", ");
|
|
putbs("\"" + itemp->prettyName() + "\"");
|
|
}
|
|
puts("};\n");
|
|
nvals = 0;
|
|
puts("const char* " + protect("__VenumItemValues") + "[]\n");
|
|
puts("= {");
|
|
for (AstEnumItem* itemp = enump->itemsp(); itemp;
|
|
itemp = VN_CAST(itemp->nextp(), EnumItem)) {
|
|
AstConst* constp = VN_CAST(itemp->valuep(), Const);
|
|
if (++nvals > 1) puts(", ");
|
|
putbs("\"" + constp->num().displayed(nodep, "%0b") + "\"");
|
|
}
|
|
puts("};\n");
|
|
puts("tracep->declDTypeEnum(" + cvtToStr(enumNum) + ", \""
|
|
+ enump->prettyName() + "\", " + cvtToStr(nvals) + ", "
|
|
+ cvtToStr(enump->widthMin()) + ", " + protect("__VenumItemNames") + ", "
|
|
+ protect("__VenumItemValues") + ");\n");
|
|
puts("}\n");
|
|
}
|
|
return enumNum;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void emitTraceChangeOne(AstTraceInc* nodep, int arrayindex) {
|
|
iterateAndNextNull(nodep->precondsp());
|
|
const string func = nodep->full() ? "full" : "chg";
|
|
bool emitWidth = true;
|
|
if (nodep->dtypep()->basicp()->isDouble()) {
|
|
puts("tracep->" + func + "Double");
|
|
emitWidth = false;
|
|
} else if (nodep->isWide() || emitTraceIsScBv(nodep) || emitTraceIsScBigUint(nodep)) {
|
|
puts("tracep->" + func + "WData");
|
|
} else if (nodep->isQuad()) {
|
|
puts("tracep->" + func + "QData");
|
|
} else if (nodep->declp()->widthMin() > 16) {
|
|
puts("tracep->" + func + "IData");
|
|
} else if (nodep->declp()->widthMin() > 8) {
|
|
puts("tracep->" + func + "SData");
|
|
} else if (nodep->declp()->widthMin() > 1) {
|
|
puts("tracep->" + func + "CData");
|
|
} else {
|
|
puts("tracep->" + func + "Bit");
|
|
emitWidth = false;
|
|
}
|
|
|
|
const uint32_t offset = (arrayindex < 0) ? 0 : (arrayindex * nodep->declp()->widthWords());
|
|
const uint32_t code = nodep->declp()->code() + offset;
|
|
puts(v3Global.opt.trueTraceThreads() && !nodep->full() ? "(base+" : "(oldp+");
|
|
puts(cvtToStr(code - nodep->baseCode()));
|
|
puts(",");
|
|
emitTraceValue(nodep, arrayindex);
|
|
if (emitWidth) puts("," + cvtToStr(nodep->declp()->widthMin()));
|
|
puts(");\n");
|
|
}
|
|
|
|
void emitTraceValue(AstTraceInc* nodep, int arrayindex) {
|
|
if (AstVarRef* const varrefp = VN_CAST(nodep->valuep(), VarRef)) {
|
|
AstVar* varp = varrefp->varp();
|
|
puts("(");
|
|
if (emitTraceIsScBigUint(nodep)) {
|
|
puts("(vluint32_t*)");
|
|
} else if (emitTraceIsScBv(nodep)) {
|
|
puts("VL_SC_BV_DATAP(");
|
|
}
|
|
iterate(varrefp); // Put var name out
|
|
// Tracing only supports 1D arrays
|
|
if (nodep->declp()->arrayRange().ranged()) {
|
|
if (arrayindex == -2) {
|
|
puts("[i]");
|
|
} else if (arrayindex == -1) {
|
|
puts("[0]");
|
|
} else {
|
|
puts("[" + cvtToStr(arrayindex) + "]");
|
|
}
|
|
}
|
|
if (varp->isSc()) puts(".read()");
|
|
if (emitTraceIsScUint(nodep)) {
|
|
puts(nodep->isQuad() ? ".to_uint64()" : ".to_uint()");
|
|
} else if (emitTraceIsScBigUint(nodep)) {
|
|
puts(".get_raw()");
|
|
} else if (emitTraceIsScBv(nodep)) {
|
|
puts(")");
|
|
}
|
|
puts(")");
|
|
} else {
|
|
puts("(");
|
|
iterate(nodep->valuep());
|
|
puts(")");
|
|
}
|
|
}
|
|
|
|
// VISITORS
|
|
using EmitCFunc::visit; // Suppress hidden overloaded virtual function warning
|
|
virtual void visit(AstCFunc* nodep) override {
|
|
if (!nodep->isTrace()) return;
|
|
if (nodep->slow() != m_slow) return;
|
|
|
|
if (splitNeeded()) {
|
|
// Splitting file, so using parallel build.
|
|
v3Global.useParallelBuild(true);
|
|
// Close old file
|
|
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
|
// Open a new file
|
|
newOutCFile(splitFilenumInc());
|
|
}
|
|
|
|
EmitCFunc::visit(nodep);
|
|
}
|
|
virtual void visit(AstTraceDecl* nodep) override {
|
|
const int enumNum = emitTraceDeclDType(nodep->dtypep());
|
|
if (nodep->arrayRange().ranged()) {
|
|
puts("{int i; for (i=0; i<" + cvtToStr(nodep->arrayRange().elements()) + "; i++) {\n");
|
|
emitTraceInitOne(nodep, enumNum);
|
|
puts("}}\n");
|
|
} else {
|
|
emitTraceInitOne(nodep, enumNum);
|
|
puts("\n");
|
|
}
|
|
}
|
|
virtual void visit(AstTraceInc* nodep) override {
|
|
if (nodep->declp()->arrayRange().ranged()) {
|
|
// It traces faster if we unroll the loop
|
|
for (int i = 0; i < nodep->declp()->arrayRange().elements(); i++) {
|
|
emitTraceChangeOne(nodep, i);
|
|
}
|
|
} else {
|
|
emitTraceChangeOne(nodep, -1);
|
|
}
|
|
}
|
|
|
|
explicit EmitCTrace(AstNodeModule* modp, bool slow)
|
|
: m_slow{slow} {
|
|
m_modp = modp;
|
|
// Open output file
|
|
newOutCFile(0);
|
|
// Emit functions
|
|
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
if (AstCFunc* const funcp = VN_CAST(nodep, CFunc)) { iterate(funcp); }
|
|
}
|
|
// Close output file
|
|
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
|
|
}
|
|
virtual ~EmitCTrace() override = default;
|
|
|
|
public:
|
|
static void main(AstNodeModule* modp, bool slow) { EmitCTrace(modp, slow); }
|
|
};
|
|
|
|
//######################################################################
|
|
// EmitC class functions
|
|
|
|
void V3EmitC::emitc() {
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
|
// Set user4p in all CFunc and Var to point to the containing AstNodeModule
|
|
AstUser4InUse user4InUse;
|
|
const auto setAll = [](AstNodeModule* modp) -> void {
|
|
for (AstNode* nodep = VN_CAST(modp, NodeModule)->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
if (VN_IS(nodep, CFunc) || VN_IS(nodep, Var)) nodep->user4p(modp);
|
|
}
|
|
};
|
|
for (AstNode* modp = v3Global.rootp()->modulesp(); modp; modp = modp->nextp()) {
|
|
setAll(VN_CAST(modp, NodeModule));
|
|
}
|
|
setAll(v3Global.rootp()->constPoolp()->modp());
|
|
|
|
// Process each module in turn
|
|
for (AstNodeModule* nodep = v3Global.rootp()->modulesp(); nodep;
|
|
nodep = VN_CAST(nodep->nextp(), NodeModule)) {
|
|
if (VN_IS(nodep, Class)) continue; // Imped with ClassPackage
|
|
{
|
|
EmitCImp cint;
|
|
cint.mainInt(nodep);
|
|
cint.mainImp(nodep, true);
|
|
}
|
|
{
|
|
EmitCImp fast;
|
|
fast.mainImp(nodep, false);
|
|
}
|
|
}
|
|
|
|
// Emit trace routines (currently they can only exist in the top module)
|
|
if (v3Global.opt.trace()) {
|
|
EmitCTrace::main(v3Global.rootp()->topModulep(), /* slow: */ true);
|
|
EmitCTrace::main(v3Global.rootp()->topModulep(), /* slow: */ false);
|
|
}
|
|
}
|
|
|
|
void V3EmitC::emitcFiles() {
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
|
for (AstNodeFile* filep = v3Global.rootp()->filesp(); filep;
|
|
filep = VN_CAST(filep->nextp(), NodeFile)) {
|
|
AstCFile* cfilep = VN_CAST(filep, CFile);
|
|
if (cfilep && cfilep->tblockp()) {
|
|
V3OutCFile of(cfilep->name());
|
|
of.puts("// DESCR"
|
|
"IPTION: Verilator generated C++\n");
|
|
EmitCFunc visitor(cfilep->tblockp(), &of, true);
|
|
}
|
|
}
|
|
}
|