verilator/src/V3EmitCImp.cpp

941 lines
38 KiB
C++
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Emit C++ for tree
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
2025-01-01 14:30:25 +01:00
// Copyright 2003-2025 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 "V3PchAstMT.h"
#include "V3EmitC.h"
#include "V3EmitCFunc.h"
#include "V3ThreadPool.h"
#include "V3UniqueNames.h"
#include <map>
#include <set>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Internal EmitC implementation
class EmitCImp final : EmitCFunc {
// MEMBERS
const AstNodeModule* const m_fileModp; // Files names/headers constructed using this module
const bool m_slow; // Creating __Slow file
size_t m_nSplitFiles = 0; // Sequence number for file splitting
std::deque<AstCFile*>& m_cfilesr; // cfiles generated by this emit
// METHODS
void openNextOutputFile(bool canBeSplit) {
UASSERT(!ofp(), "Output file already open");
splitSizeReset(); // Reset file size tracking
m_lazyDecls.reset(); // Need to emit new lazy declarations
AstCFile* filep = nullptr;
V3OutCFile* ofilep = 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 std::string filename = VL_DEV_NULL;
filep = createCFile(filename, /* slow: */ m_slow, /* source: */ true);
ofilep = new V3OutCFile{filename};
} else {
std::string filename = v3Global.opt.makeDir();
filename += "/" + EmitCUtil::prefixNameProtect(m_fileModp);
if (canBeSplit) filename += "__" + std::to_string(m_nSplitFiles++);
if (m_slow) filename += "__Slow";
filename += ".cpp";
filep = createCFile(filename, /* slow: */ m_slow, /* source: */ true);
ofilep = v3Global.opt.systemC() ? new V3OutScFile{filename} : new V3OutCFile{filename};
}
m_cfilesr.push_back(filep);
setOutputFile(ofilep, filep);
putsHeader();
puts("// DESCRIPTION: Verilator output: Design implementation internals\n");
puts("// See " + EmitCUtil::topClassName() + ".h for the primary calling header\n");
puts("\n");
puts("#include \"" + EmitCUtil::pchClassName() + ".h\"\n");
emitSystemCSection(m_modp, VSystemCSectionType::IMP_HDR);
}
void emitStaticVarDefns(const AstNodeModule* modp) {
// Emit static variable definitions
const string modName = EmitCUtil::prefixNameProtect(modp);
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* const varp = VN_CAST(nodep, Var)) {
if (varp->isStatic()) {
putns(varp, varp->vlArgType(true, false, false, modName));
puts(";\n");
}
}
}
}
void emitParamDefns(const AstNodeModule* modp) {
const string modName = EmitCUtil::prefixNameProtect(modp);
bool first = true;
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* const varp = VN_CAST(nodep, Var)) {
if (varp->isParam()) {
if (first) {
puts("\n");
putsDecoration(modp, "// Parameter definitions for " + modName + "\n");
first = false;
}
UASSERT_OBJ(varp->valuep(), nodep, "No init for a param?");
// Only C++ LiteralTypes can be constexpr
const bool canBeConstexpr = varp->dtypep()->isLiteralType();
putns(varp, canBeConstexpr ? "constexpr " : "const ");
const string scopedName = modName + "::" + varp->nameProtect();
putns(varp, varp->dtypep()->cType(scopedName, false, false));
if (!canBeConstexpr) {
puts(" = ");
emitConstInit(varp->valuep());
}
puts(";\n");
}
}
}
if (!first) puts("\n");
}
void emitCtorImp(const AstNodeModule* modp) {
const string modName = EmitCUtil::prefixNameProtect(modp);
puts("\n");
m_lazyDecls.emit("void " + modName + "__", protect("_ctor_var_reset"),
"(" + modName + "* vlSelf);");
puts("\n");
putns(modp, modName + "::" + modName + "(" + EmitCUtil::symClassName()
+ "* symsp, const char* v__name)\n");
puts(" : VerilatedModule{v__name}\n");
ofp()->indentInc();
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* const varp = VN_CAST(nodep, Var)) {
if (const AstBasicDType* const dtypep
= VN_CAST(varp->dtypeSkipRefp(), BasicDType)) {
if (dtypep->keyword().isMTaskState()) {
puts(", ");
putns(varp, varp->nameProtect());
puts("(");
iterateConst(varp->valuep());
puts(")\n");
} else if (varp->isIO() && varp->isSc()) {
puts(", ");
putns(varp, varp->nameProtect());
puts("(");
putsQuoted(varp->nameProtect());
puts(")\n");
Timing support (#3363) Adds timing support to Verilator. It makes it possible to use delays, event controls within processes (not just at the start), wait statements, and forks. Building a design with those constructs requires a compiler that supports C++20 coroutines (GCC 10, Clang 5). The basic idea is to have processes and tasks with delays/event controls implemented as C++20 coroutines. This allows us to suspend and resume them at any time. There are five main runtime classes responsible for managing suspended coroutines: * `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle` with move semantics and automatic cleanup. * `VlDelayScheduler`, for coroutines suspended by delays. It resumes them at a proper simulation time. * `VlTriggerScheduler`, for coroutines suspended by event controls. It resumes them if its corresponding trigger was set. * `VlForkSync`, used for syncing `fork..join` and `fork..join_any` blocks. * `VlCoroutine`, the return type of all verilated coroutines. It allows for suspending a stack of coroutines (normally, C++ coroutines are stackless). There is a new visitor in `V3Timing.cpp` which: * scales delays according to the timescale, * simplifies intra-assignment timing controls and net delays into regular timing controls and assignments, * simplifies wait statements into loops with event controls, * marks processes and tasks with timing controls in them as suspendable, * creates delay, trigger scheduler, and fork sync variables, * transforms timing controls and fork joins into C++ awaits There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`) that integrate static scheduling with timing. This involves providing external domains for variables, so that the necessary combinational logic gets triggered after coroutine resumption, as well as statements that need to be injected into the design eval function to perform this resumption at the correct time. There is also a function that transforms forked processes into separate functions. See the comments in `verilated_timing.h`, `verilated_timing.cpp`, `V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals documentation for more details. Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
} else if (dtypep->isDelayScheduler()) {
puts(", ");
putns(varp, varp->nameProtect());
Timing support (#3363) Adds timing support to Verilator. It makes it possible to use delays, event controls within processes (not just at the start), wait statements, and forks. Building a design with those constructs requires a compiler that supports C++20 coroutines (GCC 10, Clang 5). The basic idea is to have processes and tasks with delays/event controls implemented as C++20 coroutines. This allows us to suspend and resume them at any time. There are five main runtime classes responsible for managing suspended coroutines: * `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle` with move semantics and automatic cleanup. * `VlDelayScheduler`, for coroutines suspended by delays. It resumes them at a proper simulation time. * `VlTriggerScheduler`, for coroutines suspended by event controls. It resumes them if its corresponding trigger was set. * `VlForkSync`, used for syncing `fork..join` and `fork..join_any` blocks. * `VlCoroutine`, the return type of all verilated coroutines. It allows for suspending a stack of coroutines (normally, C++ coroutines are stackless). There is a new visitor in `V3Timing.cpp` which: * scales delays according to the timescale, * simplifies intra-assignment timing controls and net delays into regular timing controls and assignments, * simplifies wait statements into loops with event controls, * marks processes and tasks with timing controls in them as suspendable, * creates delay, trigger scheduler, and fork sync variables, * transforms timing controls and fork joins into C++ awaits There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`) that integrate static scheduling with timing. This involves providing external domains for variables, so that the necessary combinational logic gets triggered after coroutine resumption, as well as statements that need to be injected into the design eval function to perform this resumption at the correct time. There is also a function that transforms forked processes into separate functions. See the comments in `verilated_timing.h`, `verilated_timing.cpp`, `V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals documentation for more details. Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
puts("{*symsp->_vm_contextp__}\n");
}
}
}
}
puts(", vlSymsp{symsp}\n");
ofp()->indentDec();
puts(" {\n");
putsDecoration(modp, "// Reset structure values\n");
puts(modName + "__" + protect("_ctor_var_reset") + "(this);\n");
emitSystemCSection(modp, VSystemCSectionType::CTOR);
puts("}\n");
}
void emitConfigureImp(const AstNodeModule* modp) {
const string modName = EmitCUtil::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") + "(bool first) {\n");
puts("(void)first; // Prevent unused variable warning\n");
if (v3Global.opt.coverage()) {
puts(modName + "__" + protect("_configure_coverage") + "(this, first);\n");
}
puts("}\n");
}
void emitCoverageImp() {
// 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.
if (v3Global.opt.coverage()) {
puts("\n// Coverage\n");
puts("void " + EmitCUtil::prefixNameProtect(m_modp) + "::__vlCoverInsert(");
puts(v3Global.opt.threads() > 1 ? "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() > 1) {
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");
puts("std::string fullhier = std::string{VerilatedModule::name()} + hierp;\n");
puts("if (!fullhier.empty() && fullhier[0] == '.') fullhier = fullhier.substr(1);\n");
// Used for second++ instantiation of identical bin
puts("if (!enable) count32p = &fake_zero_count;\n");
puts("*count32p = 0;\n");
2024-02-09 03:51:36 +01:00
puts("VL_COVER_INSERT(vlSymsp->_vm_contextp__->coveragep(), VerilatedModule::name(), "
"count32p,");
puts(" \"filename\",filenamep,");
puts(" \"lineno\",lineno,");
puts(" \"column\",column,\n");
puts("\"hier\",fullhier,");
puts(" \"page\",pagep,");
puts(" \"comment\",commentp,");
puts(" (linescovp[0] ? \"linescov\" : \"\"), linescovp);\n");
puts("}\n");
}
if (v3Global.opt.coverageToggle()) {
puts("\n// Toggle Coverage\n");
puts("void " + EmitCUtil::prefixNameProtect(m_modp) + "::__vlCoverToggleInsert(");
puts("int begin, int end, bool ranged, ");
puts(v3Global.opt.threads() > 1 ? "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) {\n");
if (v3Global.opt.threads() > 1) {
puts("assert(sizeof(uint32_t) == sizeof(std::atomic<uint32_t>));\n");
}
puts("int step = (end >= begin) ? 1 : -1;\n");
// range is inclusive
puts("for (int i = begin; i != end + step; i += step) {\n");
puts("for (int j = 0; j < 2; j++) {\n");
if (v3Global.opt.threads() > 1) {
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");
puts("std::string fullhier = std::string{VerilatedModule::name()} + hierp;\n");
puts("if (!fullhier.empty() && fullhier[0] == '.') fullhier = fullhier.substr(1);\n");
puts("std::string commentWithIndex = commentp;\n");
puts("if (ranged) commentWithIndex += '[' + std::to_string(i) + ']';\n");
puts("commentWithIndex += j ? \":0->1\" : \":1->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(), VerilatedModule::name(), "
"count32p,");
puts(" \"filename\",filenamep,");
puts(" \"lineno\",lineno,");
puts(" \"column\",column,\n");
puts("\"hier\",fullhier,");
puts(" \"page\",pagep,");
puts(" \"comment\",commentWithIndex.c_str(),");
puts(" \"\", \"\");\n"); // linescov argument, but in toggle coverage it is always
// empty
puts("++countp;\n");
puts("}\n");
puts("}\n");
puts("}\n");
}
}
void emitDestructorImp(const AstNodeModule* modp) {
puts("\n");
putns(modp, EmitCUtil::prefixNameProtect(modp) + "::~" + EmitCUtil::prefixNameProtect(modp)
+ "() {\n");
emitSystemCSection(modp, VSystemCSectionType::DTOR);
puts("}\n");
}
void emitSavableImp(const 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)
putns(modp, "void " + EmitCUtil::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* const varp = VN_CAST(nodep, Var)) {
hash.insert(varp->name());
hash.insert(varp->dtypep()->width());
}
}
ofp()->printf("uint64_t __Vcheckval = 0x%" PRIx64 "ULL;\n",
static_cast<uint64_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* const 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 if (VN_IS(varp->dtypep(), NBACommitQueueDType)) {
} 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 = "__Vi"s + 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() == VBasicDTypeKwd::STRING)) {
const int vecnum = vects++;
const string ivar = "__Vi"s + cvtToStr(vecnum);
puts("for (int __Vi" + cvtToStr(vecnum) + " = " + cvtToStr(0));
puts("; " + ivar + " < " + cvtToStr(elementp->widthWords()));
puts("; ++" + ivar + ") {\n");
}
putns(varp, "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");
}
}
}
// Predicate to check if we actually need to emit anything into the common implementation file.
// Used to avoid creating empty output files.
bool hasCommonImp(const AstNodeModule* modp) const {
// Nothing to emit if no module!
if (!modp) return false;
// We always need the slow file
if (m_slow) return true;
// The fast file is only required when we have `systemc_implementation nodes
if (v3Global.hasSystemCSections()) {
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstSystemCSection* const ssp = VN_CAST(nodep, SystemCSection)) {
if (ssp->sectionType() == VSystemCSectionType::IMP) return true;
}
}
}
return false;
}
// Actually emit common implementation contents for given AstNodeModule
void doCommonImp(const AstNodeModule* modp) {
if (m_slow) {
emitStaticVarDefns(modp);
if (!VN_IS(modp, Class)) {
emitParamDefns(modp);
emitCtorImp(modp);
emitConfigureImp(modp);
emitDestructorImp(modp);
2023-03-04 01:26:15 +01:00
emitCoverageImp();
}
emitSavableImp(modp);
} else {
// From `systemc_implementation
emitSystemCSection(modp, VSystemCSectionType::IMP);
}
}
void emitCommonImp(const AstNodeModule* modp) {
const AstClass* const classp
= VN_IS(modp, ClassPackage) ? VN_AS(modp, ClassPackage)->classp() : nullptr;
if (hasCommonImp(modp) || hasCommonImp(classp)) {
openNextOutputFile(/* canBeSplit: */ false);
doCommonImp(modp);
if (classp) {
VL_RESTORER(m_modp);
m_modp = classp;
doCommonImp(classp);
}
closeOutputFile();
}
}
void emitCFuncImp(const AstNodeModule* modp) {
// Functions to be emitted here
std::vector<AstCFunc*> funcps;
const auto gather = [this, &funcps](const AstNodeModule* modp) {
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
AstCFunc* const funcp = VN_CAST(nodep, CFunc);
if (!funcp) continue;
// TRACE_* and DPI handled elsewhere
if (funcp->isTrace()) continue;
if (funcp->dpiImportPrototype()) continue;
if (funcp->dpiExportDispatcher()) continue;
if (funcp->slow() != m_slow) continue;
funcps.push_back(funcp);
}
};
gather(modp);
VL_RESTORER(m_classOrPackage);
if (const AstClassPackage* const packagep = VN_CAST(modp, ClassPackage)) {
m_classOrPackage = packagep;
gather(packagep->classp());
}
// Do not create empty files
if (funcps.empty()) return;
// Open output file
openNextOutputFile(/* canBeSplit: */ true);
// Emit all functions
for (AstCFunc* const funcp : funcps) {
VL_RESTORER(m_modp);
m_modp = EmitCParentModule::get(funcp);
iterateConst(funcp);
}
// Close output file
closeOutputFile();
}
// VISITORS
void visit(AstCFunc* nodep) override {
if (splitNeeded()) {
// Splitting file, so using parallel build.
v3Global.useParallelBuild(true);
// Close old file
closeOutputFile();
// Open a new file
openNextOutputFile(/* canBeSplit: */ true);
}
EmitCFunc::visit(nodep);
}
explicit EmitCImp(const AstNodeModule* modp, bool slow, std::deque<AstCFile*>& cfilesr)
: m_fileModp{modp}
, m_slow{slow}
, m_cfilesr{cfilesr} {
UINFO(5, " Emitting implementation of " << EmitCUtil::prefixNameProtect(modp));
m_modp = modp;
// Emit implementation of this module, if this is an AstClassPackage, then put the
// corresponding AstClass implementation in the same file as often optimizations are
// possible when both are seen by the compiler
// TODO: is the above comment still true?
// Emit implementations of common parts
emitCommonImp(modp);
// Emit implementations of all AstCFunc
emitCFuncImp(modp);
}
~EmitCImp() override = default;
public:
static void main(const AstNodeModule* modp, bool slow,
std::deque<AstCFile*>& cfilesr) VL_MT_STABLE {
EmitCImp{modp, slow, cfilesr};
}
};
//######################################################################
// Tracing routines
class EmitCTrace final : EmitCFunc {
// NODE STATE/TYPES
// None allowed to support threaded emitting
// MEMBERS
const bool m_slow; // Making slow file
int m_enumNum = 0; // Enumeration number (whole netlist)
V3UniqueNames m_uniqueNames; // For generating unique file names
std::unordered_map<AstNode*, int> m_enumNumMap; // EnumDType to enumeration number
std::deque<AstCFile*>& m_cfilesr; // cfiles generated by this emit
2023-12-19 17:07:06 +01:00
V3OutCFile* m_typesFp = nullptr; // File for type declarations
int m_traceTypeSubs = 0; // Number of trace type declaration sub-functions
int m_typeSplitSize = 0; // # of cfunc nodes placed into output file
// METHODS
void openNextOutputFile() {
UASSERT(!ofp(), "Output file already open");
splitSizeReset(); // Reset file size tracking
m_lazyDecls.reset(); // Need to emit new lazy declarations
string filename
= (v3Global.opt.makeDir() + "/" + EmitCUtil::topClassName() + "_" + protect("_Trace"));
filename = m_uniqueNames.get(filename);
if (m_slow) filename += "__Slow";
filename += ".cpp";
AstCFile* const cfilep = createCFile(filename, m_slow, true /*source*/);
cfilep->support(true);
m_cfilesr.push_back(cfilep);
V3OutCFile* const ofilep
= optSystemC() ? new V3OutScFile{filename} : new V3OutCFile{filename};
setOutputFile(ofilep, cfilep);
putsHeader();
puts("// DESCR"
"IPTION: Verilator output: Tracing implementation internals\n");
// Includes
for (const string& base : v3Global.opt.traceSourceLangs())
puts("#include \"" + base + ".h\"\n");
puts("#include \"" + EmitCUtil::symClassName() + ".h\"\n");
puts("\n");
}
2023-12-19 17:07:06 +01:00
V3OutCFile* typesFp() const VL_MT_SAFE { return m_typesFp; }
void openNextTypesFile() {
UASSERT(!m_typesFp, "Declarations output file already open");
string filename = (v3Global.opt.makeDir() + "/" + EmitCUtil::topClassName() + "_"
+ protect("_TraceDecls"));
2023-12-19 17:07:06 +01:00
filename = m_uniqueNames.get(filename);
filename += "__Slow.cpp";
AstCFile* const cfilep = createCFile(filename, m_slow, true /*source*/);
cfilep->support(true);
m_cfilesr.push_back(cfilep);
if (optSystemC()) {
m_typesFp = new V3OutScFile{filename};
} else {
m_typesFp = new V3OutCFile{filename};
}
typesFp()->putsHeader();
typesFp()->puts("// DESCR"
"IPTION: Verilator output: Tracing declarations\n");
// Includes
for (const string& base : v3Global.opt.traceSourceLangs())
typesFp()->puts("#include \"" + base + ".h\"\n");
2023-12-19 17:07:06 +01:00
typesFp()->puts("\n");
typesFp()->puts("\nvoid " + EmitCUtil::prefixNameProtect(m_modp) + "__"
2023-12-19 17:07:06 +01:00
+ protect("traceDeclTypesSub" + cvtToStr(m_traceTypeSubs++)) + "("
+ v3Global.opt.traceClassBase() + "* tracep) {\n");
}
void closeTypesFile() {
typesFp()->puts("}\n");
VL_DO_CLEAR(delete m_typesFp, m_typesFp = nullptr);
}
void callTypeSubs() {
typesFp()->puts("}\n");
// Forward declarations for subs in other files
for (int i = 0; i < m_traceTypeSubs - 1; ++i) {
typesFp()->puts("void " + EmitCUtil::prefixNameProtect(m_modp) + "__"
2023-12-19 17:07:06 +01:00
+ protect("traceDeclTypesSub" + cvtToStr(i)) + "("
+ v3Global.opt.traceClassBase() + "* tracep);\n");
}
typesFp()->puts("\nvoid " + EmitCUtil::prefixNameProtect(m_modp) + "__"
+ protect("trace_decl_types") + "(" + v3Global.opt.traceClassBase()
+ "* tracep) {\n");
2023-12-19 17:07:06 +01:00
for (int i = 0; i < m_traceTypeSubs; ++i) {
typesFp()->puts(EmitCUtil::prefixNameProtect(m_modp) + "__"
2023-12-19 17:07:06 +01:00
+ protect("traceDeclTypesSub" + cvtToStr(i)) + "(tracep);\n");
}
}
bool typesSplitNeeded() {
return v3Global.opt.outputSplitCTrace()
&& m_typeSplitSize >= v3Global.opt.outputSplitCTrace();
}
bool emitTraceIsScBv(const AstTraceInc* nodep) {
const AstVarRef* const varrefp = VN_CAST(nodep->declp()->valuep(), VarRef);
if (!varrefp) return false;
const AstVar* const varp = varrefp->varp();
return varp->isSc() && varp->isScBv();
}
bool emitTraceIsScBigUint(const AstTraceInc* nodep) {
const AstVarRef* const varrefp = VN_CAST(nodep->declp()->valuep(), VarRef);
if (!varrefp) return false;
const AstVar* const varp = varrefp->varp();
return varp->isSc() && varp->isScBigUint();
}
bool emitTraceIsScUint(const AstTraceInc* nodep) {
const AstVarRef* const varrefp = VN_CAST(nodep->declp()->valuep(), VarRef);
if (!varrefp) return false;
const AstVar* const varp = varrefp->varp();
return varp->isSc() && (varp->isScUint() || varp->isScUintBool());
}
void emitTraceInitOne(const 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 if (nodep->dtypep()->basicp()->isEvent()) {
puts("tracep->declEvent(");
} else {
puts("tracep->declBit(");
}
// Code
puts("c+" + cvtToStr(nodep->code()));
if (nodep->arrayRange().ranged()) puts("+i*" + cvtToStr(nodep->widthWords()));
// Function index
puts(",");
puts(cvtToStr(nodep->fidx()));
// Name
puts(",");
putsQuoted(VIdProtect::protectWordsIf(nodep->showname(), nodep->protect()));
// Enum number
puts("," + cvtToStr(enumNum));
// Direction
2024-11-26 00:25:36 +01:00
if (nodep->declDirection().isInout()) {
puts(", VerilatedTraceSigDirection::INOUT");
} else if (nodep->declDirection().isWritable()) {
puts(", VerilatedTraceSigDirection::OUTPUT");
} else if (nodep->declDirection().isNonOutput()) {
puts(", VerilatedTraceSigDirection::INPUT");
} else {
puts(", VerilatedTraceSigDirection::NONE");
}
// Kind
puts(", VerilatedTraceSigKind::");
puts(nodep->varType().traceSigKind());
// Type
puts(", VerilatedTraceSigType::");
puts(nodep->dtypep()->basicp()->keyword().traceSigType());
// Array range
if (nodep->arrayRange().ranged()) {
puts(", true,(i+" + cvtToStr(nodep->arrayRange().lo()) + ")");
} else {
puts(", false,-1");
}
// Bit range
if (!nodep->dtypep()->basicp()->isDouble() && nodep->bitRange().ranged()) {
puts(", " + cvtToStr(nodep->bitRange().left()) + ","
+ cvtToStr(nodep->bitRange().right()));
}
//
puts(");");
}
2023-12-19 17:07:06 +01:00
int getEnumMapNum(AstEnumDType* nodep) {
int enumNum = m_enumNumMap[nodep];
if (!enumNum) {
if (typesSplitNeeded()) {
// Splitting file, so using parallel build.
v3Global.useParallelBuild(true);
closeTypesFile();
openNextTypesFile();
}
enumNum = ++m_enumNum;
m_enumNumMap[nodep] = enumNum;
int nvals = 0;
typesFp()->puts("{\n");
typesFp()->putns(nodep, "const char* " + protect("__VenumItemNames") + "[]\n");
2023-12-19 17:07:06 +01:00
typesFp()->puts("= {");
for (AstEnumItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), EnumItem)) {
if (++nvals > 1) typesFp()->puts(", ");
typesFp()->putbs("\"" + itemp->prettyName() + "\"");
}
typesFp()->puts("};\n");
nvals = 0;
typesFp()->puts("const char* " + protect("__VenumItemValues") + "[]\n");
typesFp()->puts("= {");
for (AstEnumItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), EnumItem)) {
AstConst* const constp = VN_AS(itemp->valuep(), Const);
if (++nvals > 1) typesFp()->puts(", ");
typesFp()->putbs("\"" + constp->num().displayed(nodep, "%0b") + "\"");
}
typesFp()->puts("};\n");
typesFp()->puts("tracep->declDTypeEnum(" + cvtToStr(enumNum) + ", \""
+ nodep->prettyName() + "\", " + cvtToStr(nvals) + ", "
+ cvtToStr(nodep->widthMin()) + ", " + protect("__VenumItemNames")
+ ", " + protect("__VenumItemValues") + ");\n");
typesFp()->puts("}\n");
m_typeSplitSize += 3;
}
return enumNum;
}
int emitTraceDeclDType(AstNodeDType* nodep) {
// Return enum number or -1 for none
if (v3Global.opt.traceEnabledFst()) {
// 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* const enump = VN_CAST(nodep->skipRefToEnump(), EnumDType)) {
2023-12-19 17:07:06 +01:00
return getEnumMapNum(enump);
}
}
return -1;
}
void emitTraceChangeOne(AstTraceInc* nodep, int arrayindex) {
// Note: Both VTraceType::CHANGE and VTraceType::FULL use the 'full' methods
const std::string func = nodep->traceType() == VTraceType::CHANGE ? "chg" : "full";
bool emitWidth = true;
string stype;
if (nodep->dtypep()->basicp()->isDouble()) {
stype = "Double";
emitWidth = false;
} else if (nodep->isWide() || emitTraceIsScBv(nodep) || emitTraceIsScBigUint(nodep)) {
stype = "WData";
} else if (nodep->isQuad()) {
stype = "QData";
} else if (nodep->declp()->widthMin() > 16) {
stype = "IData";
} else if (nodep->declp()->widthMin() > 8) {
stype = "SData";
} else if (nodep->declp()->widthMin() > 1) {
stype = "CData";
} else if (nodep->dtypep()->basicp()->isEvent()) {
stype = "Event";
emitWidth = false;
} else {
stype = "Bit";
emitWidth = false;
}
putns(nodep, "bufp->" + func + stype);
const uint32_t offset = (arrayindex < 0) ? 0 : (arrayindex * nodep->declp()->widthWords());
const uint32_t code = nodep->declp()->code() + offset;
// Note: Both VTraceType::CHANGE and VTraceType::FULL use the 'full' methods
puts(v3Global.opt.useTraceOffload() && nodep->traceType() == VTraceType::CHANGE
? "(base+"
: "(oldp+");
puts(cvtToStr(code - nodep->baseCode()));
puts(",");
emitTraceValue(nodep, arrayindex);
if (emitWidth) puts("," + cvtToStr(nodep->declp()->widthMin()));
puts(");\n");
}
void emitTraceValue(const AstTraceInc* nodep, int arrayindex) {
if (AstVarRef* const varrefp = VN_CAST(nodep->valuep(), VarRef)) {
const AstVar* const varp = varrefp->varp();
if (varp->isEvent()) puts("&");
puts("(");
if (emitTraceIsScBigUint(nodep)) {
puts("(uint32_t*)");
} else if (emitTraceIsScBv(nodep)) {
puts("VL_SC_BV_DATAP(");
}
iterateConst(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("(");
iterateConst(nodep->valuep());
puts(")");
}
}
// VISITORS
using EmitCFunc::visit; // Suppress hidden overloaded virtual function warning
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
closeOutputFile();
// Open a new file
openNextOutputFile();
}
EmitCFunc::visit(nodep);
}
void visit(AstTracePushPrefix* nodep) override {
putns(nodep, "tracep->pushPrefix(");
putsQuoted(VIdProtect::protectWordsIf(nodep->prefix(), nodep->protect()));
puts(", VerilatedTracePrefixType::");
puts(nodep->prefixType().ascii());
puts(");\n");
}
void visit(AstTracePopPrefix* nodep) override { //
putns(nodep, "tracep->popPrefix();\n");
}
void visit(AstTraceDecl* nodep) override {
const int enumNum = emitTraceDeclDType(nodep->dtypep());
putns(nodep, "");
if (nodep->arrayRange().ranged()) {
puts("for (int i = 0; i < " + cvtToStr(nodep->arrayRange().elements()) + "; ++i) {\n");
emitTraceInitOne(nodep, enumNum);
puts("\n}\n");
} else {
emitTraceInitOne(nodep, enumNum);
puts("\n");
}
}
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, std::deque<AstCFile*>& cfilesr)
: m_slow{slow}
, m_cfilesr{cfilesr} {
m_modp = modp;
// Open output file
openNextOutputFile();
2023-12-19 17:07:06 +01:00
if (m_slow) openNextTypesFile();
// Emit functions
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (AstCFunc* const funcp = VN_CAST(nodep, CFunc)) iterateConst(funcp);
}
// Close output file
closeOutputFile();
2023-12-19 17:07:06 +01:00
if (m_slow) {
callTypeSubs();
closeTypesFile();
}
}
~EmitCTrace() override = default;
public:
static void main(AstNodeModule* modp, bool slow, std::deque<AstCFile*>& cfilesr) VL_MT_STABLE {
EmitCTrace{modp, slow, cfilesr};
}
};
//######################################################################
// EmitC class functions
void V3EmitC::emitcImp() {
UINFO(2, __FUNCTION__ << ":");
// Make parent module pointers available.
const EmitCParentModule emitCParentModule;
std::list<std::deque<AstCFile*>> cfiles;
V3ThreadScope threadScope;
// Process each module in turn
for (const AstNode* nodep = v3Global.rootp()->modulesp(); nodep; nodep = nodep->nextp()) {
if (VN_IS(nodep, Class)) continue; // Imped with ClassPackage
const AstNodeModule* const modp = VN_AS(nodep, NodeModule);
cfiles.emplace_back();
auto& slowCfilesr = cfiles.back();
threadScope.enqueue(
[modp, &slowCfilesr] { EmitCImp::main(modp, /* slow: */ true, slowCfilesr); });
cfiles.emplace_back();
auto& fastCfilesr = cfiles.back();
threadScope.enqueue(
[modp, &fastCfilesr] { EmitCImp::main(modp, /* slow: */ false, fastCfilesr); });
}
// Emit trace routines (currently they can only exist in the top module)
if (v3Global.opt.trace() && !v3Global.opt.lintOnly()) {
cfiles.emplace_back();
auto& slowCfilesr = cfiles.back();
threadScope.enqueue([&slowCfilesr] {
EmitCTrace::main(v3Global.rootp()->topModulep(), /* slow: */ true, slowCfilesr);
});
cfiles.emplace_back();
auto& fastCfilesr = cfiles.back();
threadScope.enqueue([&fastCfilesr] {
EmitCTrace::main(v3Global.rootp()->topModulep(), /* slow: */ false, fastCfilesr);
});
}
// Wait for futures
threadScope.wait();
for (const auto& collr : cfiles) {
for (const auto cfilep : collr) v3Global.rootp()->addFilesp(cfilep);
}
}
void V3EmitC::emitcFiles() {
UINFO(2, __FUNCTION__ << ":");
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
for (AstNodeFile *filep = v3Global.rootp()->filesp(), *nextp; filep; filep = nextp) {
nextp = VN_AS(filep->nextp(), NodeFile);
AstCFile* const cfilep = VN_CAST(filep, CFile);
if (cfilep && cfilep->tblockp()) {
2022-11-20 19:11:01 +01:00
V3OutCFile of{cfilep->name()};
of.puts("// DESCR"
"IPTION: Verilator generated C++\n");
Internals: Refactor text based Ast constructs (#6280) (#6571) Remove the large variety of ways raw "text" is represented in the Ast. Particularly, the only thing that represents a string to be emitted in the output is AstText. There are 5 AstNodes that can contain AstText, and V3Emit will throw an error if an AstText is encountered anywhere else: - AstCStmt: Internally generated procedural statements involving raw text. - AstCStmtUser: This is the old AstUCStmt, renamed so it sorts next to AstCStmt, as it's largely equivalent. We should never create this internally unless used to represent user input. It is used for $c, statements in the input, and for some 'systemc_* blocks. - AstCExpr: Internally generaged expression involving raw text. - AstCExprUser: This is the old AstUCFunc, renamed so it sorts next to AstCExpr. It is largely equivalent, but also has more optimizations disabled. This should never be created internally, it is only used for $c expressions in the input. - AstTextBlock: Use by V3ProtectLib only, to generate the hierarchical wrappers. Text "tracking" for indentation is always on for AstCStmt, AstCExpr, and AstTextBlock, as these are always generated by us, and should always be well formed. Tracking is always off for AstCStmtUser and AstCExprUser, as these contain arbitrary user input that might not be safe to parse for indentation. Remove subsequently redundant AstNodeSimpleText and AstNodeText types. This patch also fixes incorrect indentation in emitted waveform tracing functions, and makes the output more readable for hier block SV stubs. With that, all raw text nodes are handled as a proper AstNodeStmt or AstNodeExpr as required for #6280.
2025-10-21 13:41:29 +02:00
EmitCFunc{cfilep->tblockp(), &of, cfilep};
}
}
}