mirror of
https://github.com/verilator/verilator.git
synced 2026-10-05 17:43:31 +02:00
The constant pool is now an ordinary package, created with the netlist and instantiated under $root like any other package, and most special handling has been removed, including AstConstPool. Lookups go through V3ConstPool, which has a singleton instance owned by V3Global. Static methods on V3Common form the public interfce to add/find constant pool entries. With that, the constant pool is usable at any stage during compilation, so enum and dimension tables created by V3Width, and enum value tables created by V3Randomize now also live in the constant pool instead of $unit, so identical tables are shared. Associative array constants are handled separately from unpacked tables, which used to be broken but unused. Emitted constant pool variables use direct initialization, and `constinit` with C++20 where the type allows it. This ensures we don't change a run-time in a way that would result in unintended code size increase. -fno-merge-const-pool, which was introduced years ago but never prompted a bug report is deprecated and has no effect. This is also prep for future work.
1958 lines
81 KiB
C++
1958 lines
81 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Emit C++ for tree
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify it
|
|
// under the terms of either the GNU Lesser General Public License Version 3
|
|
// or the Perl Artistic License Version 2.0.
|
|
// SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
//
|
|
//*************************************************************************
|
|
|
|
#ifndef VERILATOR_V3EMITCFUNC_H_
|
|
#define VERILATOR_V3EMITCFUNC_H_
|
|
|
|
#include "config_build.h"
|
|
#include "verilatedos.h"
|
|
|
|
#include "V3EmitCConstInit.h"
|
|
#include "V3Global.h"
|
|
#include "V3MemberMap.h"
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
// Number of VL_CONST_W_*X's in verilated.h (IE VL_CONST_W_8X is last)
|
|
constexpr int EMITC_NUM_CONSTW = 8;
|
|
|
|
//######################################################################
|
|
// Emit lazy forward declarations
|
|
|
|
class EmitCLazyDecls final : public VNVisitorConst {
|
|
// NODE STATE/TYPES
|
|
// None allowed to support threaded emitting
|
|
|
|
// MEMBERS
|
|
std::unordered_set<string> m_emittedManually; // Set of names already declared manually.
|
|
EmitCBaseVisitorConst& m_emitter; // For access to file output
|
|
bool m_needsBlankLine = false; // Emit blank line if any declarations were emitted (cosmetic)
|
|
std::set<AstNode*> m_emitted; // -> in set. Already emitted decl for symbols.
|
|
|
|
// METHODS
|
|
bool declaredOnce(AstNode* nodep) { return m_emitted.insert(nodep).second; }
|
|
|
|
void lazyDeclare(AstCFunc* funcp) {
|
|
// Already declared in this compilation unit
|
|
if (!declaredOnce(funcp)) return;
|
|
// Check if this kind of function is lazily declared
|
|
if (!(funcp->isMethod() && funcp->isLoose()) && !funcp->dpiImportPrototype()) return;
|
|
// Already declared manually
|
|
if (m_emittedManually.count(funcp->nameProtect())) return;
|
|
// Needs lazy declaration, emit one
|
|
m_emitter.emitCFuncDecl(funcp, EmitCParentModule::get(funcp), funcp->dpiImportPrototype());
|
|
m_needsBlankLine = true;
|
|
}
|
|
|
|
void lazyDeclareConstPoolVar(AstVar* varp) {
|
|
if (!declaredOnce(varp)) return; // Already declared
|
|
const string nameProtect
|
|
= EmitCUtil::topClassName() + "__ConstPool__" + varp->nameProtect();
|
|
m_emitter.putns(varp, "extern const ");
|
|
m_emitter.puts(varp->dtypep()->cType(nameProtect, false, false));
|
|
m_emitter.puts(";\n");
|
|
m_needsBlankLine = true;
|
|
}
|
|
|
|
// VISITORS
|
|
void visit(AstNodeCCall* nodep) override {
|
|
lazyDeclare(nodep->funcp());
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
|
|
void visit(AstAddrOfCFunc* nodep) override {
|
|
lazyDeclare(nodep->funcp());
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
|
|
void visit(AstVarRef* nodep) override {
|
|
AstVar* const varp = nodep->varp();
|
|
// Only constant pool symbols are lazy declared for now ...
|
|
if (EmitCParentModule::get(varp)->isConstPool()) lazyDeclareConstPoolVar(varp);
|
|
}
|
|
|
|
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
|
|
|
|
public:
|
|
explicit EmitCLazyDecls(EmitCBaseVisitorConst& emitter)
|
|
: m_emitter{emitter} {}
|
|
void emit(AstNode* nodep) {
|
|
m_needsBlankLine = false;
|
|
iterateChildrenConst(nodep);
|
|
if (m_needsBlankLine) m_emitter.puts("\n");
|
|
}
|
|
void emit(const string& prefix, const string& name, const string& suffix) {
|
|
m_emittedManually.insert(name);
|
|
m_emitter.ensureNewLine();
|
|
m_emitter.puts(prefix);
|
|
m_emitter.puts(name);
|
|
m_emitter.puts(suffix);
|
|
m_emitter.ensureNewLine();
|
|
}
|
|
void declared(AstCFunc* nodep) { m_emitted.insert(nodep); }
|
|
void reset() { m_emitted.clear(); }
|
|
};
|
|
|
|
// ######################################################################
|
|
// Emit statements and expressions
|
|
|
|
class EmitCFunc VL_NOT_FINAL : public EmitCConstInit {
|
|
VMemberMap m_memberMap;
|
|
AstVarRef* m_wideTempRefp = nullptr; // Variable that _WW macros should be setting
|
|
std::unordered_map<AstJumpBlock*, size_t> m_labelNumbers; // Label numbers for AstJumpBlocks
|
|
bool m_createdScopeHash = false; // Already created a scope hash
|
|
|
|
protected:
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
EmitCLazyDecls m_lazyDecls{*this}; // Visitor for emitting lazy declarations
|
|
bool m_useSelfForThis = false; // Replace "this" with "vlSelf"
|
|
bool m_usevlSelfRef = false; // Use vlSelfRef reference instead of vlSelf pointer
|
|
const AstNodeModule* m_modp = nullptr; // Current module being emitted
|
|
const AstCFunc* m_cfuncp = nullptr; // Current function being emitted
|
|
bool m_instantiatesOwnProcess = false;
|
|
const AstClassPackage* m_classOrPackage = nullptr; // Pointer to current class or package
|
|
string m_classOrPackageHash; // Hash of class or package name
|
|
|
|
bool constructorNeedsProcess(const AstClass* const classp) {
|
|
const AstNode* const newp = m_memberMap.findMember(classp, "new");
|
|
if (!newp) return false;
|
|
const AstCFunc* const ctorp = VN_CAST(newp, CFunc);
|
|
if (!ctorp) return false;
|
|
UASSERT_OBJ(ctorp->isConstructor(), ctorp, "`new` is not a constructor!");
|
|
return ctorp->needProcess();
|
|
}
|
|
|
|
bool constructorNeedsProcess(const AstNodeDType* const dtypep) {
|
|
if (const AstClassRefDType* const crefdtypep = VN_CAST(dtypep, ClassRefDType))
|
|
return constructorNeedsProcess(crefdtypep->classp());
|
|
return false;
|
|
}
|
|
|
|
bool coverageUsesLocalCounter(AstNodeCoverDecl* const declp) const {
|
|
// Only functions with an object receiver can address the module-local
|
|
// coverage array. Static/package/class helper functions still use the
|
|
// symbol-table array, as they may have vlSymsp but no vlSelf/this.
|
|
// Also require the coverage declaration to belong to the current
|
|
// module; cloned task bodies can increment declarations owned by a
|
|
// different module, and those must keep using the global array.
|
|
return m_cfuncp && !m_cfuncp->isStatic() && !VN_IS(m_modp, Class)
|
|
&& !VN_IS(m_modp, ClassPackage)
|
|
&& EmitCParentModule::get(declp->dataDeclThisp()) == m_modp;
|
|
}
|
|
int coverageBinNum(AstNodeCoverDecl* const declp, bool forceGlobal = false) const {
|
|
return !forceGlobal && coverageUsesLocalCounter(declp)
|
|
? declp->dataDeclThisp()->localBinNum()
|
|
: declp->dataDeclThisp()->binNum();
|
|
}
|
|
void putCoverageArray(AstNodeCoverDecl* const declp, bool forceGlobal = false) {
|
|
if (!forceGlobal && coverageUsesLocalCounter(declp)) {
|
|
// Keep counters on the module object when possible, so every
|
|
// no-inline instance has storage available if coverage output is
|
|
// later written with forcePerInstance.
|
|
puts(m_cfuncp->isLoose() ? "vlSelf->__Vcoverage" : "this->__Vcoverage");
|
|
} else {
|
|
// Static/package/class helper functions do not have vlSelf, so
|
|
// keep using the shared symbol-table coverage array there.
|
|
puts("vlSymsp->__Vcoverage");
|
|
}
|
|
}
|
|
void emitCoverOtherDeclInsert(AstCoverOtherDecl* nodep, bool forceGlobal = false) {
|
|
putns(nodep, "vlSelf->__vlCoverInsert("); // As Declared in emitCoverageDecl
|
|
putCoverageArray(nodep, forceGlobal);
|
|
puts(" + ");
|
|
puts(cvtToStr(coverageBinNum(nodep, forceGlobal)));
|
|
// first controls whether duplicate module instances are collapsed in
|
|
// the default coverage view. The following flag says whether countp
|
|
// points to an object-local counter; only those counters must be kept
|
|
// real for later instances so forcePerInstance can split hierarchy.
|
|
puts(", first"); // Enable, passed from __Vconfigure parameter
|
|
puts(", ");
|
|
puts(!forceGlobal && coverageUsesLocalCounter(nodep) ? "true" : "false");
|
|
puts(", ");
|
|
putsQuoted(protect(nodep->fileline()->filename()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->fileline()->lineno()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->offset() + nodep->fileline()->firstColumn()));
|
|
puts(", ");
|
|
putsQuoted((!nodep->hier().empty() ? "." : "")
|
|
+ VIdProtect::protectWordsIf(nodep->hier(), nodep->protect()));
|
|
puts(", ");
|
|
putsQuoted(VIdProtect::protectWordsIf(nodep->page(), nodep->protect()));
|
|
puts(", ");
|
|
putsQuoted(VIdProtect::protectWordsIf(nodep->comment(), nodep->protect()));
|
|
puts(", ");
|
|
putsQuoted(nodep->linescov());
|
|
puts(", ");
|
|
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmVar(), nodep->protect()));
|
|
puts(", ");
|
|
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmFrom(), nodep->protect()));
|
|
puts(", ");
|
|
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTo(), nodep->protect()));
|
|
puts(", ");
|
|
putsQuoted(VIdProtect::protectWordsIf(nodep->fsmTag(), nodep->protect()));
|
|
puts(");\n");
|
|
}
|
|
|
|
public:
|
|
// METHODS
|
|
bool displayEmitHeader(AstNode* nodep);
|
|
void displayNode(AstNode* nodep, AstSFormatF* fmtp, const string& vformat, AstNode* exprsp,
|
|
bool isScan);
|
|
|
|
bool emitSimpleOk(AstNodeExpr* nodep);
|
|
void emitIQW(const AstNode* nodep) {
|
|
// See "Type letters" in verilated.h
|
|
// Other abbrevs: "C"har, "S"hort, "F"loat, "D"ouble, stri"N"g, "R"=queue, "U"npacked
|
|
puts(nodep->dtypep()->skipRefp()->charIQWN());
|
|
}
|
|
void emitRU(const AstNode* nodep) {
|
|
const AstNodeDType* dtp = nodep->dtypep()->skipRefp();
|
|
// See "Type letters" in verilated.h
|
|
if (VN_IS(dtp, UnpackArrayDType))
|
|
puts("U");
|
|
else if (VN_IS(dtp, QueueDType) || VN_IS(dtp, DynArrayDType))
|
|
puts("R");
|
|
}
|
|
void emitScIQW(AstVar* nodep) {
|
|
UASSERT_OBJ(nodep->isSc(), nodep, "emitting SystemC operator on non-SC variable");
|
|
puts(nodep->isScBigUint() ? "SB"
|
|
: (nodep->isScUint() || nodep->isScUintBool()) ? "SU"
|
|
: nodep->isScBv() ? "SW"
|
|
: nodep->isDouble() ? "SD"
|
|
: (nodep->isScQuad() ? "SQ" : "SI"));
|
|
}
|
|
void emitDatap(const AstNode* nodep) {
|
|
// When passing to a function with va_args the compiler doesn't
|
|
// know need a pointer so when wide, need to look inside VlWide
|
|
if (nodep->isWide()) puts(".data()");
|
|
}
|
|
void emitOpName(AstNode* nodep, const string& format, AstNode* lhsp, AstNode* rhsp,
|
|
AstNode* thsp);
|
|
void emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer, bool inProcess);
|
|
void emitDereference(AstNode* nodep, const string& pointer);
|
|
std::string dereferenceString(const std::string& pointer) const;
|
|
void emitCvtPackStr(AstNode* nodep);
|
|
void emitConstant(AstConst* nodep);
|
|
void emitConstantString(const AstConst* nodep);
|
|
void emitSetVarConstant(const string& assignString, AstConst* constp);
|
|
void emitVarReset(const string& prefix, AstVar* varp, bool constructing);
|
|
string emitVarResetRecurse(const AstVar* varp, bool constructing,
|
|
const string& varNameProtected, AstNodeDType* dtypep, int depth,
|
|
const string& suffix, const AstNode* valuep);
|
|
void emitVarResetScopeHash();
|
|
void emitChangeDet();
|
|
void putCommaIterateNext(AstNode* nodep, bool comma = false) {
|
|
for (AstNode* subnodep = nodep; subnodep; subnodep = subnodep->nextp()) {
|
|
if (comma) puts(", ");
|
|
iterateConst(subnodep);
|
|
comma = true;
|
|
}
|
|
}
|
|
template <typename T>
|
|
string optionalProcArg(const T* const nodep) {
|
|
return (nodep && constructorNeedsProcess(nodep)) ? "vlProcess, " : "";
|
|
}
|
|
const AstCNew* getSuperNewCallRecursep(AstNode* const nodep) {
|
|
// Get the super.new call
|
|
if (!nodep) return nullptr;
|
|
if (const AstCNew* const cnewp = VN_CAST(nodep, CNew)) return cnewp;
|
|
if (const AstStmtExpr* const stmtp = VN_CAST(nodep, StmtExpr)) {
|
|
if (const AstCNew* const cnewp = VN_CAST(stmtp->exprp(), CNew)) return cnewp;
|
|
}
|
|
if (const AstJumpBlock* const blockp = VN_CAST(nodep, JumpBlock)) {
|
|
if (const AstCNew* const cnewp = getSuperNewCallRecursep(blockp->stmtsp())) {
|
|
return cnewp;
|
|
}
|
|
}
|
|
if (const AstCNew* const cnewp = getSuperNewCallRecursep(nodep->nextp())) return cnewp;
|
|
return nullptr;
|
|
}
|
|
|
|
void emitConstantW(AstConst* nodep, AstVarRef* assigntop) {
|
|
// For tradition and compilation speed, assign each word directly into
|
|
// output variable instead of using '='
|
|
putns(nodep, "");
|
|
if (nodep->num().isFourState()) {
|
|
nodep->v3warn(E_UNSUPPORTED, "Unsupported: 4-state numbers in this context");
|
|
return;
|
|
}
|
|
|
|
int upWidth = nodep->num().widthToFit();
|
|
int chunks = 0;
|
|
if (upWidth > EMITC_NUM_CONSTW * VL_EDATASIZE) {
|
|
// Output e.g. 8 words in groups of e.g. 8
|
|
chunks = (upWidth - 1) / (EMITC_NUM_CONSTW * VL_EDATASIZE);
|
|
upWidth %= (EMITC_NUM_CONSTW * VL_EDATASIZE);
|
|
if (upWidth == 0) upWidth = (EMITC_NUM_CONSTW * VL_EDATASIZE);
|
|
}
|
|
{ // Upper e.g. 8 words
|
|
if (chunks) {
|
|
putnbs(nodep, "VL_CONSTHI_W_");
|
|
puts(cvtToStr(VL_WORDS_I(upWidth)));
|
|
puts("X(");
|
|
puts(cvtToStr(nodep->widthMin()));
|
|
puts(",");
|
|
puts(cvtToStr(chunks * EMITC_NUM_CONSTW * VL_EDATASIZE));
|
|
} else {
|
|
putnbs(nodep, "VL_CONST_W_");
|
|
puts(cvtToStr(VL_WORDS_I(upWidth)));
|
|
puts("X(");
|
|
puts(cvtToStr(nodep->widthMin()));
|
|
}
|
|
puts(",");
|
|
if (!assigntop->selfPointer().isEmpty()) {
|
|
emitDereference(assigntop, assigntop->selfPointerProtect(m_useSelfForThis));
|
|
}
|
|
puts(assigntop->varp()->nameProtect());
|
|
for (int word = VL_WORDS_I(upWidth) - 1; word >= 0; word--) {
|
|
// Only 32 bits - llx + long long here just to appease CPP format warning
|
|
ofp()->printf(",0x%08" PRIx64, static_cast<uint64_t>(nodep->num().edataWord(
|
|
word + chunks * EMITC_NUM_CONSTW)));
|
|
}
|
|
puts(")");
|
|
}
|
|
for (chunks--; chunks >= 0; chunks--) {
|
|
puts(";\n");
|
|
putbs("VL_CONSTLO_W_");
|
|
puts(cvtToStr(EMITC_NUM_CONSTW));
|
|
puts("X(");
|
|
puts(cvtToStr(chunks * EMITC_NUM_CONSTW * VL_EDATASIZE));
|
|
puts(",");
|
|
if (!assigntop->selfPointer().isEmpty()) {
|
|
emitDereference(assigntop, assigntop->selfPointerProtect(m_useSelfForThis));
|
|
}
|
|
puts(assigntop->varp()->nameProtect());
|
|
for (int word = EMITC_NUM_CONSTW - 1; word >= 0; word--) {
|
|
// Only 32 bits - llx + long long here just to appease CPP format warning
|
|
ofp()->printf(",0x%08" PRIx64, static_cast<uint64_t>(nodep->num().edataWord(
|
|
word + chunks * EMITC_NUM_CONSTW)));
|
|
}
|
|
puts(")");
|
|
}
|
|
}
|
|
|
|
void emitNodesWithText(AstNode* nodesp, bool useSelfForThis, bool tracking,
|
|
const std::string& separator) {
|
|
for (AstNode* nodep = nodesp; nodep; nodep = nodep->nextp()) {
|
|
if (const AstText* const textp = VN_CAST(nodep, Text)) {
|
|
const std::string text
|
|
= VSelfPointerText::replaceThis(useSelfForThis, textp->text());
|
|
if (tracking) {
|
|
puts(text);
|
|
} else {
|
|
ofp()->putsNoTracking(text);
|
|
}
|
|
} else {
|
|
iterateConst(nodep);
|
|
}
|
|
if (nodep->nextp()) puts(separator);
|
|
}
|
|
}
|
|
|
|
void putConstructorSubinit(const AstClass* classp, AstCFunc* cfuncp) {
|
|
// Direct non-virtual bases in declaration order
|
|
for (const AstClassExtends* extp = classp->extendsp(); extp;
|
|
extp = VN_AS(extp->nextp(), ClassExtends)) {
|
|
if (extp->classp()->isInterfaceClass()) continue;
|
|
if (extp->classp()->useVirtualPublic()) continue;
|
|
puts(EmitCUtil::prefixNameProtect(extp->classp()));
|
|
puts("::init");
|
|
if (constructorNeedsProcess(extp->classp())) {
|
|
puts("(vlProcess, vlSymsp");
|
|
} else {
|
|
puts("(vlSymsp");
|
|
}
|
|
// Handle super.new() args for the concrete parent
|
|
if (const AstCNew* const superNewCallp = getSuperNewCallRecursep(cfuncp->stmtsp())) {
|
|
putCommaIterateNext(superNewCallp->argsp(), true);
|
|
}
|
|
puts(");\n");
|
|
// Since SystemVerilog allows only for extending from one class, for sure there won't
|
|
// be any more constructors to be called. Even if there are some classes which are
|
|
// "implemented", those are interface classes which do not have constructors.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// VISITORS
|
|
using EmitCConstInit::visit;
|
|
void visit(AstCFunc* const nodep) override {
|
|
if (nodep->emptyBody() && !nodep->isLoose()) return;
|
|
VL_RESTORER(m_useSelfForThis);
|
|
VL_RESTORER(m_cfuncp);
|
|
VL_RESTORER(m_instantiatesOwnProcess);
|
|
VL_RESTORER(m_createdScopeHash);
|
|
m_cfuncp = nodep;
|
|
m_instantiatesOwnProcess = false;
|
|
m_labelNumbers.clear(); // No need to save/restore, all Jumps must be within the function
|
|
|
|
splitSizeInc(nodep);
|
|
|
|
puts("\n");
|
|
m_lazyDecls.emit(nodep);
|
|
if (nodep->ifdef() != "") putns(nodep, "#ifdef " + nodep->ifdef() + "\n");
|
|
emitCFuncHeader(nodep, m_modp, /* withScope: */ true);
|
|
|
|
puts(" {\n");
|
|
if (nodep->isConstructor()) {
|
|
const AstClass* const classp = VN_CAST(nodep->scopep()->modp(), Class);
|
|
if (classp && classp->extendsp()) putConstructorSubinit(classp, nodep);
|
|
}
|
|
|
|
// "+" in the debug indicates a print from the model
|
|
puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ ");
|
|
for (int i = 0; i < m_modp->level(); ++i) puts(" ");
|
|
puts(EmitCUtil::prefixNameProtect(m_modp));
|
|
puts(nodep->isLoose() ? "__" : "::");
|
|
puts(nodep->nameProtect() + "\\n\"); );\n");
|
|
|
|
if (nodep->isLoose()) {
|
|
m_lazyDecls.declared(nodep); // Defined here, so no longer needs declaration
|
|
if (!nodep->isStatic()) { // Standard prologue
|
|
m_useSelfForThis = true;
|
|
if (!VN_IS(m_modp, Class)) {
|
|
puts(EmitCUtil::symClassAssign()); // Uses vlSelf
|
|
} else {
|
|
puts("(void)vlSelf; // Prevent unused variable warning\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Instantiate a process class if it's going to be needed somewhere later
|
|
nodep->forall([&](const AstNodeCCall* ccallp) -> bool {
|
|
if (ccallp->funcp()->needProcess()
|
|
&& (ccallp->funcp()->isCoroutine() == VN_IS(ccallp->backp(), CAwait))) {
|
|
if (!nodep->needProcess() && !m_instantiatesOwnProcess) {
|
|
m_instantiatesOwnProcess = true;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
if (m_instantiatesOwnProcess) {
|
|
AstCStmt* const vlprocp = new AstCStmt{nodep->fileline()};
|
|
vlprocp->add("VlProcessRef vlProcess = std::make_shared<VlProcess>();\n");
|
|
vlprocp->add("VlProcess::currentp(vlProcess.get());");
|
|
nodep->stmtsp()->addHereThisAsNext(vlprocp);
|
|
} else if (nodep->needProcess() && nodep->stmtsp()) {
|
|
// Set current process so VlRNG() constructors in this function seed from it
|
|
AstCStmt* const setProcessp = new AstCStmt{nodep->fileline()};
|
|
setProcessp->add("VlProcess::currentp(vlProcess.get());");
|
|
nodep->stmtsp()->addHereThisAsNext(setProcessp);
|
|
}
|
|
|
|
for (AstNode* subnodep = nodep->argsp(); subnodep; subnodep = subnodep->nextp()) {
|
|
if (AstVar* const varp = VN_CAST(subnodep, Var)) {
|
|
if (varp->isFuncReturn()) emitVarDecl(varp);
|
|
}
|
|
}
|
|
|
|
if (m_useSelfForThis) {
|
|
m_usevlSelfRef = true;
|
|
// Using reference to the vlSelf pointer will help the C++
|
|
// compiler to have dereferenceable hints, which can help to
|
|
// reduce the need for branch instructions in the generated
|
|
// code to allow the compiler to generate load store after the
|
|
// if condition (including short-circuit evaluation)
|
|
// speculatively and also reduce the data cache pollution when
|
|
// executing in the wrong path to make Verilated code faster.
|
|
puts("auto& vlSelfRef = std::ref(*vlSelf).get();\n");
|
|
}
|
|
|
|
if (nodep->varsp()) {
|
|
putsDecoration(nodep, "// Locals\n");
|
|
iterateAndNextConstNull(nodep->varsp());
|
|
}
|
|
|
|
if (nodep->stmtsp()) {
|
|
putsDecoration(nodep, "// Body\n");
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
|
}
|
|
|
|
m_usevlSelfRef = false;
|
|
|
|
if (nodep->isCoroutine()) {
|
|
// Sometimes coroutines don't have co_awaits,
|
|
// so emit a co_return at the end to avoid compile errors.
|
|
puts("co_return;\n");
|
|
}
|
|
|
|
puts("}\n");
|
|
if (nodep->ifdef() != "") puts("#endif // " + nodep->ifdef() + "\n");
|
|
}
|
|
|
|
void visit(AstVar* nodep) override {
|
|
UASSERT_OBJ(m_cfuncp, nodep, "Cannot emit non-local variable");
|
|
emitVarDecl(nodep);
|
|
}
|
|
|
|
void visit(AstCvtArrayToArray* nodep) override {
|
|
if (nodep->reverse()) {
|
|
puts("VL_REVCLONE_Q(");
|
|
} else {
|
|
puts("VL_CLONE_Q(");
|
|
}
|
|
iterateAndNextConstNull(nodep->fromp());
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->blockSize()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->srcElementBits()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->dstElementBits()));
|
|
puts(")");
|
|
}
|
|
|
|
void visit(AstCvtArrayToPacked* nodep) override {
|
|
AstNodeDType* const fromDtp = nodep->fromp()->dtypep()->skipRefp();
|
|
AstNodeDType* const elemDtp = fromDtp->subDTypep()->skipRefp();
|
|
const bool descending = [&]() {
|
|
if (const AstUnpackArrayDType* const unpackDtp = VN_CAST(fromDtp, UnpackArrayDType))
|
|
return !unpackDtp->declRange().ascending();
|
|
return false;
|
|
}();
|
|
puts("VL_PACK_");
|
|
emitIQW(nodep);
|
|
puts("_");
|
|
emitRU(fromDtp);
|
|
emitIQW(elemDtp);
|
|
if (descending) {
|
|
// Wrap source in VL_PACK_REVERSED so VL_PACK sees ascending order
|
|
emitOpName(nodep, "(%nw, %rw, %P, VL_PACK_REVERSED(%li))", nodep->fromp(), elemDtp,
|
|
nullptr);
|
|
} else {
|
|
emitOpName(nodep, "(%nw, %rw, %P, %li)", nodep->fromp(), elemDtp, nullptr);
|
|
}
|
|
}
|
|
|
|
void visit(AstCvtUnpackedToQueue* nodep) override {
|
|
AstNodeDType* const elemDTypep = nodep->fromp()->dtypep()->subDTypep();
|
|
emitOpName(nodep, nodep->emitC(), nodep->fromp(), elemDTypep, nullptr);
|
|
}
|
|
|
|
void visit(AstNodeAssign* nodep) override {
|
|
if (AstCReset* const resetp = VN_CAST(nodep->rhsp(), CReset)) {
|
|
// TODO get rid of emitVarReset and instead let AstNodeAssign understand how to init
|
|
// anything
|
|
AstNode* fromp = nodep->lhsp();
|
|
// Fork needs to use a member select. Nothing else should be possible before VarRef.
|
|
if (AstMemberSel* const sfromp = VN_CAST(fromp, MemberSel)) {
|
|
// Fork-DynScope generated pointer to previously automatic variable
|
|
AstVar* const memberVarp = sfromp->varp();
|
|
fromp = sfromp->fromp();
|
|
if (AstNullCheck* const sfromp = VN_CAST(fromp, NullCheck)) fromp = sfromp->lhsp();
|
|
AstNodeVarRef* const fromVarRefp = VN_AS(fromp, NodeVarRef);
|
|
emitVarReset(
|
|
("VL_NULL_CHECK("s
|
|
+ (fromVarRefp->selfPointer().isEmpty()
|
|
? ""
|
|
: dereferenceString(fromVarRefp->selfPointerProtect(m_useSelfForThis)))
|
|
+ fromVarRefp->varp()->nameProtect() + ", \""
|
|
+ V3OutFormatter::quoteNameControls(protect(nodep->fileline()->filename()))
|
|
+ "\", " + std::to_string(nodep->fileline()->lineno()) + ")->"),
|
|
memberVarp, resetp->constructing());
|
|
} else {
|
|
AstNodeVarRef* const fromVarRefp = VN_AS(fromp, NodeVarRef);
|
|
AstVar* const varp = fromVarRefp->varp();
|
|
const string prefix
|
|
= fromVarRefp->selfPointer().isEmpty()
|
|
? ""
|
|
: dereferenceString(
|
|
VN_AS(fromp, NodeVarRef)->selfPointerProtect(m_useSelfForThis));
|
|
emitVarReset(prefix, varp, resetp->constructing());
|
|
}
|
|
return;
|
|
}
|
|
bool paren = true;
|
|
bool decind = false;
|
|
bool rhs = true;
|
|
bool reverseUnpack = false; // Set for descending CvtPackedToArray
|
|
const AstUnpackArrayDType* const unpackDtp
|
|
= VN_CAST(nodep->dtypep()->skipRefp(), UnpackArrayDType);
|
|
if (AstSel* const selp = VN_CAST(nodep->lhsp(), Sel)) {
|
|
UASSERT_OBJ(selp->widthMin() == selp->widthConst(), selp, "Width mismatch");
|
|
if (selp->widthMin() == 1) {
|
|
putnbs(nodep, "VL_ASSIGNBIT_");
|
|
emitIQW(selp->fromp());
|
|
if (nodep->rhsp()->isAllOnesV()) {
|
|
puts("O(");
|
|
rhs = false;
|
|
} else {
|
|
puts("I(");
|
|
}
|
|
iterateAndNextConstNull(selp->lsbp());
|
|
puts(", ");
|
|
iterateAndNextConstNull(selp->fromp());
|
|
if (rhs) puts(", ");
|
|
} else {
|
|
putnbs(nodep, "VL_ASSIGNSEL_");
|
|
emitIQW(selp->fromp());
|
|
emitIQW(nodep->rhsp());
|
|
puts("(");
|
|
putns(selp->fromp(), cvtToStr(selp->fromp()->widthMin()) + ", ");
|
|
puts(cvtToStr(nodep->widthMin()) + ", ");
|
|
iterateAndNextConstNull(selp->lsbp());
|
|
puts(", ");
|
|
iterateAndNextConstNull(selp->fromp());
|
|
puts(", ");
|
|
}
|
|
} else if (const AstGetcRefN* const selp = VN_CAST(nodep->lhsp(), GetcRefN)) {
|
|
iterateAndNextConstNull(selp->lhsp());
|
|
puts(" = ");
|
|
putnbs(selp, "VL_PUTC_N(");
|
|
iterateAndNextConstNull(selp->lhsp());
|
|
puts(", ");
|
|
iterateAndNextConstNull(selp->rhsp());
|
|
puts(", ");
|
|
} else if (AstVar* const varp = AstVar::scVarRecurse(nodep->lhsp())) {
|
|
putnbs(varp, "VL_ASSIGN_"); // Set a systemC variable
|
|
emitScIQW(varp);
|
|
emitIQW(nodep);
|
|
puts("(");
|
|
puts(cvtToStr(nodep->widthMin()) + ", ");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(", ");
|
|
} else if (AstVar* const varp = AstVar::scVarRecurse(nodep->rhsp())) {
|
|
putnbs(varp, "VL_ASSIGN_"); // Get a systemC variable
|
|
emitIQW(nodep);
|
|
emitScIQW(varp);
|
|
puts("(");
|
|
puts(cvtToStr(nodep->widthMin()) + ", ");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(", ");
|
|
} else if (const AstCvtPackedToArray* const castp
|
|
= VN_CAST(nodep->rhsp(), CvtPackedToArray)) {
|
|
putns(castp, "VL_UNPACK_");
|
|
emitRU(nodep);
|
|
emitIQW(nodep->dtypep()->subDTypep());
|
|
puts("_");
|
|
emitRU(castp->fromp());
|
|
emitIQW(castp->fromp());
|
|
puts("(");
|
|
putns(castp->dtypep(), cvtToStr(castp->dtypep()->subDTypep()->widthMin()));
|
|
puts(", ");
|
|
puts(cvtToStr(castp->fromp()->widthMin()));
|
|
puts(", ");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(", ");
|
|
rhs = false;
|
|
iterateAndNextConstNull(castp->fromp());
|
|
// Descending unpacked dest: reverse after unpack
|
|
if (unpackDtp && !unpackDtp->declRange().ascending()) reverseUnpack = true;
|
|
} else if (const AstCvtArrayToArray* const castp
|
|
= VN_CAST(nodep->rhsp(), CvtArrayToArray)) {
|
|
if (castp->reverse()) {
|
|
putns(castp, "VL_REVCOPY_Q(");
|
|
} else {
|
|
putns(castp, "VL_COPY_Q(");
|
|
}
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(", ");
|
|
rhs = false;
|
|
iterateAndNextConstNull(castp->fromp());
|
|
puts(", ");
|
|
puts(cvtToStr(castp->blockSize()));
|
|
puts(", ");
|
|
puts(cvtToStr(castp->srcElementBits()));
|
|
puts(", ");
|
|
puts(cvtToStr(castp->dstElementBits()));
|
|
} else if (nodep->isWide() && VN_IS(nodep->lhsp(), VarRef) //
|
|
&& !VN_IS(nodep->rhsp(), CExpr) //
|
|
&& !VN_IS(nodep->rhsp(), CMethodHard) //
|
|
// Although not here currently, note putting !VN_IS(Const) works,
|
|
// and means using '=' and bypasses using emitConstantW.
|
|
// Whuch we don't want to do as slows compiler down.
|
|
&& !VN_IS(nodep->rhsp(), VarRef) //
|
|
&& !VN_IS(nodep->rhsp(), InitArray) //
|
|
&& !VN_IS(nodep->rhsp(), AssocSel) //
|
|
&& !VN_IS(nodep->rhsp(), MemberSel) //
|
|
&& !VN_IS(nodep->rhsp(), StructSel) //
|
|
&& !VN_IS(nodep->rhsp(), ArraySel) //
|
|
&& !VN_IS(nodep->rhsp(), ExprStmt)) {
|
|
// Wide functions assign into the array directly, don't need separate assign statement
|
|
m_wideTempRefp = VN_AS(nodep->lhsp(), VarRef);
|
|
paren = false;
|
|
} else if (nodep->isWide() && !unpackDtp && !VN_IS(nodep->rhsp(), Const)) {
|
|
putnbs(nodep, "VL_ASSIGN_W(");
|
|
puts(cvtToStr(nodep->widthMin()) + ", ");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(", ");
|
|
} else if (VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)
|
|
&& (VN_IS(nodep->rhsp(), StreamL) || VN_IS(nodep->lhsp(), StreamL)
|
|
|| VN_IS(nodep->rhsp(), StreamR) || VN_IS(nodep->lhsp(), StreamR)
|
|
|| VN_IS(nodep->rhsp(), StreamR))) {
|
|
//if either side is streamL or streamR don't emit lhsp everything will be passed by
|
|
//reference
|
|
|
|
paren = false;
|
|
|
|
} else {
|
|
paren = false;
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(" ");
|
|
ofp()->blockInc();
|
|
decind = true;
|
|
if (!VN_IS(nodep->rhsp(), Const)) ofp()->putBreak();
|
|
putns(nodep, "= ");
|
|
if (unpackDtp && VN_IS(nodep->rhsp(), InitArray)) {
|
|
// Emit "VlUnpacked<type, depth>{{...InitArray...}}"
|
|
puts(unpackDtp->cType("", false, false, false));
|
|
}
|
|
}
|
|
if (rhs) iterateAndNextConstNull(nodep->rhsp());
|
|
if (paren) puts(")");
|
|
if (decind) ofp()->blockDec();
|
|
puts(";\n");
|
|
if (reverseUnpack) {
|
|
puts("VL_UNPACK_REVERSED(");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(");\n");
|
|
}
|
|
}
|
|
void visit(AstAssocSel* nodep) override {
|
|
iterateAndNextConstNull(nodep->fromp());
|
|
const std::string atFunc = nodep->isLValue() ? ".atWrite(" : ".at(";
|
|
putnbs(nodep, atFunc);
|
|
AstAssocArrayDType* const adtypep
|
|
= VN_AS(nodep->fromp()->dtypep()->skipRefp(), AssocArrayDType);
|
|
UASSERT_OBJ(adtypep, nodep, "Associative select on non-associative type");
|
|
iterateAndNextConstNull(nodep->bitp());
|
|
puts(")");
|
|
}
|
|
void visit(AstWildcardSel* nodep) override {
|
|
iterateAndNextConstNull(nodep->fromp());
|
|
const std::string atFunc = nodep->isLValue() ? ".atWrite(" : ".at(";
|
|
putnbs(nodep, atFunc);
|
|
AstWildcardArrayDType* const adtypep
|
|
= VN_AS(nodep->fromp()->dtypep()->skipRefp(), WildcardArrayDType);
|
|
UASSERT_OBJ(adtypep, nodep, "Wildcard select on non-wildcard-associative type");
|
|
iterateAndNextConstNull(nodep->bitp());
|
|
puts(")");
|
|
}
|
|
void visit(AstCCall* nodep) override {
|
|
const AstCFunc* const funcp = nodep->funcp();
|
|
const AstNodeModule* const funcModp = EmitCParentModule::get(funcp);
|
|
putnbs(nodep, "");
|
|
if (funcp->dpiImportPrototype()) {
|
|
// Calling DPI import
|
|
putns(nodep, funcp->name());
|
|
} else if (funcp->isProperMethod() && funcp->isStatic()) {
|
|
// Call static method via the containing class
|
|
putns(funcModp, EmitCUtil::prefixNameProtect(funcModp) + "::");
|
|
putns(nodep, funcp->nameProtect());
|
|
} else if (nodep->superReference()) {
|
|
// Calling superclass method
|
|
putns(funcModp, EmitCUtil::prefixNameProtect(funcModp) + "::");
|
|
putns(nodep, funcp->nameProtect());
|
|
} else if (funcp->isLoose()) {
|
|
// Calling loose method
|
|
putns(nodep, funcNameProtect(funcp));
|
|
} else {
|
|
// Calling regular method/function
|
|
if (!nodep->selfPointer().isEmpty()) {
|
|
emitDereference(nodep, nodep->selfPointerProtect(m_useSelfForThis));
|
|
}
|
|
putns(nodep, funcp->nameProtect());
|
|
}
|
|
emitCCallArgs(nodep, nodep->selfPointerProtect(m_useSelfForThis), m_cfuncp->needProcess());
|
|
}
|
|
void visit(AstCMethodCall* nodep) override {
|
|
const AstCFunc* const funcp = nodep->funcp();
|
|
UASSERT_OBJ(!funcp->isLoose(), nodep, "Loose method called via AstCMethodCall");
|
|
iterateConst(nodep->fromp());
|
|
putnbs(nodep, "->");
|
|
putns(funcp, funcp->nameProtect());
|
|
emitCCallArgs(nodep, "", m_cfuncp->needProcess());
|
|
}
|
|
void visit(AstCAwait* nodep) override {
|
|
putns(nodep, "co_await ");
|
|
iterateConst(nodep->exprp());
|
|
puts(";\n");
|
|
}
|
|
void visit(AstCNew* nodep) override {
|
|
if (VN_IS(nodep->dtypep(), VoidDType)) { // super.new case
|
|
putsDecoration(nodep, "/*super.new*/");
|
|
return;
|
|
}
|
|
// assignment case;
|
|
putns(nodep, "VL_NEW(" + EmitCUtil::prefixNameProtect(nodep->dtypep()) + ", "
|
|
+ optionalProcArg(nodep->dtypep()) + "vlSymsp");
|
|
putCommaIterateNext(nodep->argsp(), true);
|
|
puts(")");
|
|
}
|
|
void visit(AstCMethodHard* nodep) override {
|
|
iterateConst(nodep->fromp());
|
|
putns(nodep, nodep->usePtr() ? "->" : ".");
|
|
putns(nodep, nodep->name());
|
|
if (nodep->method() == VCMethod::FORCE_READ_SEL) {
|
|
emitIQW(nodep);
|
|
if (nodep->isWide()) puts("<" + cvtToStr(nodep->dtypep()->widthWords()) + ">");
|
|
} else if (nodep->method() == VCMethod::ARRAY_SLICE) {
|
|
// VlUnpacked::slice<N_Out>(loIdx) - N_Out is the (fixed) result array size
|
|
const AstUnpackArrayDType* const adtypep
|
|
= VN_AS(nodep->dtypep()->skipRefp(), UnpackArrayDType);
|
|
puts("<" + cvtToStr(adtypep->elementsConst()) + ">");
|
|
} else if (nodep->method() == VCMethod::COVERGROUP_ADD_COVERPOINT) {
|
|
// The hit-list bound is a template argument of the returned VlCoverpointT<>, and the
|
|
// node's own dtype is that type, so it is the one source of truth for both.
|
|
const AstCoverpointDType* const cpdtypep
|
|
= VN_AS(nodep->dtypep()->skipRefp(), CoverpointDType);
|
|
puts("<" + cvtToStr(cpdtypep->hitBound()) + ">");
|
|
} else if (nodep->method() == VCMethod::COVERGROUP_ADD_CROSS) {
|
|
const AstCoverCrossDType* const cxdtypep
|
|
= VN_AS(nodep->dtypep()->skipRefp(), CoverCrossDType);
|
|
puts("<" + cxdtypep->cppTemplateArgs() + ">");
|
|
}
|
|
puts("(");
|
|
bool comma = false;
|
|
int argNum = 0;
|
|
for (AstNode* subnodep = nodep->pinsp(); subnodep; subnodep = subnodep->nextp()) {
|
|
if (comma) puts(", ");
|
|
if (nodep->method() == VCMethod::RANDOMIZER_HARD && argNum == 1) {
|
|
// For RANDOMIZER_HARD's filename argument (2nd arg after constraint),
|
|
// apply protect() similar to VL_STOP to handle --protected flag
|
|
if (const AstCExpr* const cexprp = VN_CAST(subnodep, CExpr)) {
|
|
// Extract filename from the CExpr (which contains "filename")
|
|
std::string filename;
|
|
for (const AstNode* textnodep = cexprp->nodesp(); textnodep;
|
|
textnodep = textnodep->nextp()) {
|
|
if (const AstText* const textp = VN_CAST(textnodep, Text)) {
|
|
filename = textp->text();
|
|
break;
|
|
}
|
|
}
|
|
// Remove surrounding quotes if present
|
|
if (filename.size() >= 2 && filename.front() == '"'
|
|
&& filename.back() == '"') {
|
|
filename = filename.substr(1, filename.size() - 2);
|
|
}
|
|
// Emit with protect()
|
|
putsQuoted(protect(filename));
|
|
} else {
|
|
iterateConst(subnodep);
|
|
}
|
|
} else {
|
|
iterateConst(subnodep);
|
|
}
|
|
comma = true;
|
|
argNum++;
|
|
}
|
|
if (nodep->withp()) {
|
|
if (comma) puts(", ");
|
|
iterateConst(nodep->withp());
|
|
}
|
|
puts(")");
|
|
}
|
|
void visit(AstLambdaArgRef* nodep) override { putbs(nodep->nameProtect()); }
|
|
void visit(AstWith* nodep) override {
|
|
// With uses a C++11 lambda
|
|
putnbs(nodep, "[&](");
|
|
if (auto* const argrefp = nodep->indexArgRefp()) {
|
|
putnbs(argrefp, argrefp->dtypep()->cType(argrefp->nameProtect(), false, false));
|
|
puts(", ");
|
|
}
|
|
if (auto* const argrefp = nodep->valueArgRefp()) {
|
|
putnbs(argrefp, argrefp->dtypep()->cType(argrefp->nameProtect(), false, false));
|
|
}
|
|
puts(") -> ");
|
|
putnbs(nodep, nodep->dtypep()->cType("", false, false));
|
|
puts(" {\n");
|
|
VL_RESTORER(m_createdScopeHash);
|
|
iterateAndNextConstNull(nodep->exprp());
|
|
puts("}\n");
|
|
}
|
|
void visit(AstCase* nodep) override { // LCOV_EXCL_LINE
|
|
// In V3Case...
|
|
nodep->v3fatalSrc("Case statements should have been reduced out");
|
|
}
|
|
void visit(AstComment* nodep) override {
|
|
string at;
|
|
if (nodep->showAt()) {
|
|
at = " at " + nodep->fileline()->ascii();
|
|
// If protecting, passthru less information about the design
|
|
if (!v3Global.opt.protectIds()) return;
|
|
}
|
|
if (!(nodep->protect() && v3Global.opt.protectIds())) {
|
|
putsDecoration(nodep, "// "s + nodep->name() + at + "\n");
|
|
}
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
void visit(AstCoverOtherDecl* nodep) override {
|
|
emitCoverOtherDeclInsert(nodep);
|
|
if (coverageUsesLocalCounter(nodep)) {
|
|
// Some task bodies are cloned into a caller module where the
|
|
// original instance-local counter is not addressable. Those clones
|
|
// fall back to the global bin number, so register that slot too.
|
|
emitCoverOtherDeclInsert(nodep, true);
|
|
}
|
|
}
|
|
void visit(AstCoverToggleDecl* nodep) override {
|
|
putns(nodep, "vlSelf->__vlCoverToggleInsert("); // As Declared in emitCoverageDecl
|
|
puts(cvtToStr(nodep->range().right()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->range().left()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->range().ranged()));
|
|
puts(", ");
|
|
putCoverageArray(nodep);
|
|
puts(" + ");
|
|
puts(cvtToStr(coverageBinNum(nodep)));
|
|
// first controls whether duplicate module instances are collapsed in
|
|
// the default coverage view. The following flag says whether countp
|
|
// points to an object-local counter; only those counters must be kept
|
|
// real for later instances so forcePerInstance can split hierarchy.
|
|
puts(", first"); // Enable, passed from __Vconfigure parameter
|
|
puts(", ");
|
|
puts(coverageUsesLocalCounter(nodep) ? "true" : "false");
|
|
puts(", ");
|
|
putsQuoted(protect(nodep->fileline()->filename()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->fileline()->lineno()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->fileline()->firstColumn()));
|
|
puts(", ");
|
|
putsQuoted((!nodep->hier().empty() ? "." : "")
|
|
+ VIdProtect::protectWordsIf(nodep->hier(), nodep->protect()));
|
|
puts(", ");
|
|
putsQuoted(VIdProtect::protectWordsIf(nodep->page(), nodep->protect()));
|
|
puts(", ");
|
|
putsQuoted(VIdProtect::protectWordsIf(nodep->comment(), nodep->protect()));
|
|
puts(");\n");
|
|
}
|
|
void visit(AstCoverInc* nodep) override {
|
|
if (VN_IS(nodep->declp(), CoverOtherDecl)) {
|
|
if (v3Global.opt.threads() > 1) {
|
|
putns(nodep, "");
|
|
putCoverageArray(nodep->declp());
|
|
puts("[");
|
|
puts(cvtToStr(coverageBinNum(nodep->declp())));
|
|
puts("].fetch_add(1, std::memory_order_relaxed);\n");
|
|
} else {
|
|
putns(nodep, "++(");
|
|
putCoverageArray(nodep->declp());
|
|
puts("[");
|
|
puts(cvtToStr(coverageBinNum(nodep->declp())));
|
|
puts("]);\n");
|
|
}
|
|
} else {
|
|
puts("VL_COV_TOGGLE_CHG_");
|
|
if (v3Global.opt.threads() > 1) {
|
|
puts("MT_");
|
|
} else {
|
|
puts("ST_");
|
|
}
|
|
emitIQW(nodep->toggleExprp());
|
|
puts("(");
|
|
// Each bit of variable has 2 counters, so the size of variable is half of the size of
|
|
// coverpoint
|
|
puts(cvtToStr(nodep->declp()->size() / 2));
|
|
puts(", ");
|
|
// Toggle update uses the same object-local counter array that
|
|
// __vlCoverToggleInsert registered.
|
|
putCoverageArray(nodep->declp());
|
|
puts(" + ");
|
|
puts(cvtToStr(coverageBinNum(nodep->declp())));
|
|
puts(", ");
|
|
iterateConst(nodep->toggleExprp());
|
|
puts(", ");
|
|
iterateConst(nodep->toggleCovExprp());
|
|
puts(");\n");
|
|
}
|
|
}
|
|
void visit(AstDisableFork* nodep) override { putns(nodep, "vlProcess->disableFork();\n"); }
|
|
void visit(AstCReturn* nodep) override {
|
|
putns(nodep, "return (");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(");\n");
|
|
}
|
|
void visit(AstDisplay* nodep) override {
|
|
string text = nodep->fmtp()->text();
|
|
if (nodep->addNewline()) text += "\n";
|
|
displayNode(nodep, nodep->fmtp(), text, nodep->fmtp()->exprsp(), false);
|
|
}
|
|
void visit(AstDumpCtl* nodep) override {
|
|
switch (nodep->ctlType()) {
|
|
case VDumpCtlType::FILE:
|
|
putns(nodep, "vlSymsp->_vm_contextp__->dumpfile(");
|
|
iterateConst(nodep->exprp());
|
|
puts(");\n");
|
|
break;
|
|
case VDumpCtlType::VARS:
|
|
// We ignore number of levels to dump in exprp()
|
|
if (v3Global.opt.trace()) {
|
|
putns(nodep, "vlSymsp->_traceDumpOpen();\n");
|
|
} else {
|
|
putns(nodep, "VL_PRINTF_MT(\"-Info: ");
|
|
puts(V3OutFormatter::quoteNameControls(protect(nodep->fileline()->filename())));
|
|
puts(":");
|
|
puts(cvtToStr(nodep->fileline()->lineno()));
|
|
puts(": $dumpvar ignored, as Verilated without --trace");
|
|
puts("\\n\");\n");
|
|
}
|
|
break;
|
|
case VDumpCtlType::ALL:
|
|
// $dumpall currently ignored
|
|
break;
|
|
case VDumpCtlType::FLUSH:
|
|
// $dumpall currently ignored; would need rework of VCD single thread,
|
|
// or flag we pass-through to next eval() iteration
|
|
break;
|
|
case VDumpCtlType::LIMIT:
|
|
// $dumplimit currently ignored
|
|
break;
|
|
case VDumpCtlType::OFF:
|
|
// Currently ignored as both Vcd and Fst do not support them, as would need "X" dump
|
|
break;
|
|
case VDumpCtlType::ON:
|
|
// Currently ignored as $dumpoff is also ignored
|
|
break;
|
|
default: nodep->v3fatalSrc("Bad case, unexpected " << nodep->ctlType().ascii());
|
|
}
|
|
}
|
|
void visit(AstScopeName* nodep) override {
|
|
// For use under AstCCalls for dpiImports. ScopeNames under
|
|
// displays are handled in AstDisplay
|
|
if (!nodep->dpiExport()) {
|
|
// this is where the DPI import context scope is set
|
|
const string scope = nodep->scopeDpiName();
|
|
putnbs(nodep, "(vlSymsp->" + protect("__Vscopep_" + scope) + ")");
|
|
}
|
|
}
|
|
void visit(AstSFormat* nodep) override {
|
|
displayNode(nodep, nodep->fmtp(), nodep->fmtp()->text(), nodep->fmtp()->exprsp(), false);
|
|
}
|
|
void visit(AstSFormatF* nodep) override {
|
|
displayNode(nodep, nodep, nodep->text(), nodep->exprsp(), false);
|
|
}
|
|
void visit(AstFScanF* nodep) override {
|
|
displayNode(nodep, nullptr, nodep->text(), nodep->exprsp(), true);
|
|
}
|
|
void visit(AstSScanF* nodep) override {
|
|
displayNode(nodep, nullptr, nodep->text(), nodep->exprsp(), true);
|
|
}
|
|
void visit(AstValuePlusArgs* nodep) override {
|
|
putns(nodep, "VL_VALUEPLUSARGS_IN");
|
|
emitIQW(nodep->outp());
|
|
puts("(");
|
|
puts(cvtToStr(nodep->outp()->widthMin()));
|
|
puts(", ");
|
|
iterateConst(nodep->searchp());
|
|
puts(", ");
|
|
putbs("");
|
|
iterateAndNextConstNull(nodep->outp());
|
|
puts(")");
|
|
}
|
|
void visit(AstTestPlusArgs* nodep) override {
|
|
putns(nodep, "VL_TESTPLUSARGS_I(");
|
|
iterateConst(nodep->searchp());
|
|
puts(")");
|
|
}
|
|
void visit(AstFError* nodep) override {
|
|
putns(nodep, "VL_FERROR_I");
|
|
puts(nodep->strp()->isString() ? "N(" : "W(");
|
|
iterateAndNextConstNull(nodep->filep());
|
|
putbs(", ");
|
|
if (nodep->strp()->isWide()) {
|
|
puts(cvtToStr(nodep->strp()->widthWords()));
|
|
putbs(", ");
|
|
}
|
|
iterateAndNextConstNull(nodep->strp());
|
|
puts(")");
|
|
}
|
|
void visit(AstFGetS* nodep) override {
|
|
emitOpName(nodep, nodep->emitC(), nodep->strgp(), nodep->filep(), nullptr);
|
|
}
|
|
void visit(AstFOpen* nodep) override {
|
|
putns(nodep, "VL_FOPEN_NN(");
|
|
iterateConst(nodep->filenamep());
|
|
putbs(", ");
|
|
iterateConst(nodep->modep());
|
|
puts(");\n");
|
|
}
|
|
void visit(AstFOpenMcd* nodep) override {
|
|
putns(nodep, "VL_FOPEN_MCD_N(");
|
|
iterateConst(nodep->filenamep());
|
|
puts(");\n");
|
|
}
|
|
void visit(AstNodeReadWriteMem* nodep) override {
|
|
putns(nodep, nodep->cFuncPrefixp());
|
|
puts("N(");
|
|
puts(nodep->isHex() ? "true" : "false");
|
|
putbs(", ");
|
|
// Need real storage width
|
|
puts(cvtToStr(nodep->memp()->dtypep()->subDTypep()->widthMin()));
|
|
uint32_t array_lo = 0;
|
|
{
|
|
const AstVarRef* const varrefp = VN_CAST(nodep->memp(), VarRef);
|
|
if (!varrefp) {
|
|
nodep->v3error(nodep->verilogKwd() << " loading non-variable");
|
|
} else if (VN_IS(varrefp->varp()->dtypeSkipRefp(), AssocArrayDType)) {
|
|
// nodep->memp() below will when verilated code is compiled create a C++ template
|
|
} else if (const AstUnpackArrayDType* const adtypep
|
|
= VN_CAST(varrefp->varp()->dtypeSkipRefp(), UnpackArrayDType)) {
|
|
putbs(", ");
|
|
puts(cvtToStr(varrefp->varp()->dtypep()->arrayUnpackedElements()));
|
|
array_lo = adtypep->lo();
|
|
putbs(", ");
|
|
puts(cvtToStr(array_lo));
|
|
} else {
|
|
nodep->v3error(nodep->verilogKwd()
|
|
<< " loading other than unpacked/associative-array variable");
|
|
}
|
|
}
|
|
putbs(", ");
|
|
iterateConst(nodep->filenamep());
|
|
putbs(", ");
|
|
{
|
|
const bool need_ptr = !VN_IS(nodep->memp()->dtypep(), AssocArrayDType);
|
|
if (need_ptr) puts(" &(");
|
|
iterateAndNextConstNull(nodep->memp());
|
|
if (need_ptr) puts(")");
|
|
}
|
|
putbs(", ");
|
|
if (nodep->lsbp()) {
|
|
iterateAndNextConstNull(nodep->lsbp());
|
|
} else {
|
|
puts(cvtToStr(array_lo));
|
|
}
|
|
putbs(", ");
|
|
if (nodep->msbp()) {
|
|
iterateAndNextConstNull(nodep->msbp());
|
|
} else {
|
|
puts("~0ULL");
|
|
}
|
|
puts(");\n");
|
|
}
|
|
void visit(AstFClose* nodep) override {
|
|
putns(nodep, "VL_FCLOSE_I(");
|
|
iterateAndNextConstNull(nodep->filep());
|
|
puts("); ");
|
|
}
|
|
void visit(AstFFlush* nodep) override {
|
|
if (!nodep->filep()) {
|
|
putns(nodep, "VL_FFLUSH_MT();");
|
|
} else {
|
|
putns(nodep, "VL_FFLUSH_I(");
|
|
iterateAndNextConstNull(nodep->filep());
|
|
puts(");\n");
|
|
}
|
|
}
|
|
void visit(AstFSeek* nodep) override {
|
|
putns(nodep, "(VL_FSEEK_I(");
|
|
iterateAndNextConstNull(nodep->filep());
|
|
puts(", ");
|
|
iterateAndNextConstNull(nodep->offset());
|
|
puts(", ");
|
|
iterateAndNextConstNull(nodep->operation());
|
|
puts(") == -1 ? -1 : 0)");
|
|
}
|
|
void visit(AstFTell* nodep) override {
|
|
putns(nodep, "VL_FTELL_I(");
|
|
iterateAndNextConstNull(nodep->filep());
|
|
puts(")");
|
|
}
|
|
void visit(AstFRewind* nodep) override {
|
|
putns(nodep, "(VL_FSEEK_I(");
|
|
iterateAndNextConstNull(nodep->filep());
|
|
puts(", 0, 0) == -1 ? -1 : 0)");
|
|
}
|
|
void visit(AstFRead* nodep) override {
|
|
putns(nodep, "VL_FREAD_I(");
|
|
puts(cvtToStr(nodep->memp()->widthMin())); // Need real storage width
|
|
putbs(", ");
|
|
uint32_t array_lo = 0;
|
|
uint32_t array_size = 0;
|
|
{
|
|
const AstVarRef* const varrefp = VN_CAST(nodep->memp(), VarRef);
|
|
if (!varrefp) {
|
|
nodep->v3error(nodep->verilogKwd() << " loading non-variable");
|
|
} else if (VN_CAST(varrefp->varp()->dtypeSkipRefp(), BasicDType)) {
|
|
} else if (const AstUnpackArrayDType* const adtypep
|
|
= VN_CAST(varrefp->varp()->dtypeSkipRefp(), UnpackArrayDType)) {
|
|
array_lo = adtypep->lo();
|
|
array_size = adtypep->elementsConst();
|
|
} else {
|
|
nodep->v3error(nodep->verilogKwd()
|
|
<< " loading other than unpacked-array variable");
|
|
}
|
|
}
|
|
puts(cvtToStr(array_lo));
|
|
putbs(", ");
|
|
puts(cvtToStr(array_size));
|
|
putbs(", ");
|
|
puts("&(");
|
|
iterateAndNextConstNull(nodep->memp());
|
|
puts(")");
|
|
putbs(", ");
|
|
iterateAndNextConstNull(nodep->filep());
|
|
putbs(", ");
|
|
if (nodep->startp()) {
|
|
iterateAndNextConstNull(nodep->startp());
|
|
} else {
|
|
puts(cvtToStr(array_lo));
|
|
}
|
|
putbs(", ");
|
|
if (nodep->countp()) {
|
|
iterateAndNextConstNull(nodep->countp());
|
|
} else {
|
|
puts(cvtToStr(array_size));
|
|
}
|
|
puts(")");
|
|
}
|
|
void visit(AstStackTraceF* nodep) override { putns(nodep, "VL_STACKTRACE_N()"); }
|
|
void visit(AstStackTraceT* nodep) override { putns(nodep, "VL_STACKTRACE();\n"); }
|
|
void visit(AstSystemT* nodep) override {
|
|
putns(nodep, "(void)VL_SYSTEM_I");
|
|
emitIQW(nodep->lhsp());
|
|
puts("(");
|
|
if (nodep->lhsp()->isWide()) {
|
|
puts(cvtToStr(nodep->lhsp()->widthWords()));
|
|
putbs(", ");
|
|
}
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(");\n");
|
|
}
|
|
void visit(AstSystemF* nodep) override {
|
|
putns(nodep, "VL_SYSTEM_I");
|
|
emitIQW(nodep->lhsp());
|
|
puts("(");
|
|
if (nodep->lhsp()->isWide()) {
|
|
puts(cvtToStr(nodep->lhsp()->widthWords()));
|
|
putbs(", ");
|
|
}
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(")");
|
|
}
|
|
void visit(AstStmtExpr* nodep) override {
|
|
if (VN_IS(nodep->exprp()->dtypep()->skipRefp(), VoidDType)) {
|
|
putns(nodep, "");
|
|
} else {
|
|
putns(nodep, "std::ignore = ");
|
|
}
|
|
iterateConst(nodep->exprp());
|
|
puts(";\n");
|
|
}
|
|
void visit(AstJumpBlock* nodep) override {
|
|
// Allocate label number
|
|
const size_t n = m_labelNumbers.size();
|
|
const bool newEntry = m_labelNumbers.emplace(nodep, n).second;
|
|
UASSERT_OBJ(newEntry, nodep, "AstJumpBlock visited twide");
|
|
// Emit
|
|
putns(nodep, "{\n"); // Make it visually obvious label jumps outside these
|
|
VL_RESTORER(m_createdScopeHash);
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
|
puts("__Vlabel" + std::to_string(n) + ": ;\n");
|
|
puts("}\n");
|
|
}
|
|
void visit(AstJumpGo* nodep) override {
|
|
// Retrieve target label number - must already exist (from enclosing AstJumpBlock)
|
|
const size_t n = m_labelNumbers.at(nodep->blockp());
|
|
// Emit
|
|
putns(nodep, "goto __Vlabel" + std::to_string(n) + ";\n");
|
|
}
|
|
void visit(AstLoop* nodep) override {
|
|
UASSERT_OBJ(!nodep->contsp(), nodep, "'contsp' only used before LinkJump");
|
|
VL_RESTORER(m_createdScopeHash);
|
|
// Special case when the AstLoopTest is first for output readability
|
|
if (AstLoopTest* const testp = VN_CAST(nodep->stmtsp(), LoopTest)) {
|
|
putns(nodep, "while (");
|
|
iterateConst(testp->condp());
|
|
puts(") {\n");
|
|
iterateAndNextConstNull(testp->nextp());
|
|
puts("}\n");
|
|
return;
|
|
}
|
|
// Special case when the AstLoopTest is last for output readability
|
|
if (AstNode* lastp = nodep->stmtsp()) {
|
|
while (AstNode* const nextp = lastp->nextp()) lastp = nextp;
|
|
if (AstLoopTest* const testp = VN_CAST(lastp, LoopTest)) {
|
|
putns(nodep, "do {\n");
|
|
for (AstNode* p = nodep->stmtsp(); p != lastp; p = p->nextp()) iterateConst(p);
|
|
puts("} while (");
|
|
iterateConst(testp->condp());
|
|
puts(");\n");
|
|
return;
|
|
}
|
|
}
|
|
// Emit generic case directly
|
|
putns(nodep, "while (true) {\n");
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
|
puts("}\n");
|
|
}
|
|
void visit(AstLoopTest* nodep) override {
|
|
VL_RESTORER(m_createdScopeHash);
|
|
putns(nodep, "if (!(");
|
|
iterateAndNextConstNull(nodep->condp());
|
|
puts(")) break;\n");
|
|
}
|
|
void visit(AstCLocalScope* nodep) override {
|
|
putns(nodep, "{\n");
|
|
VL_RESTORER(m_createdScopeHash);
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
|
puts("}\n");
|
|
}
|
|
void visit(AstNodeIf* nodep) override {
|
|
putns(nodep, "if (");
|
|
if (!nodep->branchPred().unknown()) {
|
|
puts(nodep->branchPred().ascii());
|
|
puts("(("); // Two parens, so that VL_UNLIKELY((class<foo,bar>)) works
|
|
}
|
|
iterateAndNextConstNull(nodep->condp());
|
|
if (!nodep->branchPred().unknown()) puts("))");
|
|
puts(") {\n");
|
|
{
|
|
VL_RESTORER(m_createdScopeHash);
|
|
iterateAndNextConstNull(nodep->thensp());
|
|
}
|
|
puts("}");
|
|
if (!nodep->elsesp()) {
|
|
puts("\n");
|
|
} else {
|
|
if (VN_IS(nodep->elsesp(), NodeIf) && !nodep->elsesp()->nextp()) {
|
|
puts(" else ");
|
|
iterateAndNextConstNull(nodep->elsesp());
|
|
} else {
|
|
VL_RESTORER(m_createdScopeHash);
|
|
puts(" else {\n");
|
|
iterateAndNextConstNull(nodep->elsesp());
|
|
puts("}\n");
|
|
}
|
|
}
|
|
}
|
|
void visit(AstExprStmt* nodep) override {
|
|
VL_RESTORER(m_createdScopeHash);
|
|
const bool containsAwait = nodep->exists([](AstCAwait*) -> bool { return true; });
|
|
if (containsAwait) {
|
|
UASSERT_OBJ(m_cfuncp && m_cfuncp->isCoroutine(), nodep,
|
|
"AstExprStmt with CAwait must be in coroutine");
|
|
putnbs(nodep, "(co_await ([&]() -> VlCoroutine {\n");
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
|
puts("co_return;\n");
|
|
if (!nodep->hasResult()) {
|
|
puts("}()))");
|
|
return;
|
|
}
|
|
puts("}()), ");
|
|
iterateAndNextConstNull(nodep->resultp());
|
|
puts(")");
|
|
return;
|
|
}
|
|
// GCC allows compound statements in expressions, but this is not standard.
|
|
// So we use an immediate-evaluation lambda and comma operator
|
|
putnbs(nodep, "([&]() {\n");
|
|
if (!nodep->hasResult()) {
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
|
puts("}())");
|
|
return;
|
|
}
|
|
iterateAndNextConstNull(nodep->stmtsp());
|
|
puts("}(), ");
|
|
iterateAndNextConstNull(nodep->resultp());
|
|
puts(")");
|
|
}
|
|
void visit(AstStop* nodep) override {
|
|
putns(nodep, "VL_STOP_MT(");
|
|
putsQuoted(protect(nodep->fileline()->filename()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->fileline()->lineno()));
|
|
puts(", \"\"");
|
|
if (nodep->isFatal()) puts(", false");
|
|
puts(");\n");
|
|
}
|
|
void visit(AstFinish* nodep) override {
|
|
// Disable all the forks so they don't operate after simulation is finished.
|
|
if (m_cfuncp && m_cfuncp->needProcess()) putns(nodep, "vlProcess->disableFork();\n");
|
|
putns(nodep, "VL_FINISH_MT(");
|
|
putsQuoted(protect(nodep->fileline()->filename()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->fileline()->lineno()));
|
|
puts(", \"\");\n");
|
|
}
|
|
void visit(AstFinishFork* nodep) override {
|
|
// Disable all the forks so they don't operate after simulation is finished.
|
|
if (m_cfuncp && m_cfuncp->needProcess()) putns(nodep, "vlProcess->disableFork();\n");
|
|
putns(nodep, "VL_FINISH_MT(");
|
|
putsQuoted(protect(nodep->fileline()->filename()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->fileline()->lineno()));
|
|
puts(", \"\");\n");
|
|
if (m_cfuncp->isCoroutine()) {
|
|
putns(nodep, "co_return;\n");
|
|
} else {
|
|
putns(nodep, "return;\n");
|
|
}
|
|
}
|
|
void visit(AstPrintTimeScale* nodep) override {
|
|
putns(nodep, "VL_PRINTTIMESCALE(");
|
|
putsQuoted(protect(nodep->prettyName()));
|
|
puts(", ");
|
|
putsQuoted(nodep->timeunit().ascii());
|
|
puts(", vlSymsp->_vm_contextp__);\n");
|
|
}
|
|
void visit(AstRand* nodep) override {
|
|
emitOpName(nodep, nodep->emitC(), nodep->seedp(), nullptr, nullptr);
|
|
}
|
|
void visit(AstRandRNG* nodep) override {
|
|
emitOpName(nodep, nodep->emitC(), nullptr, nullptr, nullptr);
|
|
}
|
|
void visit(AstTime* nodep) override {
|
|
putns(nodep, "VL_TIME_UNITED_Q(");
|
|
UASSERT_OBJ(!nodep->timeunit().isNone(), nodep, "$time has no units");
|
|
const double time
|
|
= nodep->timeunit().multiplier() / v3Global.rootp()->timeprecision().multiplier();
|
|
UASSERT_OBJ(time >= 1, nodep, "TimeQ is less than 1, will result in division by zero");
|
|
puts(cvtToStr(time));
|
|
puts(")");
|
|
}
|
|
void visit(AstTimeD* nodep) override {
|
|
putns(nodep, "VL_TIME_UNITED_D(");
|
|
UASSERT_OBJ(!nodep->timeunit().isNone(), nodep, "$realtime has no units");
|
|
puts(cvtToStr(nodep->timeunit().multiplier()
|
|
/ v3Global.rootp()->timeprecision().multiplier()));
|
|
puts(")");
|
|
}
|
|
void visit(AstGetInitialRandomSeed* nodep) override {
|
|
putns(nodep, "vlSymsp->_vm_contextp__->randSeed()");
|
|
}
|
|
void visit(AstTimeFormat* nodep) override {
|
|
putns(nodep, "VL_TIMEFORMAT_IINI(");
|
|
if (nodep->unitsp()) {
|
|
puts("true, ");
|
|
iterateAndNextConstNull(nodep->unitsp());
|
|
} else {
|
|
puts("false, 0");
|
|
}
|
|
puts(", ");
|
|
if (nodep->precisionp()) {
|
|
puts("true, ");
|
|
iterateAndNextConstNull(nodep->precisionp());
|
|
} else {
|
|
puts("false, 0");
|
|
}
|
|
puts(", ");
|
|
if (nodep->suffixp()) {
|
|
puts("true, ");
|
|
iterateAndNextConstNull(nodep->suffixp());
|
|
} else {
|
|
puts("false, \"\"");
|
|
}
|
|
puts(", ");
|
|
if (nodep->widthp()) {
|
|
puts("true, ");
|
|
iterateAndNextConstNull(nodep->widthp());
|
|
} else {
|
|
puts("false, 0");
|
|
}
|
|
puts(", vlSymsp->_vm_contextp__);\n");
|
|
}
|
|
void visit(AstTimePrecision* nodep) override {
|
|
putns(nodep, "vlSymsp->_vm_contextp__->timeprecision()");
|
|
}
|
|
|
|
// Nodes involing AstText
|
|
void visit(AstText* nodep) override {
|
|
// All Text should be under TextBlock/CStmt/CStmtUser/CExpr/CExprUser
|
|
nodep->v3fatalSrc("Text node in unexpected position");
|
|
}
|
|
void visit(AstTextBlock* nodep) override {
|
|
putnbs(nodep, "");
|
|
puts(nodep->prefix());
|
|
emitNodesWithText(nodep->nodesp(), false, true, nodep->separator());
|
|
puts(nodep->suffix());
|
|
}
|
|
void visit(AstCStmt* nodep) override {
|
|
putnbs(nodep, "");
|
|
emitNodesWithText(nodep->nodesp(), false, true, "");
|
|
ensureNewLine();
|
|
}
|
|
void visit(AstCExpr* nodep) override {
|
|
putnbs(nodep, "");
|
|
emitNodesWithText(nodep->nodesp(), false, true, "");
|
|
}
|
|
void visit(AstCStmtUser* nodep) override {
|
|
putnbs(nodep, "");
|
|
ofp()->putsNoTracking("\n");
|
|
if (nodep->fromDollarC() && v3Global.opt.decoration() && !v3Global.opt.protectIds()) {
|
|
ofp()->putsNoTracking("// $c statement at " + nodep->fileline()->ascii() + "\n");
|
|
}
|
|
emitNodesWithText(nodep->nodesp(), m_useSelfForThis, false, "");
|
|
puts("\n");
|
|
}
|
|
void visit(AstCExprUser* nodep) override {
|
|
putnbs(nodep, "");
|
|
ofp()->putsNoTracking("\n");
|
|
if (/* is always from $c */ v3Global.opt.decoration() && !v3Global.opt.protectIds()) {
|
|
ofp()->putsNoTracking(
|
|
(nodep->isPure() ? "// $cpure expression at " : "// $c expression at ")
|
|
+ nodep->fileline()->ascii() + "\n");
|
|
}
|
|
emitNodesWithText(nodep->nodesp(), m_useSelfForThis, false, "");
|
|
puts("\n");
|
|
}
|
|
|
|
// Operators
|
|
void visit(AstNodeTermop* nodep) override {
|
|
emitOpName(nodep, nodep->emitC(), nullptr, nullptr, nullptr);
|
|
}
|
|
void visit(AstNodeUniop* nodep) override {
|
|
if (nodep->emitCheckMaxWords()
|
|
&& (nodep->widthWords() > VL_MULS_MAX_WORDS
|
|
|| nodep->lhsp()->widthWords() > VL_MULS_MAX_WORDS)) {
|
|
nodep->v3warn(
|
|
E_UNSUPPORTED,
|
|
"Unsupported: "
|
|
<< nodep->prettyOperatorName() << " operator of " << nodep->width()
|
|
<< " bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h");
|
|
}
|
|
if (emitSimpleOk(nodep)) {
|
|
putnbs(nodep, "(");
|
|
puts(nodep->emitSimpleOperator());
|
|
puts(" ");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(")");
|
|
} else {
|
|
emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nullptr, nullptr);
|
|
}
|
|
}
|
|
void visit(AstNodeBiop* nodep) override {
|
|
if (nodep->emitCheckMaxWords() && nodep->widthWords() > VL_MULS_MAX_WORDS) {
|
|
nodep->v3warn(
|
|
E_UNSUPPORTED,
|
|
"Unsupported: "
|
|
<< nodep->prettyOperatorName() << " operator of " << nodep->width()
|
|
<< " bits exceeds hardcoded limit VL_MULS_MAX_WORDS in verilatedos.h");
|
|
}
|
|
if (emitSimpleOk(nodep)) {
|
|
putnbs(nodep, "(");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(" ");
|
|
putbs(nodep->emitSimpleOperator());
|
|
puts(" ");
|
|
iterateAndNextConstNull(nodep->rhsp());
|
|
puts(")");
|
|
} else {
|
|
emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->rhsp(), nullptr);
|
|
}
|
|
}
|
|
void visit(AstNodeTriop* nodep) override {
|
|
UASSERT_OBJ(!emitSimpleOk(nodep), nodep, "Triop cannot be described in a simple way");
|
|
emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->rhsp(), nodep->thsp());
|
|
}
|
|
void visit(AstCvtPackString* nodep) override { emitCvtPackStr(nodep->lhsp()); }
|
|
void visit(AstRedXor* nodep) override {
|
|
if (nodep->lhsp()->isWide()) {
|
|
visit(static_cast<AstNodeUniop*>(nodep));
|
|
} else {
|
|
const AstVarRef* const vrefp = VN_CAST(nodep->lhsp(), VarRef);
|
|
const int widthPow2 = vrefp ? vrefp->varp()->dtypep()->widthPow2()
|
|
: nodep->lhsp()->dtypep()->widthPow2();
|
|
UASSERT_OBJ(widthPow2 > 1, nodep,
|
|
"Reduction over single bit value should have been folded");
|
|
putnbs(nodep, "VL_REDXOR_");
|
|
puts(cvtToStr(widthPow2));
|
|
puts("(");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(")");
|
|
}
|
|
}
|
|
void visit(AstCCast* nodep) override {
|
|
// Extending a value of the same word width is just a NOP.
|
|
if (const AstClassRefDType* const classDtypep
|
|
= VN_CAST(nodep->dtypep()->skipRefp(), ClassRefDType)) {
|
|
putns(nodep, "(" + classDtypep->cType("", false, false) + ")(");
|
|
} else if (nodep->size() <= VL_BYTESIZE) {
|
|
putns(nodep, "(CData)(");
|
|
} else if (nodep->size() <= VL_SHORTSIZE) {
|
|
putns(nodep, "(SData)(");
|
|
} else if (nodep->size() <= VL_IDATASIZE) {
|
|
putns(nodep, "(IData)(");
|
|
} else {
|
|
putns(nodep, "(QData)(");
|
|
}
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(")");
|
|
}
|
|
void visit(AstCond* nodep) override {
|
|
// Widths match up already, so we'll just use C++'s operator w/o any temps.
|
|
if (nodep->thenp()->isWide()) {
|
|
emitOpName(nodep, nodep->emitC(), nodep->condp(), nodep->thenp(), nodep->elsep());
|
|
} else {
|
|
putnbs(nodep, "(");
|
|
iterateAndNextConstNull(nodep->condp());
|
|
putbs(" ? ");
|
|
iterateAndNextConstNull(nodep->thenp());
|
|
putbs(" : ");
|
|
iterateAndNextConstNull(nodep->elsep());
|
|
puts(")");
|
|
}
|
|
}
|
|
void visit(AstMatchMasked* nodep) override {
|
|
emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->matchp(), nullptr);
|
|
}
|
|
void visit(AstMemberSel* nodep) override {
|
|
const AstVar* const varp = nodep->varp();
|
|
const bool dereferenceCovergroupRef
|
|
= varp->covergroupRefMember() && nodep->access().isReadOrRW();
|
|
if (dereferenceCovergroupRef) putnbs(nodep, "(*");
|
|
iterateAndNextConstNull(nodep->fromp());
|
|
putnbs(nodep, "->");
|
|
if (varp->isIfaceRef()) {
|
|
// varp is the __Viftop companion (e.g. "tx__Viftop"); use the
|
|
// MemberSel name which matches the cell's C++ member (e.g. "tx").
|
|
puts(nodep->nameProtect());
|
|
} else {
|
|
puts(varp->nameProtect());
|
|
}
|
|
if (dereferenceCovergroupRef) puts(")");
|
|
}
|
|
void visit(AstStructSel* nodep) override {
|
|
iterateAndNextConstNull(nodep->fromp());
|
|
putnbs(nodep, ".");
|
|
puts(nodep->nameProtect());
|
|
}
|
|
void visit(AstNullCheck* nodep) override {
|
|
putns(nodep, "VL_NULL_CHECK(");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(", ");
|
|
putsQuoted(protect(nodep->fileline()->filename()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->fileline()->lineno()));
|
|
puts(")");
|
|
}
|
|
void visit(AstNewCopy* nodep) override {
|
|
// Polymorphic shallow clone: preserves runtime type via virtual clone()
|
|
// VL_NULL_CHECK enforces null check per IEEE 1800-2023 8.7
|
|
putns(nodep, "VL_NULL_CHECK(");
|
|
if (VN_IS(nodep->rhsp(), Const) && VN_AS(nodep->rhsp(), Const)->isNull()) {
|
|
// V3Const folded rhs to null: emit a typed empty ref so VL_NULL_CHECK fires
|
|
const AstClassRefDType* const refDTypep
|
|
= VN_CAST(nodep->dtypep()->skipRefp(), ClassRefDType);
|
|
puts(refDTypep->cType("", false, false) + "{}");
|
|
} else {
|
|
iterateAndNextConstNull(nodep->rhsp());
|
|
}
|
|
puts(", ");
|
|
putsQuoted(protect(nodep->fileline()->filename()));
|
|
puts(", ");
|
|
puts(cvtToStr(nodep->fileline()->lineno()));
|
|
puts(").clone(vlSymsp->__Vm_deleter)");
|
|
}
|
|
void visit(AstSel* nodep) override {
|
|
// Note ASSIGN checks for this on a LHS
|
|
UASSERT_OBJ(nodep->widthMin() == nodep->widthConst(), nodep, "Width mismatch");
|
|
emitOpName(nodep, nodep->emitC(), nodep->fromp(), nodep->lsbp(), nullptr);
|
|
}
|
|
void visit(AstReplicate* nodep) override {
|
|
if (nodep->srcp()->widthMin() == 1 && !nodep->isWide()) {
|
|
UASSERT_OBJ((static_cast<int>(VN_AS(nodep->countp(), Const)->toUInt())
|
|
* nodep->srcp()->widthMin())
|
|
== nodep->widthMin(),
|
|
nodep, "Replicate non-constant or width miscomputed");
|
|
putns(nodep, "VL_REPLICATE_");
|
|
emitIQW(nodep);
|
|
puts("OI(");
|
|
if (nodep->srcp()) puts(cvtToStr(nodep->srcp()->widthMin()));
|
|
puts(", ");
|
|
iterateAndNextConstNull(nodep->srcp());
|
|
puts(", ");
|
|
iterateAndNextConstNull(nodep->countp());
|
|
puts(")");
|
|
} else {
|
|
emitOpName(nodep, nodep->emitC(), nodep->srcp(), nodep->countp(), nullptr);
|
|
}
|
|
}
|
|
void emitStreamR(AstStreamR* nodep, AstNode* parent) {
|
|
//TODO: This might need to handle more cases like the visit(AstStreamR) function
|
|
emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->rhsp(), nullptr);
|
|
}
|
|
void visit(AstStreamR* nodep) override {
|
|
//The parrent node of our AstStreamR will give just enough info for what streamR should
|
|
//output if nodep->backp() is not the parent then emitStreamR should have been used. throw
|
|
//an error
|
|
bool backpIsParent = (nodep->backp()->op1p() == nodep || nodep->backp()->op2p() == nodep);
|
|
UASSERT(backpIsParent, "can not find return type for streamR");
|
|
if ((VN_IS(nodep->backp()->dtypep()->skipRefp(), QueueDType))) {
|
|
emitOpName(nodep, "VL_STREAMR_%nq%lq%rq(%lw, %P, %li, %ri)", nodep->lhsp(),
|
|
nodep->rhsp(), nullptr);
|
|
} else if (VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) {
|
|
if (!((nodep->backp()->op1p() && nodep->backp()->op1p()->isWide())
|
|
|| (nodep->backp()->op2p() && nodep->backp()->op2p()->isWide()))) {
|
|
//If our lhsp is a queue make sure we streamR and return the correct type.
|
|
//If either side is wide or the previous node is string type dont use this case
|
|
emitOpName(nodep->backp(), "VL_STREAMR_%nq%lq%rq(%lw, %P, %li, %ri)",
|
|
nodep->lhsp(), nodep->rhsp(), nullptr);
|
|
} else {
|
|
emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->rhsp(), nullptr);
|
|
}
|
|
} else {
|
|
emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->rhsp(), nullptr);
|
|
}
|
|
}
|
|
void visit(AstStreamL* nodep) override {
|
|
// Attempt to use a "fast" stream function for slice size = power of 2
|
|
if (!nodep->isWide()) {
|
|
const uint32_t isPow2 = VN_AS(nodep->rhsp(), Const)->num().countOnes() == 1;
|
|
const uint32_t sliceSize = VN_AS(nodep->rhsp(), Const)->toUInt();
|
|
if (isPow2 && sliceSize <= (nodep->isQuad() ? sizeof(uint64_t) : sizeof(uint32_t))) {
|
|
putns(nodep, "VL_STREAML_FAST_");
|
|
bool usesQueue = false;
|
|
AstQueueDType* qtypep
|
|
= nodep->backp()->op2p()
|
|
? VN_CAST(nodep->backp()->op2p()->dtypep()->skipRefp(), QueueDType)
|
|
: nullptr;
|
|
if (VN_IS(nodep->backp(), Assign)
|
|
&& qtypep) { // If we are assigning to a queue then emit the correct symbol
|
|
puts("R"); // R for queue
|
|
usesQueue = true;
|
|
} else {
|
|
emitIQW(nodep);
|
|
}
|
|
emitIQW(nodep->lhsp());
|
|
puts("I(");
|
|
puts(cvtToStr(nodep->lhsp()->widthMin()));
|
|
puts(", ");
|
|
if (usesQueue) {
|
|
iterateAndNextConstNull(nodep->backp()->op2p());
|
|
puts(", ");
|
|
}
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(", ");
|
|
const uint32_t rd_log2 = V3Number::log2b(VN_AS(nodep->rhsp(), Const)->toUInt());
|
|
puts(cvtToStr(rd_log2) + ")");
|
|
return;
|
|
}
|
|
}
|
|
if (VN_IS(nodep->backp(), Assign)
|
|
&& VN_IS(nodep->backp()->op2p()->dtypep()->skipRefp(), QueueDType)) {
|
|
int queueWidth
|
|
= nodep->backp()->op2p()->dtypep()->subDTypep()->width(); //We need to know the
|
|
//width of both sides
|
|
emitOpName(nodep,
|
|
"VL_STREAML_%nq%lq%rq(%lw," + std::to_string(queueWidth)
|
|
+ ", %P, %li, %ri)",
|
|
nodep->lhsp(), nodep->rhsp(), nullptr);
|
|
} else {
|
|
emitOpName(nodep, nodep->emitC(), nodep->lhsp(), nodep->rhsp(), nullptr);
|
|
}
|
|
}
|
|
void visit(AstCastDynamic* nodep) override {
|
|
putnbs(nodep, "VL_CAST_DYNAMIC(");
|
|
iterateAndNextConstNull(nodep->fromp());
|
|
puts(", ");
|
|
iterateAndNextConstNull(nodep->top());
|
|
puts(")");
|
|
}
|
|
void visit(AstCountBits* nodep) override {
|
|
putnbs(nodep, "VL_COUNTBITS_");
|
|
emitIQW(nodep->lhsp());
|
|
puts("(");
|
|
puts(cvtToStr(nodep->lhsp()->widthMin()));
|
|
puts(", ");
|
|
if (nodep->lhsp()->isWide()) {
|
|
puts(cvtToStr(nodep->lhsp()->widthWords())); // Note argument width, not node width
|
|
// (which is always 32)
|
|
puts(", ");
|
|
}
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
puts(", ");
|
|
iterateAndNextConstNull(nodep->rhsp());
|
|
puts(", ");
|
|
iterateAndNextConstNull(nodep->thsp());
|
|
puts(", ");
|
|
iterateAndNextConstNull(nodep->fhsp());
|
|
puts(")");
|
|
}
|
|
void visit(AstInitItem* nodep) override { iterateChildrenConst(nodep); }
|
|
// Terminals
|
|
void visit(AstVarRef* nodep) override {
|
|
const AstVar* const varp = nodep->varp();
|
|
const AstNodeModule* const varModp = EmitCParentModule::get(varp);
|
|
const bool dereferenceCovergroupRef
|
|
= varp->covergroupRefMember() && nodep->access().isReadOrRW();
|
|
if (dereferenceCovergroupRef) putns(nodep, "(*");
|
|
if (varModp->isConstPool()) {
|
|
// Reference to constant pool variable
|
|
putns(nodep, EmitCUtil::topClassName() + "__ConstPool__");
|
|
} else if (varp->isStatic()) {
|
|
// Access static variable via the containing class
|
|
putns(nodep, EmitCUtil::prefixNameProtect(varModp) + "::");
|
|
} else if (VN_IS(varModp, Class) && varModp != m_modp) {
|
|
// Superclass member reference
|
|
putns(nodep, EmitCUtil::prefixNameProtect(varModp) + "::");
|
|
} else if (varp->isIfaceRef()) {
|
|
putns(nodep, nodep->selfPointerProtect(m_useSelfForThis));
|
|
return;
|
|
} else if (!nodep->selfPointer().isEmpty()) {
|
|
emitDereference(nodep, nodep->selfPointerProtect(m_useSelfForThis));
|
|
}
|
|
putns(nodep, nodep->varp()->nameProtect());
|
|
if (dereferenceCovergroupRef) puts(")");
|
|
}
|
|
void visit(AstAddrOfCFunc* nodep) override {
|
|
// Note: Can be thought to handle more, but this is all that is needed right now
|
|
const AstCFunc* const funcp = nodep->funcp();
|
|
UASSERT_OBJ(funcp->isLoose(), nodep, "Cannot take address of non-loose method");
|
|
putns(nodep, "&");
|
|
puts(funcNameProtect(funcp));
|
|
}
|
|
void visit(AstConst* nodep) override { //
|
|
if (m_wideTempRefp && nodep->isWide()) {
|
|
UASSERT_OBJ(m_wideTempRefp, nodep, "Wide Constant w/ no temp");
|
|
emitConstantW(nodep, m_wideTempRefp);
|
|
m_wideTempRefp = nullptr; // We used it, fail if set it a second time
|
|
} else {
|
|
emitConstant(nodep);
|
|
}
|
|
}
|
|
void visit(AstThisRef* nodep) override {
|
|
putnbs(nodep, nodep->dtypep()->cType("", false, false));
|
|
puts("{");
|
|
puts(VSelfPointerText::replaceThis(m_useSelfForThis, "this"));
|
|
puts("}");
|
|
}
|
|
void visit(AstNodeSel* nodep) override {
|
|
if (!VN_IS(nodep, ArraySel) && !VN_IS(nodep, WordSel)) {
|
|
visit(static_cast<AstNodeBiop*>(nodep));
|
|
return;
|
|
}
|
|
// ArraySel or WordSel
|
|
iterateAndNextConstNull(nodep->fromp());
|
|
// Special case constant index for readability
|
|
if (AstConst* const idxp = VN_CAST(nodep->bitp(), Const)) {
|
|
puts("[" + std::to_string(idxp->toUInt()) + "U]");
|
|
return;
|
|
}
|
|
putbs("[");
|
|
iterateAndNextConstNull(nodep->bitp());
|
|
puts("]");
|
|
}
|
|
|
|
//
|
|
void visit(AstConsAssoc* nodep) override {
|
|
putnbs(nodep, nodep->dtypep()->cType("", false, false));
|
|
puts("()");
|
|
if (nodep->defaultp()) {
|
|
putbs(".setDefault(");
|
|
iterateAndNextConstNull(nodep->defaultp());
|
|
puts(")");
|
|
}
|
|
}
|
|
void visit(AstSetAssoc* nodep) override {
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
putnbs(nodep, ".set(");
|
|
iterateAndNextConstNull(nodep->keyp());
|
|
puts(", ");
|
|
putbs("");
|
|
iterateAndNextConstNull(nodep->valuep());
|
|
puts(")");
|
|
}
|
|
void visit(AstConsWildcard* nodep) override {
|
|
putnbs(nodep, nodep->dtypep()->cType("", false, false));
|
|
puts("()");
|
|
if (nodep->defaultp()) {
|
|
putbs(".setDefault(");
|
|
iterateAndNextConstNull(nodep->defaultp());
|
|
puts(")");
|
|
}
|
|
}
|
|
void visit(AstSetWildcard* nodep) override {
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
putnbs(nodep, ".set(");
|
|
iterateAndNextConstNull(nodep->keyp());
|
|
puts(", ");
|
|
putbs("");
|
|
iterateAndNextConstNull(nodep->valuep());
|
|
puts(")");
|
|
}
|
|
void visit(AstConsDynArray* nodep) override {
|
|
putnbs(nodep, nodep->dtypep()->cType("", false, false));
|
|
if (!nodep->lhsp()) {
|
|
putns(nodep, "{}");
|
|
} else {
|
|
puts("::cons");
|
|
puts(nodep->lhsIsValue() ? "V" : "C");
|
|
if (nodep->rhsp()) puts(nodep->rhsIsValue() ? "V" : "C");
|
|
puts("(");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
if (nodep->rhsp()) {
|
|
puts(", ");
|
|
putbs("");
|
|
}
|
|
iterateAndNextConstNull(nodep->rhsp());
|
|
puts(")");
|
|
}
|
|
}
|
|
void visit(AstConsPackUOrStruct* nodep) override {
|
|
putnbs(nodep, nodep->dtypep()->cType("", false, false));
|
|
puts("{");
|
|
for (AstNode* memberp = nodep->membersp(); memberp; memberp = memberp->nextp()) {
|
|
iterateConst(memberp);
|
|
if (memberp->nextp()) puts(", ");
|
|
}
|
|
puts("}");
|
|
}
|
|
void visit(AstConsPackMember* nodep) override {
|
|
auto* const vdtypep = VN_AS(nodep->dtypep(), MemberDType);
|
|
putnbs(nodep, ".");
|
|
puts(vdtypep->name());
|
|
puts(" = ");
|
|
iterateConst(nodep->rhsp());
|
|
}
|
|
void visit(AstConsQueue* nodep) override {
|
|
putnbs(nodep, nodep->dtypep()->cType("", false, false));
|
|
if (!nodep->lhsp()) {
|
|
puts("{}");
|
|
} else {
|
|
puts("::cons");
|
|
puts(nodep->lhsIsValue() ? "V" : "C");
|
|
if (nodep->rhsp()) puts(nodep->rhsIsValue() ? "V" : "C");
|
|
puts("(");
|
|
iterateAndNextConstNull(nodep->lhsp());
|
|
if (nodep->rhsp()) {
|
|
puts(", ");
|
|
putbs("");
|
|
}
|
|
iterateAndNextConstNull(nodep->rhsp());
|
|
puts(")");
|
|
}
|
|
}
|
|
void visit(AstExecGraph* nodep) override {
|
|
// The location of the AstExecGraph within the containing AstCFunc is where we want to
|
|
// invoke the graph and wait for it to complete. Emitting the children does just that.
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
|
|
// Default
|
|
void visit(AstNode* nodep) override { // LCOV_EXCL_START
|
|
putns(nodep, "\n???? // "s + nodep->prettyTypeName() + "\n");
|
|
iterateChildrenConst(nodep);
|
|
if (!v3Global.opt.lintOnly()) { // An internal problem, so suppress
|
|
nodep->v3fatalSrc("Unknown node type reached emitter: " << nodep->prettyTypeName());
|
|
}
|
|
} // LCOV_EXCL_STOP
|
|
|
|
protected:
|
|
EmitCFunc() = default;
|
|
~EmitCFunc() override = default;
|
|
};
|
|
|
|
#endif // guard
|