verilator/src/V3EmitC.cpp

1612 lines
66 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Emit C++ for tree
//
2019-11-08 04:33:59 +01:00
// Code available from: https://verilator.org
//
//*************************************************************************
//
2021-01-01 16:29:54 +01:00
// 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 (m_fileModp->isTop() && !source) {
ofp->puts("// DESCR"
"IPTION: Verilator output: Primary design header\n");
ofp->puts("//\n");
ofp->puts("// This header should be included by all source files instantiating the "
"design.\n");
ofp->puts("// The class here is then constructed to instantiate the design.\n");
ofp->puts("// See the Verilator manual for examples.\n");
2020-10-18 19:23:02 +02:00
} else {
if (source) {
ofp->puts("// DESCR"
"IPTION: Verilator output: Design implementation internals\n");
} else {
ofp->puts("// DESCR"
"IPTION: Verilator output: Design internal header\n");
2020-10-18 19:23:02 +02:00
}
ofp->puts("// See " + v3Global.opt.prefix() + ".h for the primary calling header\n");
2020-10-18 19:23:02 +02:00
}
return ofp;
2020-10-18 19:23:02 +02:00
}
//---------------------------------------
// VISITORS
using EmitCFunc::visit; // Suppress hidden overloaded virtual function warning
virtual void visit(AstCFunc* nodep) override {
// TRACE_* and DPI handled elsewhere
if (nodep->funcType().isTrace()) return;
if (nodep->dpiImportPrototype()) 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 emitSensitives();
// 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 emitSettleLoop(bool initial);
void emitWrapEval();
void emitWrapFast();
void emitThreadingState();
void emitThreadingCtors(bool* firstp);
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::emitThreadingCtors(bool* firstp) {
ofp()->indentInc();
emitCtorSep(firstp);
puts("__Vm_threadPoolp(nullptr)");
emitCtorSep(firstp);
puts("__Vm_even_cycle(false)");
if (v3Global.opt.profThreads()) {
emitCtorSep(firstp);
puts("__Vm_profile_cycle_start(0)");
}
ofp()->indentDec();
}
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 if (optSystemC() && modp->isTop()) {
puts(modName + "::" + modName + "(sc_module_name)");
} else if (modp->isTop()) {
puts(modName + "::" + modName
+ "(VerilatedContext* _vcontextp__, const char* _vcname__)\n");
puts(" : VerilatedModule{_vcname__}\n");
2021-02-21 18:12:11 +01:00
first = false; // printed the first ':'
} else {
puts(modName + "::" + modName + "(const char* _vcname__)\n");
puts(" : VerilatedModule(_vcname__)\n");
first = false; // printed the first ':'
}
emitVarCtors(&first);
if (modp->isTop() && v3Global.opt.mtasks()) emitThreadingCtors(&first);
puts(" {\n");
if (modp->isTop()) {
putsDecoration("// Create Sym instance\n");
// Must be before other constructors, as __vlCoverInsert calls it.
// Note _vcontextp__ may be nullptr, VerilatedSyms::VerilatedSyms cleans it up
puts(EmitCBaseVisitor::symClassVar() + " = new " + symClassName() + "("
+ (optSystemC() ? "nullptr" : "_vcontextp__") + ", this, name());\n");
}
emitSensitives();
putsDecoration("// Reset structure values\n");
puts(modName + "__" + protect("_ctor_var_reset") + "(this);\n");
emitTextSection(AstType::atScCtor);
if (modp->isTop() && v3Global.opt.mtasks()) {
// TODO-- For now each top module creates its own ThreadPool here,
// and deletes it in the destructor. If A and B are each top level
// modules, each creates a separate thread pool. This allows
// A.eval() and B.eval() to run concurrently without any
// interference -- so long as the physical machine has enough cores
// to support both pools and all testbench threads.
//
// In the future, we might want to let the client provide a
// threadpool to the constructor. This would allow two or more
// models to share a single threadpool.
//
// For example: suppose models A and B are each compiled to run on
// 4 threads. The client might create a single thread pool with 3
2019-09-09 13:50:21 +02:00
// threads and pass it to both models. If the client can ensure that
// A.eval() and B.eval() do NOT run concurrently, there will be no
// contention for the threads. This mode is missing for now. (Is
// there demand for such a setup?)
puts("__Vm_threadPoolp = new VlThreadPool("
// Note we create N-1 threads in the thread pool. The thread
// that calls eval() becomes the final Nth thread for the
// duration of the eval call.
+ string("vlSymsp->_vm_contextp__, ") + cvtToStr(v3Global.opt.threads() - 1) + ", "
+ cvtToStr(v3Global.opt.profThreads()) + ");\n");
if (v3Global.opt.profThreads()) {
puts("__Vm_profile_cycle_start = 0;\n");
puts("__Vm_profile_time_finished = 0;\n");
puts("__Vm_profile_window_ct = 0;");
}
}
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");
2020-03-02 03:39:23 +01:00
if (modp->isTop()) {
2020-04-24 03:22:47 +02:00
if (v3Global.opt.mtasks()) {
puts("VL_DO_CLEAR(delete __Vm_threadPoolp, __Vm_threadPoolp = nullptr);\n");
2020-04-24 03:22:47 +02:00
}
2020-03-02 03:39:23 +01:00
// Call via function in __Trace.cpp as this .cpp file does not have trace header
if (v3Global.needTraceDumper()) {
puts("#ifdef VM_TRACE\n");
puts("if (VL_UNLIKELY(vlSymsp->__Vm_dumping)) _traceDumpClose();\n");
2020-03-02 03:39:23 +01:00
puts("#endif // VM_TRACE\n");
}
}
emitTextSection(AstType::atScDtor);
if (modp->isTop()) puts("VL_DO_CLEAR(delete vlSymsp, vlSymsp = nullptr);\n");
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");
2020-01-25 02:10:44 +01:00
// 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);
2020-10-17 20:21:27 +02:00
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);
2020-10-17 20:21:27 +02:00
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");
2020-10-17 20:21:27 +02:00
for (int v = 0; v < vects; ++v) puts("}\n");
}
}
}
if (modp->isTop()) { // Save the children
puts("vlSymsp->" + protect(funcname) + "(os);\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::emitSensitives() {
// Create sensitivity list for when to evaluate the model.
// If C++ code, the user must call this routine themself.
if (m_modp->isTop() && optSystemC()) {
putsDecoration("// Sensitivities on all clocks and combo inputs\n");
puts("SC_METHOD(eval);\n");
for (AstNode* nodep = m_modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* varp = VN_CAST(nodep, Var)) {
if (varp->isNonOutput() && (varp->isScSensitive() || varp->isUsedClock())) {
int vects = 0;
// This isn't very robust and may need cleanup for other data types
for (AstUnpackArrayDType* arrayp
= VN_CAST(varp->dtypeSkipRefp(), UnpackArrayDType);
arrayp;
arrayp = VN_CAST(arrayp->subDTypep()->skipRefp(), 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(arrayp->lo()));
puts("; " + ivar + "<=" + cvtToStr(arrayp->hi()));
puts("; ++" + ivar + ") {\n");
}
puts("sensitive << " + varp->nameProtect());
for (int v = 0; v < vects; ++v) puts("[__Vi" + cvtToStr(v) + "]");
puts(";\n");
2020-10-17 20:21:27 +02:00
for (int v = 0; v < vects; ++v) puts("}\n");
}
}
}
puts("\n");
}
}
void EmitCImp::emitSettleLoop(bool initial) {
const string self = initial ? "vlSelf" : "this";
2018-03-10 18:52:11 +01:00
putsDecoration("// Evaluate till stable\n");
puts("int __VclockLoop = 0;\n");
puts("QData __Vchange = 1;\n");
if (v3Global.opt.trace()) puts("vlSymsp->__Vm_activity = true;\n");
2018-03-10 18:52:11 +01:00
puts("do {\n");
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ ");
puts(initial ? "Initial" : "Clock");
puts(" loop\\n\"););\n");
if (initial) puts(topClassName() + "__" + protect("_eval_settle") + "(" + self + ");\n");
puts(topClassName() + "__" + protect("_eval") + "(" + self + ");\n");
puts("if (VL_UNLIKELY(++__VclockLoop > " + cvtToStr(v3Global.opt.convergeLimit()) + ")) {\n");
puts("// About to fail, so enable debug to see what's not settling.\n");
puts("// Note you must run make with OPT=-DVL_DEBUG for debug prints.\n");
puts("int __Vsaved_debug = Verilated::debug();\n");
puts("Verilated::debug(1);\n");
puts("__Vchange = " + topClassName() + "__" + protect("_change_request") + "(" + self
+ ");\n");
puts("Verilated::debug(__Vsaved_debug);\n");
puts("VL_FATAL_MT(");
putsQuoted(protect(m_modp->fileline()->filename()));
puts(", ");
puts(cvtToStr(m_modp->fileline()->lineno()));
puts(", \"\",\n");
puts("\"Verilated model didn't ");
if (initial) puts("DC ");
puts("converge\\n\"\n");
2021-04-18 17:52:29 +02:00
puts("\"- See https://verilator.org/warn/DIDNOTCONVERGE\");\n");
puts("} else {\n");
puts("__Vchange = " + topClassName() + "__" + protect("_change_request") + "(" + self
+ ");\n");
puts("}\n");
2018-03-10 18:52:11 +01:00
puts("} while (VL_UNLIKELY(__Vchange));\n");
}
void EmitCImp::emitWrapFast() {
UASSERT_OBJ(m_modp->isTop(), m_modp, "Attempting to emitWrapFast for non-top class");
puts("\nVerilatedContext* " + topClassName() + "::contextp() const {\n");
puts(/**/ "return vlSymsp->_vm_contextp__;\n");
puts("}\n");
}
void EmitCImp::emitWrapEval() {
UASSERT_OBJ(m_modp->isTop(), m_modp, "Attempting to emitWrapEval for non-top class");
const string selfDecl = "(" + topClassName() + "* vlSelf)";
// Forward declarations
puts("\n");
m_lazyDecls.emit("void " + topClassName() + "__", protect("_eval_initial"), selfDecl + ";");
m_lazyDecls.emit("void " + topClassName() + "__", protect("_eval_settle"), selfDecl + ";");
m_lazyDecls.emit("void " + topClassName() + "__", protect("_eval"), selfDecl + ";");
m_lazyDecls.emit("QData " + topClassName() + "__", protect("_change_request"), selfDecl + ";");
puts("#ifdef VL_DEBUG\n");
m_lazyDecls.emit("void " + topClassName() + "__", protect("_eval_debug_assertions"),
selfDecl + ";");
puts("#endif // VL_DEBUG\n");
m_lazyDecls.emit("void " + topClassName() + "__", protect("_final"), selfDecl + ";");
// _eval_initial_loop
puts("\nstatic void " + protect("_eval_initial_loop") + selfDecl + " {\n");
puts(symClassAssign());
puts("vlSymsp->__Vm_didInit = true;\n");
puts(topClassName() + "__" + protect("_eval_initial") + "(vlSelf);\n");
emitSettleLoop(/* initial: */ true);
ensureNewLine();
puts("}\n");
// ::eval_step
puts("\nvoid " + topClassName() + "::eval_step() {\n");
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+++++TOP Evaluate " + topClassName()
+ "::eval_step\\n\"); );\n");
puts("#ifdef VL_DEBUG\n");
putsDecoration("// Debug assertions\n");
puts(topClassName() + "__" + protect("_eval_debug_assertions") + "(this);\n");
2019-07-30 03:07:37 +02:00
puts("#endif // VL_DEBUG\n");
putsDecoration("// Initialize\n");
puts("if (VL_UNLIKELY(!vlSymsp->__Vm_didInit)) " + protect("_eval_initial_loop")
+ "(this);\n");
if (v3Global.opt.threads() == 1) {
uint32_t mtaskId = 0;
putsDecoration("// MTask " + cvtToStr(mtaskId) + " start\n");
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"MTask" + cvtToStr(mtaskId) + " starting\\n\"););\n");
puts("Verilated::mtaskId(" + cvtToStr(mtaskId) + ");\n");
}
if (v3Global.opt.mtasks() && v3Global.opt.profThreads()) {
puts("if (VL_UNLIKELY((vlSymsp->_vm_contextp__->profThreadsStart() != "
"__Vm_profile_time_finished)\n");
puts(" && (VL_TIME_Q() > vlSymsp->_vm_contextp__->profThreadsStart())\n");
puts(" && (vlSymsp->_vm_contextp__->profThreadsWindow() >= 1))) {\n");
// Within a profile (either starting, middle, or end)
puts("if (__Vm_profile_window_ct == 0) {\n"); // Opening file?
// Start profile on this cycle. We'll capture a window worth, then
// only analyze the next window worth. The idea is that the first window
// capture will hit some cache-cold stuff (eg printf) but it'll be warm
// by the time we hit the second window, we hope.
puts("__Vm_profile_cycle_start = VL_RDTSC_Q();\n");
// "* 2" as first half is warmup, second half is collection
puts("__Vm_profile_window_ct = vlSymsp->_vm_contextp__->profThreadsWindow() * 2 "
"+ "
"1;\n");
puts("}\n");
puts("--__Vm_profile_window_ct;\n");
puts("if (__Vm_profile_window_ct == vlSymsp->_vm_contextp__->profThreadsWindow()) "
"{\n");
// This barrier record in every threads' profile demarcates the
// cache-warm-up cycles before the barrier from the actual profile
// cycles afterward.
puts("__Vm_threadPoolp->profileAppendAll(");
puts("VlProfileRec(VlProfileRec::Barrier()));\n");
puts("__Vm_profile_cycle_start = VL_RDTSC_Q();\n");
puts("}\n");
puts("else if (__Vm_profile_window_ct == 0) {\n");
// Ending file.
puts("vluint64_t elapsed = VL_RDTSC_Q() - __Vm_profile_cycle_start;\n");
puts("__Vm_threadPoolp->profileDump(vlSymsp->_vm_contextp__->profThreadsFilename()."
"c_str(), elapsed);\n");
// This turns off the test to enter the profiling code, but still
// allows the user to collect another profile by changing
// profThreadsStart
puts("__Vm_profile_time_finished = vlSymsp->_vm_contextp__->profThreadsStart();\n");
puts("__Vm_profile_cycle_start = 0;\n");
puts("}\n");
puts("}\n");
}
emitSettleLoop(/* initial: */ false);
if (v3Global.opt.threads() == 1) {
puts("Verilated::endOfThreadMTask(vlSymsp->__Vm_evalMsgQp);\n");
}
if (v3Global.opt.threads()) puts("Verilated::endOfEval(vlSymsp->__Vm_evalMsgQp);\n");
puts("}\n");
splitSizeInc(10);
// ::eval_end_step
2020-03-02 03:39:23 +01:00
if (v3Global.needTraceDumper() && !optSystemC()) {
puts("\nvoid " + topClassName() + "::eval_end_step() {\n");
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+eval_end_step " + topClassName()
2020-03-02 03:39:23 +01:00
+ "::eval_end_step\\n\"); );\n");
puts("#ifdef VM_TRACE\n");
putsDecoration("// Tracing\n");
// SystemC's eval loop deals with calling trace, not us
puts("if (VL_UNLIKELY(vlSymsp->__Vm_dumping)) _traceDump();\n");
2020-03-02 03:39:23 +01:00
puts("#endif // VM_TRACE\n");
puts("}\n");
splitSizeInc(10);
2020-03-02 03:39:23 +01:00
}
// ::final
puts("\nvoid " + topClassName() + "::final() {\n");
puts(topClassName() + "__" + protect("_final") + "(this);\n");
puts("}\n");
splitSizeInc(10);
}
void EmitCImp::emitThreadingState() {
ofp()->putsPrivate(false); // Accessed from loose function
AstExecGraph* execGraphp = v3Global.rootp()->execGraphp();
UASSERT_OBJ(execGraphp, v3Global.rootp(), "Root should have an execGraphp");
puts("VlThreadPool* __Vm_threadPoolp;\n");
puts("bool __Vm_even_cycle;\n");
if (v3Global.opt.profThreads()) {
// rdtsc() at current cycle start
puts("vluint64_t __Vm_profile_cycle_start;\n");
// Time we finished analysis
puts("vluint64_t __Vm_profile_time_finished;\n");
// Track our position in the cache warmup and actual profile window
puts("vluint32_t __Vm_profile_window_ct;\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();
2010-01-17 21:10:37 +01:00
if (v3Global.needHeavy()) {
puts("#include \"verilated_heavy.h\"\n");
2010-01-17 21:10:37 +01:00
} else {
puts("#include \"verilated.h\"\n");
2010-01-17 21:10:37 +01:00
}
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");
if (v3Global.dpi() && modp->isTop()) {
// do this before including our main .h file so that any references to
// types defined in svdpi.h are available
puts("#include \"svdpi.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));
2020-08-24 01:37:56 +02:00
if (classp->extendsp())
puts(" : public " + prefixNameProtect(classp->extendsp()->classp()));
puts(" {\n");
} else if (optSystemC() && modp->isTop()) {
puts("SC_MODULE(" + prefixNameProtect(modp) + ") {\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");
if (modp->isTop()) {
puts("// Public to allow access to /*verilator_public*/ items;\n");
puts("// otherwise the application code can consider these internals.\n");
}
}
puts(prefixNameProtect(cellp->modp()) + "* " + cellp->nameProtect() + ";\n");
}
}
}
2014-11-07 13:50:11 +01:00
emitTypedefs(modp->stmtsp());
2020-02-02 02:11:21 +01:00
string section;
section = "\n// PORTS\n";
if (modp->isTop()) {
section += ("// The application code writes and reads these signals to\n"
"// propagate new values into/out from the Verilated model.\n");
}
emitVarList(modp->stmtsp(), EVL_CLASS_IO, "", section /*ref*/);
2020-02-02 02:11:21 +01:00
section = "\n// LOCAL SIGNALS\n";
if (modp->isTop()) section += "// Internals; generally not touched by application code\n";
emitVarList(modp->stmtsp(), EVL_CLASS_SIG, "", section /*ref*/);
2020-02-02 02:11:21 +01:00
section = "\n// LOCAL VARIABLES\n";
if (modp->isTop()) section += "// Internals; generally not touched by application code\n";
emitVarList(modp->stmtsp(), EVL_CLASS_TEMP, "", section /*ref*/);
puts("\n// INTERNAL VARIABLES\n");
if (modp->isTop()) puts("// Internals; generally not touched by application code\n");
2020-04-07 18:17:48 +02:00
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");
2020-04-07 18:17:48 +02:00
}
ofp()->putsPrivate(false); // public:
2020-03-02 03:39:23 +01:00
if (modp->isTop()) {
if (v3Global.opt.mtasks()) emitThreadingState();
}
emitCoverageDecl(modp); // may flip public/private
2020-02-02 02:11:21 +01:00
section = "\n// PARAMETERS\n";
if (modp->isTop())
section += "// Parameters marked /*verilator public*/ for use by application code\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 if (optSystemC() && modp->isTop()) {
ofp()->putsPrivate(false); // public:
puts("SC_CTOR(" + prefixNameProtect(modp) + ");\n");
puts("virtual ~" + prefixNameProtect(modp) + "();\n");
} else if (optSystemC()) {
ofp()->putsPrivate(false); // public:
2021-02-21 18:12:11 +01:00
puts(prefixNameProtect(modp) + "(const char* __VCname = \"\");\n");
puts("~" + prefixNameProtect(modp) + "();\n");
} else {
ofp()->putsPrivate(false); // public:
if (modp->isTop()) {
puts("/// Construct the model; called by application code\n");
puts("/// If contextp is null, then the model will use the default global context\n");
puts("/// If name is \"\", then makes a wrapper with a\n");
puts("/// single model invisible with respect to DPI scope names.\n");
puts(prefixNameProtect(modp) + "(VerilatedContext* contextp,"
+ " const char* name = \"TOP\");\n");
puts(prefixNameProtect(modp) + "(const char* name = \"TOP\")\n");
puts(" : " + prefixNameProtect(modp) + "(nullptr, name) {}\n");
} else {
if (VN_IS(modp, Class)) {
// TODO move all constructor definition to e.g. V3CUse
puts(prefixNameProtect(modp) + "();\n");
} else {
puts(prefixNameProtect(modp) + "(const char* name = \"TOP\");\n");
}
}
if (modp->isTop()) {
puts("/// Destroy the model; called (often implicitly) by application code\n");
}
puts("~" + prefixNameProtect(modp) + "();\n");
}
if (v3Global.opt.trace() && modp->isTop()) {
puts("/// Trace signals in the model; called by application code\n");
puts("void trace(" + v3Global.opt.traceClassBase()
+ "C* tfp, int levels, int options = 0);\n");
if (optSystemC()) {
puts("/// SC tracing; avoid overloaded virtual function lint warning\n");
puts("virtual void trace(sc_trace_file* tfp) const override { "
"::sc_core::sc_module::trace(tfp); }\n");
}
}
emitTextSection(AstType::atScInt);
if (modp->isTop()) {
puts("\n// API METHODS\n");
puts("/// Return current simulation context for this model.\n");
puts("/// Used to get to e.g. simulation time via contextp()->time()\n");
puts("VerilatedContext* contextp() const;\n");
2020-03-02 03:39:23 +01:00
string callEvalEndStep
= (v3Global.needTraceDumper() && !optSystemC()) ? "eval_end_step(); " : "";
if (optSystemC()) {
ofp()->putsPrivate(true); ///< eval() is invoked by our sensitive() calls.
}
if (!optSystemC()) {
puts("/// Evaluate the model. Application must call when inputs change.\n");
}
2020-03-02 03:39:23 +01:00
puts("void eval() { eval_step(); " + callEvalEndStep + "}\n");
if (!optSystemC()) {
puts("/// Evaluate when calling multiple units/models per time step.\n");
}
2020-03-02 03:39:23 +01:00
puts("void eval_step();\n");
if (!optSystemC()) {
puts("/// Evaluate at end of a timestep for tracing, when using eval_step().\n");
puts("/// Application must call after all eval() and before time changes.\n");
puts("void eval_end_step()");
if (callEvalEndStep == "") {
puts(" {}\n");
} else {
puts(";\n");
}
2020-03-02 03:39:23 +01:00
}
ofp()->putsPrivate(false); // public:
if (!optSystemC()) {
puts("/// Simulation complete, run final blocks. Application "
"must call on completion.\n");
}
puts("void final();\n");
}
puts("\n// INTERNAL METHODS\n");
if (modp->isTop()) {
ofp()->putsPrivate(false); // public: as accessed by loose functions
if (v3Global.needTraceDumper()) {
2020-11-10 00:29:09 +01:00
if (!optSystemC()) puts("void _traceDump();\n");
puts("void _traceDumpOpen();\n");
puts("void _traceDumpClose();\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");
}
2020-02-02 02:11:21 +01:00
puts("}");
if (!VN_IS(modp, Class)) puts(" VL_ATTR_ALIGNED(VL_CACHE_LINE_BYTES)");
2020-02-02 02:11:21 +01:00
puts(";\n");
puts("\n//----------\n\n");
emitIntFuncDecls(modp, false);
// Save/restore
if (v3Global.opt.savable() && modp->isTop()) {
puts("\n");
puts("inline VerilatedSerialize& operator<<(VerilatedSerialize& os, "
+ prefixNameProtect(modp) + "& rhs) {\n" //
+ "Verilated::quiesce(); rhs." + protect("__Vserialize") + "(os); return os; }\n");
puts("inline VerilatedDeserialize& operator>>(VerilatedDeserialize& os, "
+ prefixNameProtect(modp) + "& rhs) {\n" //
+ "Verilated::quiesce(); rhs." + protect("__Vdeserialize") + "(os); return os; }\n");
}
}
//----------------------------------------------------------------------
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) {
2020-02-02 02:11:21 +01:00
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);
if (modp->isTop()) {
emitWrapFast();
emitWrapEval();
}
}
// 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
AstCFunc* m_cfuncp = nullptr; // Function we're in now
bool m_slow; // Making slow file
int m_enumNum = 0; // Enumeration number (whole netlist)
int m_baseCode = -1; // Code of first AstTraceInc in this function
// 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");
emitTraceHeader();
}
void emitTraceHeader() {
// Includes
2020-03-02 03:39:23 +01:00
puts("#include \"" + v3Global.opt.traceSourceLang() + ".h\"\n");
puts("#include \"" + symClassName() + ".h\"\n");
puts("\n");
}
void emitTraceSlow() {
puts("\n//======================\n");
2020-03-02 03:39:23 +01:00
if (v3Global.needTraceDumper() && !optSystemC()) {
puts("\nvoid " + topClassName() + "::_traceDump() {\n");
// Caller checked for __Vm_dumperp non-nullptr
puts("const VerilatedLockGuard lock(vlSymsp->__Vm_dumperMutex);\n");
puts("vlSymsp->__Vm_dumperp->dump(VL_TIME_Q());\n");
2020-03-02 03:39:23 +01:00
puts("}\n");
splitSizeInc(10);
}
if (v3Global.needTraceDumper()) {
puts("\nvoid " + topClassName() + "::_traceDumpOpen() {\n");
puts("const VerilatedLockGuard lock(vlSymsp->__Vm_dumperMutex);\n");
puts("if (VL_UNLIKELY(!vlSymsp->__Vm_dumperp)) {\n");
puts("vlSymsp->__Vm_dumperp = new " + v3Global.opt.traceClassLang() + "();\n");
puts("trace(vlSymsp->__Vm_dumperp, 0, 0);\n");
puts("std::string dumpfile = vlSymsp->_vm_contextp__->dumpfileCheck();\n");
puts("vlSymsp->__Vm_dumperp->open(dumpfile.c_str());\n");
puts("vlSymsp->__Vm_dumping = true;\n");
puts("}\n");
2020-03-02 03:39:23 +01:00
puts("}\n");
splitSizeInc(10);
puts("\nvoid " + topClassName() + "::_traceDumpClose() {\n");
puts("const VerilatedLockGuard lock(vlSymsp->__Vm_dumperMutex);\n");
puts("vlSymsp->__Vm_dumping = false;\n");
puts("VL_DO_CLEAR(delete vlSymsp->__Vm_dumperp, vlSymsp->__Vm_dumperp = "
"nullptr);\n");
2020-03-02 03:39:23 +01:00
puts("}\n");
splitSizeInc(10);
}
puts("\n");
m_lazyDecls.emit("void " + topClassName() + "__", protect("traceInitTop"),
"(" + topClassName() + "* vlSelf, " + v3Global.opt.traceClassBase()
+ "* tracep);");
puts("\nstatic void " + protect("traceInit") + "(void* voidSelf, "
+ v3Global.opt.traceClassBase() + "* tracep, uint32_t code) {\n");
putsDecoration("// Callback from tracep->open()\n");
puts(topClassName() + "*const __restrict vlSelf = static_cast<" + topClassName()
+ "*>(voidSelf);\n");
puts("if (!vlSelf->vlSymsp->_vm_contextp__->calcUnusedSigs()) {\n");
puts("VL_FATAL_MT(__FILE__, __LINE__, __FILE__,\n");
puts(" \"Turning on wave traces requires Verilated::traceEverOn(true) call "
"before time 0.\");\n");
puts("}\n");
puts("vlSelf->vlSymsp->__Vm_baseCode = code;\n");
puts("tracep->module(vlSelf->vlSymsp->name());\n");
puts("tracep->scopeEscape(' ');\n");
puts(topClassName() + "__" + protect("traceInitTop") + "(vlSelf, tracep);\n");
puts("tracep->scopeEscape('.');\n"); // Restore so later traced files won't break
puts("}\n");
splitSizeInc(10);
puts("\n");
m_lazyDecls.emit("void " + topClassName() + "__", protect("traceRegister"),
"(" + topClassName() + "* vlSelf, " + v3Global.opt.traceClassBase()
+ "* tracep);");
puts("\nvoid " + topClassName() + "::trace(");
puts(v3Global.opt.traceClassBase() + "C* tfp, int, int) {\n");
puts("tfp->spTrace()->addInitCb(&" + protect("traceInit") + ", this);\n");
puts(topClassName() + "__" + protect("traceRegister") + "(this, tfp->spTrace());\n");
puts("}\n");
splitSizeInc(10);
puts("\n//======================\n\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
2019-05-02 01:18:45 +02:00
// 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");
Improve tracing performance. (#2257) * Improve tracing performance. Various tactics used to improve performance of both VCD and FST tracing: - Both: Change tracing functions to templates to take variable widths as template parameters. For VCD, subsequently specialize these to the values used by Verilator. This avoids redundant instructions and hard to predict branches. - Both: Check for value changes via direct pointer access into the previous signal value buffer. This eliminates a lot of simple pointer arithmetic instructions form the tracing code. - Both: Verilator provides clean input, no need to mask out used bits. - VCD: pre-compute identifier codes and use memory copy instead of re-computing them every time a code is emitted. This saves a lot of instructions and hard to predict branches. The added D-cache misses are cheaper than the removed branches/instructions. - VCD: re-write the routines emitting the changes to be more efficient. - FST: Use previous signal value buffer the same way as the VCD tracing code, and only call the FST API when a change is detected. Performance as measured on SweRV EH1, with the pre-canned CoreMark benchmark running from DCCM/ICCM, clang 6.0.0, Intel i7-3770 @ 3.40GHz, and IO to ramdisk: +--------------+---------------+----------------------+ | VCD | FST | FST separate thread | | (--trace) | (--trace-fst) | (--trace-fst-thread) | ------------+-----------------------------------------------------+ Before | 30.2 s | 121.1 s | 69.8 s | ============+==============+===============+======================+ After | 24.7 s | 45.7 s | 32.4 s | ------------+--------------+---------------+----------------------+ Speedup | 22 % | 256 % | 215 % | ------------+--------------+---------------+----------------------+ Rel. to VCD | 1 x | 1.85 x | 1.31 x | ------------+--------------+---------------+----------------------+ In addition, FST trace size for the above reduced by 48%.
2020-04-14 01:13:10 +02:00
} else if (nodep->declp()->widthMin() > 1) {
puts("tracep->" + func + "CData");
} else {
puts("tracep->" + func + "Bit");
emitWidth = false;
}
Improve tracing performance. (#2257) * Improve tracing performance. Various tactics used to improve performance of both VCD and FST tracing: - Both: Change tracing functions to templates to take variable widths as template parameters. For VCD, subsequently specialize these to the values used by Verilator. This avoids redundant instructions and hard to predict branches. - Both: Check for value changes via direct pointer access into the previous signal value buffer. This eliminates a lot of simple pointer arithmetic instructions form the tracing code. - Both: Verilator provides clean input, no need to mask out used bits. - VCD: pre-compute identifier codes and use memory copy instead of re-computing them every time a code is emitted. This saves a lot of instructions and hard to predict branches. The added D-cache misses are cheaper than the removed branches/instructions. - VCD: re-write the routines emitting the changes to be more efficient. - FST: Use previous signal value buffer the same way as the VCD tracing code, and only call the FST API when a change is detected. Performance as measured on SweRV EH1, with the pre-canned CoreMark benchmark running from DCCM/ICCM, clang 6.0.0, Intel i7-3770 @ 3.40GHz, and IO to ramdisk: +--------------+---------------+----------------------+ | VCD | FST | FST separate thread | | (--trace) | (--trace-fst) | (--trace-fst-thread) | ------------+-----------------------------------------------------+ Before | 30.2 s | 121.1 s | 69.8 s | ============+==============+===============+======================+ After | 24.7 s | 45.7 s | 32.4 s | ------------+--------------+---------------+----------------------+ Speedup | 22 % | 256 % | 215 % | ------------+--------------+---------------+----------------------+ Rel. to VCD | 1 x | 1.85 x | 1.31 x | ------------+--------------+---------------+----------------------+ In addition, FST trace size for the above reduced by 48%.
2020-04-14 01:13:10 +02:00
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 - m_baseCode));
puts(",");
emitTraceValue(nodep, arrayindex);
Improve tracing performance. (#2257) * Improve tracing performance. Various tactics used to improve performance of both VCD and FST tracing: - Both: Change tracing functions to templates to take variable widths as template parameters. For VCD, subsequently specialize these to the values used by Verilator. This avoids redundant instructions and hard to predict branches. - Both: Check for value changes via direct pointer access into the previous signal value buffer. This eliminates a lot of simple pointer arithmetic instructions form the tracing code. - Both: Verilator provides clean input, no need to mask out used bits. - VCD: pre-compute identifier codes and use memory copy instead of re-computing them every time a code is emitted. This saves a lot of instructions and hard to predict branches. The added D-cache misses are cheaper than the removed branches/instructions. - VCD: re-write the routines emitting the changes to be more efficient. - FST: Use previous signal value buffer the same way as the VCD tracing code, and only call the FST API when a change is detected. Performance as measured on SweRV EH1, with the pre-canned CoreMark benchmark running from DCCM/ICCM, clang 6.0.0, Intel i7-3770 @ 3.40GHz, and IO to ramdisk: +--------------+---------------+----------------------+ | VCD | FST | FST separate thread | | (--trace) | (--trace-fst) | (--trace-fst-thread) | ------------+-----------------------------------------------------+ Before | 30.2 s | 121.1 s | 69.8 s | ============+==============+===============+======================+ After | 24.7 s | 45.7 s | 32.4 s | ------------+--------------+---------------+----------------------+ Speedup | 22 % | 256 % | 215 % | ------------+--------------+---------------+----------------------+ Rel. to VCD | 1 x | 1.85 x | 1.31 x | ------------+--------------+---------------+----------------------+ In addition, FST trace size for the above reduced by 48%.
2020-04-14 01:13:10 +02:00
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(AstNetlist* nodep) override {
// Top module only
iterate(nodep->topModulep());
}
virtual void visit(AstNodeModule* nodep) override {
m_modp = nodep;
iterateChildren(nodep);
m_modp = nullptr;
}
virtual void visit(AstCFunc* nodep) override {
if (nodep->slow() != m_slow) return;
VL_RESTORER(m_cfuncp);
VL_RESTORER(m_useSelfForThis);
if (nodep->funcType().isTrace()) { // TRACE_*
m_cfuncp = nodep;
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());
}
splitSizeInc(nodep);
puts("\n");
m_lazyDecls.emit(nodep);
emitCFuncHeader(nodep, m_modp, /* withScope: */ true);
puts(" {\n");
if (nodep->isLoose()) {
m_lazyDecls.declared(nodep); // Defined here, so no longer needs declaration
if (!nodep->isStatic()) { // Standard prologue
puts("if (false && vlSelf) {} // Prevent unused\n");
m_useSelfForThis = true;
puts(symClassAssign());
}
}
if (nodep->initsp()) {
string section;
emitVarList(nodep->initsp(), EVL_FUNC_ALL, "", section /*ref*/);
iterateAndNextNull(nodep->initsp());
}
Improve tracing performance. (#2257) * Improve tracing performance. Various tactics used to improve performance of both VCD and FST tracing: - Both: Change tracing functions to templates to take variable widths as template parameters. For VCD, subsequently specialize these to the values used by Verilator. This avoids redundant instructions and hard to predict branches. - Both: Check for value changes via direct pointer access into the previous signal value buffer. This eliminates a lot of simple pointer arithmetic instructions form the tracing code. - Both: Verilator provides clean input, no need to mask out used bits. - VCD: pre-compute identifier codes and use memory copy instead of re-computing them every time a code is emitted. This saves a lot of instructions and hard to predict branches. The added D-cache misses are cheaper than the removed branches/instructions. - VCD: re-write the routines emitting the changes to be more efficient. - FST: Use previous signal value buffer the same way as the VCD tracing code, and only call the FST API when a change is detected. Performance as measured on SweRV EH1, with the pre-canned CoreMark benchmark running from DCCM/ICCM, clang 6.0.0, Intel i7-3770 @ 3.40GHz, and IO to ramdisk: +--------------+---------------+----------------------+ | VCD | FST | FST separate thread | | (--trace) | (--trace-fst) | (--trace-fst-thread) | ------------+-----------------------------------------------------+ Before | 30.2 s | 121.1 s | 69.8 s | ============+==============+===============+======================+ After | 24.7 s | 45.7 s | 32.4 s | ------------+--------------+---------------+----------------------+ Speedup | 22 % | 256 % | 215 % | ------------+--------------+---------------+----------------------+ Rel. to VCD | 1 x | 1.85 x | 1.31 x | ------------+--------------+---------------+----------------------+ In addition, FST trace size for the above reduced by 48%.
2020-04-14 01:13:10 +02:00
m_baseCode = -1;
if (nodep->funcType() == AstCFuncType::TRACE_CHANGE_SUB) {
const AstNode* const stmtp = nodep->stmtsp();
const AstIf* const ifp = VN_CAST_CONST(stmtp, If);
const AstTraceInc* const tracep
= VN_CAST_CONST(ifp ? ifp->ifsp() : stmtp, TraceInc);
// On rare occasions we can end up with an empty sub function
m_baseCode = tracep ? tracep->declp()->code() : 0;
if (v3Global.opt.trueTraceThreads()) {
puts("const vluint32_t base = vlSymsp->__Vm_baseCode + " + cvtToStr(m_baseCode)
+ ";\n");
puts("if (false && tracep && base) {} // Prevent unused\n");
} else {
puts("vluint32_t* const oldp = tracep->oldp(vlSymsp->__Vm_baseCode + "
+ cvtToStr(m_baseCode) + ");\n");
puts("if (false && oldp) {} // Prevent unused\n");
}
} else if (nodep->funcType() == AstCFuncType::TRACE_FULL_SUB) {
m_baseCode = 0;
puts("vluint32_t* const oldp = tracep->oldp(vlSymsp->__Vm_baseCode);\n");
puts("if (false && oldp) {} // Prevent unused\n");
Improve tracing performance. (#2257) * Improve tracing performance. Various tactics used to improve performance of both VCD and FST tracing: - Both: Change tracing functions to templates to take variable widths as template parameters. For VCD, subsequently specialize these to the values used by Verilator. This avoids redundant instructions and hard to predict branches. - Both: Check for value changes via direct pointer access into the previous signal value buffer. This eliminates a lot of simple pointer arithmetic instructions form the tracing code. - Both: Verilator provides clean input, no need to mask out used bits. - VCD: pre-compute identifier codes and use memory copy instead of re-computing them every time a code is emitted. This saves a lot of instructions and hard to predict branches. The added D-cache misses are cheaper than the removed branches/instructions. - VCD: re-write the routines emitting the changes to be more efficient. - FST: Use previous signal value buffer the same way as the VCD tracing code, and only call the FST API when a change is detected. Performance as measured on SweRV EH1, with the pre-canned CoreMark benchmark running from DCCM/ICCM, clang 6.0.0, Intel i7-3770 @ 3.40GHz, and IO to ramdisk: +--------------+---------------+----------------------+ | VCD | FST | FST separate thread | | (--trace) | (--trace-fst) | (--trace-fst-thread) | ------------+-----------------------------------------------------+ Before | 30.2 s | 121.1 s | 69.8 s | ============+==============+===============+======================+ After | 24.7 s | 45.7 s | 32.4 s | ------------+--------------+---------------+----------------------+ Speedup | 22 % | 256 % | 215 % | ------------+--------------+---------------+----------------------+ Rel. to VCD | 1 x | 1.85 x | 1.31 x | ------------+--------------+---------------+----------------------+ In addition, FST trace size for the above reduced by 48%.
2020-04-14 01:13:10 +02:00
} else if (nodep->funcType() == AstCFuncType::TRACE_INIT_SUB) {
puts("const int c = vlSymsp->__Vm_baseCode;\n");
puts("if (false && tracep && c) {} // Prevent unused\n");
Improve tracing performance. (#2257) * Improve tracing performance. Various tactics used to improve performance of both VCD and FST tracing: - Both: Change tracing functions to templates to take variable widths as template parameters. For VCD, subsequently specialize these to the values used by Verilator. This avoids redundant instructions and hard to predict branches. - Both: Check for value changes via direct pointer access into the previous signal value buffer. This eliminates a lot of simple pointer arithmetic instructions form the tracing code. - Both: Verilator provides clean input, no need to mask out used bits. - VCD: pre-compute identifier codes and use memory copy instead of re-computing them every time a code is emitted. This saves a lot of instructions and hard to predict branches. The added D-cache misses are cheaper than the removed branches/instructions. - VCD: re-write the routines emitting the changes to be more efficient. - FST: Use previous signal value buffer the same way as the VCD tracing code, and only call the FST API when a change is detected. Performance as measured on SweRV EH1, with the pre-canned CoreMark benchmark running from DCCM/ICCM, clang 6.0.0, Intel i7-3770 @ 3.40GHz, and IO to ramdisk: +--------------+---------------+----------------------+ | VCD | FST | FST separate thread | | (--trace) | (--trace-fst) | (--trace-fst-thread) | ------------+-----------------------------------------------------+ Before | 30.2 s | 121.1 s | 69.8 s | ============+==============+===============+======================+ After | 24.7 s | 45.7 s | 32.4 s | ------------+--------------+---------------+----------------------+ Speedup | 22 % | 256 % | 215 % | ------------+--------------+---------------+----------------------+ Rel. to VCD | 1 x | 1.85 x | 1.31 x | ------------+--------------+---------------+----------------------+ In addition, FST trace size for the above reduced by 48%.
2020-04-14 01:13:10 +02:00
}
2020-02-02 02:11:21 +01:00
if (nodep->stmtsp()) {
putsDecoration("// Body\n");
puts("{\n");
iterateAndNextNull(nodep->stmtsp());
puts("}\n");
}
if (nodep->finalsp()) {
putsDecoration("// Final\n");
iterateAndNextNull(nodep->finalsp());
}
puts("}\n");
}
}
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);
}
}
virtual void visit(AstCoverDecl* nodep) override {}
virtual void visit(AstCoverInc* nodep) override {}
public:
explicit EmitCTrace(bool slow)
: m_slow{slow} {}
virtual ~EmitCTrace() override = default;
void main() {
// Put out the file
newOutCFile(0);
if (m_slow) emitTraceSlow();
iterate(v3Global.rootp());
VL_DO_CLEAR(delete m_ofp, m_ofp = nullptr);
}
};
//######################################################################
// EmitC class functions
static void setParentClassPointers() {
// Set user4p in all CFunc and Var to point to the containing AstNodeModule
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());
}
void V3EmitC::emitc() {
UINFO(2, __FUNCTION__ << ": " << endl);
// Set user4 to parent module
AstUser4InUse user4InUse;
setParentClassPointers();
// 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);
}
}
}
void V3EmitC::emitcTrace() {
UINFO(2, __FUNCTION__ << ": " << endl);
if (v3Global.opt.trace()) {
// Set user4 to parent module
AstUser4InUse user4InUse;
setParentClassPointers();
{
EmitCTrace slow(true);
slow.main();
}
{
EmitCTrace fast(false);
fast.main();
}
}
}
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);
}
}
}