Internals: Refector to create single V3Number::emitC. Fix some double emit issues (#6528).

This commit is contained in:
Wilson Snyder 2025-10-04 07:23:13 -04:00 committed by GitHub
parent a03d327e96
commit f09c30df35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 148 additions and 175 deletions

View File

@ -29,7 +29,6 @@
class EmitCConstInit VL_NOT_FINAL : public EmitCBaseVisitorConst { class EmitCConstInit VL_NOT_FINAL : public EmitCBaseVisitorConst {
// MEMBERS // MEMBERS
uint32_t m_unpackedWord = 0; uint32_t m_unpackedWord = 0;
bool m_inUnpacked = false;
// METHODS // METHODS
uint32_t tabModulus(const AstNodeDType* dtypep) { uint32_t tabModulus(const AstNodeDType* dtypep) {
@ -44,9 +43,7 @@ class EmitCConstInit VL_NOT_FINAL : public EmitCBaseVisitorConst {
protected: protected:
// VISITORS // VISITORS
void visit(AstInitArray* nodep) override { void visit(AstInitArray* nodep) override {
VL_RESTORER(m_inUnpacked);
VL_RESTORER(m_unpackedWord); VL_RESTORER(m_unpackedWord);
m_inUnpacked = true;
if (VN_IS(nodep->dtypep()->skipRefp(), AssocArrayDType)) { if (VN_IS(nodep->dtypep()->skipRefp(), AssocArrayDType)) {
// Note the double {{ initializer. The first { starts the initializer of the // Note the double {{ initializer. The first { starts the initializer of the
// VlAssocArray, and the second starts the initializer of m_storage within the // VlAssocArray, and the second starts the initializer of m_storage within the
@ -98,55 +95,9 @@ protected:
} // LCOV_EXCL_STOP } // LCOV_EXCL_STOP
void visit(AstConst* nodep) override { void visit(AstConst* nodep) override {
// TODO merge with EmitCFunc::emitConstant
const V3Number& num = nodep->num(); const V3Number& num = nodep->num();
UASSERT_OBJ(!num.isFourState(), nodep, "4-state value in constant pool"); UASSERT_OBJ(!num.isFourState(), nodep, "4-state value in constant pool");
const AstNodeDType* const dtypep = nodep->dtypep(); putns(nodep, num.emitC());
if (num.isNull()) {
putns(nodep, "VlNull{}");
} else if (num.isString()) {
// Note: putsQuoted does not track indentation, so we use this instead
putns(nodep, "\"");
puts(num.toString());
puts("\"");
} else if (dtypep->isWide()) {
const uint32_t size = dtypep->widthWords();
// Note the double {{ initializer. The first { starts the initializer of the VlWide,
// and the second starts the initializer of m_storage within the VlWide.
putns(nodep, "{");
ofp()->putsNoTracking("{");
if (m_inUnpacked) puts(" // VlWide " + cvtToStr(m_unpackedWord));
puts("\n");
for (uint32_t n = 0; n < size; ++n) {
if (n) puts((n % 4) ? ", " : ",\n");
ofp()->printf("0x%08" PRIx32, num.edataWord(n));
}
puts("\n");
puts("}");
ofp()->putsNoTracking("}");
} else if (dtypep->isDouble()) {
const double dnum = num.toDouble();
const char* const fmt
= !m_inUnpacked && (static_cast<int>(dnum) == dnum && -1000 < dnum && dnum < 1000)
? "%3.1f" // Force decimal point
: "%.17e"; // %e always yields a float literal
putns(nodep, "");
ofp()->printf(fmt, dnum);
} else if (dtypep->isQuad()) {
const uint64_t qnum = static_cast<uint64_t>(num.toUQuad());
const char* const fmt
= !m_inUnpacked && (qnum < 10) ? ("%" PRIx64 "ULL") : ("0x%016" PRIx64 "ULL");
putns(nodep, "");
ofp()->printf(fmt, qnum);
} else {
const uint32_t unum = num.toUInt();
const char* const fmt = !m_inUnpacked && (unum < 10) ? ("%" PRIu32 "U")
: (dtypep->widthMin() > 16) ? ("0x%08" PRIx32 "U")
: (dtypep->widthMin() > 8) ? ("0x%04" PRIx32 "U")
: ("0x%02" PRIx32 "U");
putns(nodep, "");
ofp()->printf(fmt, unum);
}
} }
// Default // Default

View File

@ -488,111 +488,14 @@ void EmitCFunc::emitCvtWideArray(AstNode* nodep, AstNode* fromp) {
puts(")"); puts(")");
} }
void EmitCFunc::emitConstant(AstConst* nodep, AstVarRef* assigntop, const string& assignString) { void EmitCFunc::emitConstant(AstConst* nodep) {
// Put out constant set to the specified variable, or given variable in a string // Put out constant set to the specified variable, or given variable in a string
// TODO merge with V3EmitCConstInit::visit(AstConst) const V3Number& num = nodep->num();
putns(nodep, ""); if (num.isFourState()) {
if (nodep->num().isNull()) {
putns(nodep, "VlNull{}");
} else if (nodep->num().isFourState()) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: 4-state numbers in this context"); nodep->v3warn(E_UNSUPPORTED, "Unsupported: 4-state numbers in this context");
} else if (nodep->num().isString()) { return;
emitConstantString(nodep);
} else if (nodep->isWide()) {
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) {
puts(assignString);
} else {
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) {
puts(assignString);
} else {
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(")");
}
} else if (nodep->isDouble()) {
if (int(nodep->num().toDouble()) == nodep->num().toDouble()
&& nodep->num().toDouble() < 1000 && nodep->num().toDouble() > -1000) {
ofp()->printf("%3.1f", nodep->num().toDouble()); // Force decimal point
} else if (std::isinf(nodep->num().toDouble())) {
if (std::signbit(nodep->num().toDouble())) puts("-");
ofp()->puts("std::numeric_limits<double>::infinity()");
} else if (std::isnan(nodep->num().toDouble())) {
if (std::signbit(nodep->num().toDouble())) puts("-");
ofp()->puts("std::numeric_limits<double>::quiet_NaN()");
} else {
// Not %g as will not always put in decimal point, so not obvious to compiler
// is a real number
ofp()->printf("%.17e", nodep->num().toDouble());
}
} else if (nodep->isQuad()) {
const uint64_t num = nodep->toUQuad();
if (num < 10) {
ofp()->printf("%" PRIu64 "ULL", num);
} else {
ofp()->printf("0x%" PRIx64 "ULL", num);
}
} else {
const uint32_t num = nodep->toUInt();
// Only 32 bits - llx + long long here just to appease CPP format warning
if (num < 10) {
puts(cvtToStr(num));
} else {
ofp()->printf("0x%" PRIx64, static_cast<uint64_t>(num));
}
// If signed, we'll do our own functions
// But must be here, or <= comparisons etc may end up signed
puts("U");
} }
putns(nodep, num.emitC());
} }
void EmitCFunc::emitConstantString(const AstConst* nodep) { void EmitCFunc::emitConstantString(const AstConst* nodep) {
@ -604,11 +507,9 @@ void EmitCFunc::emitConstantString(const AstConst* nodep) {
} }
void EmitCFunc::emitSetVarConstant(const string& assignString, AstConst* constp) { void EmitCFunc::emitSetVarConstant(const string& assignString, AstConst* constp) {
if (!constp->isWide()) { puts(assignString);
puts(assignString); puts(" = ");
puts(" = "); emitConstant(constp);
}
emitConstant(constp, nullptr, assignString);
puts(";\n"); puts(";\n");
} }

View File

@ -120,7 +120,6 @@ class EmitCFunc VL_NOT_FINAL : public EmitCConstInit {
AstVarRef* m_wideTempRefp = nullptr; // Variable that _WW macros should be setting AstVarRef* m_wideTempRefp = nullptr; // Variable that _WW macros should be setting
std::unordered_map<AstJumpBlock*, size_t> m_labelNumbers; // Label numbers for AstJumpBlocks std::unordered_map<AstJumpBlock*, size_t> m_labelNumbers; // Label numbers for AstJumpBlocks
bool m_inUC = false; // Inside an AstUCStmt or AstUCExpr bool m_inUC = false; // Inside an AstUCStmt or AstUCExpr
bool m_emitConstInit = false; // Emitting constant initializer
bool m_createdScopeHash = false; // Already created a scope hash bool m_createdScopeHash = false; // Already created a scope hash
// State associated with processing $display style string formatting // State associated with processing $display style string formatting
@ -221,7 +220,7 @@ public:
void emitDereference(AstNode* nodep, const string& pointer); void emitDereference(AstNode* nodep, const string& pointer);
void emitCvtPackStr(AstNode* nodep); void emitCvtPackStr(AstNode* nodep);
void emitCvtWideArray(AstNode* nodep, AstNode* fromp); void emitCvtWideArray(AstNode* nodep, AstNode* fromp);
void emitConstant(AstConst* nodep, AstVarRef* assigntop, const string& assignString); void emitConstant(AstConst* nodep);
void emitConstantString(const AstConst* nodep); void emitConstantString(const AstConst* nodep);
void emitSetVarConstant(const string& assignString, AstConst* constp); void emitSetVarConstant(const string& assignString, AstConst* constp);
void emitVarReset(AstVar* varp, bool constructing); void emitVarReset(AstVar* varp, bool constructing);
@ -230,13 +229,7 @@ public:
const string& suffix); const string& suffix);
void emitVarResetScopeHash(); void emitVarResetScopeHash();
void emitChangeDet(); void emitChangeDet();
void emitConstInit(AstNode* initp) { void emitConstInit(AstNode* initp) { iterateConst(initp); }
// We should refactor emit to produce output into a provided buffer, not go through members
// variables. That way we could just invoke the appropriate emitter as needed.
VL_RESTORER(m_emitConstInit);
m_emitConstInit = true;
iterateConst(initp);
}
void putCommaIterateNext(AstNode* nodep, bool comma = false) { void putCommaIterateNext(AstNode* nodep, bool comma = false) {
for (AstNode* subnodep = nodep; subnodep; subnodep = subnodep->nextp()) { for (AstNode* subnodep = nodep; subnodep; subnodep = subnodep->nextp()) {
if (comma) puts(", "); if (comma) puts(", ");
@ -263,6 +256,70 @@ public:
if (const AstCNew* const cnewp = getSuperNewCallRecursep(nodep->nextp())) return cnewp; if (const AstCNew* const cnewp = getSuperNewCallRecursep(nodep->nextp())) return cnewp;
return nullptr; 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 putConstructorSubinit(const AstClass* classp, AstCFunc* cfuncp) { void putConstructorSubinit(const AstClass* classp, AstCFunc* cfuncp) {
// Virtual bases in depth-first left-to-right order // Virtual bases in depth-first left-to-right order
std::vector<AstClass*> virtualBases; std::vector<AstClass*> virtualBases;
@ -549,6 +606,9 @@ public:
} else if (nodep->isWide() && VN_IS(nodep->lhsp(), VarRef) // } else if (nodep->isWide() && VN_IS(nodep->lhsp(), VarRef) //
&& !VN_IS(nodep->rhsp(), CExpr) // && !VN_IS(nodep->rhsp(), CExpr) //
&& !VN_IS(nodep->rhsp(), CMethodHard) // && !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(), VarRef) //
&& !VN_IS(nodep->rhsp(), AssocSel) // && !VN_IS(nodep->rhsp(), AssocSel) //
&& !VN_IS(nodep->rhsp(), MemberSel) // && !VN_IS(nodep->rhsp(), MemberSel) //
@ -558,7 +618,8 @@ public:
// Wide functions assign into the array directly, don't need separate assign statement // Wide functions assign into the array directly, don't need separate assign statement
m_wideTempRefp = VN_AS(nodep->lhsp(), VarRef); m_wideTempRefp = VN_AS(nodep->lhsp(), VarRef);
paren = false; paren = false;
} else if (nodep->isWide() && !VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)) { } else if (nodep->isWide() && !VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType)
&& !VN_IS(nodep->rhsp(), Const)) {
putnbs(nodep, "VL_ASSIGN_W("); putnbs(nodep, "VL_ASSIGN_W(");
puts(cvtToStr(nodep->widthMin()) + ", "); puts(cvtToStr(nodep->widthMin()) + ", ");
iterateAndNextConstNull(nodep->lhsp()); iterateAndNextConstNull(nodep->lhsp());
@ -1519,15 +1580,13 @@ public:
putns(nodep, "&"); putns(nodep, "&");
puts(funcNameProtect(funcp)); puts(funcNameProtect(funcp));
} }
void visit(AstConst* nodep) override { void visit(AstConst* nodep) override { //
if (m_emitConstInit) { if (m_wideTempRefp && nodep->isWide()) {
EmitCConstInit::visit(nodep);
} else if (nodep->isWide()) {
UASSERT_OBJ(m_wideTempRefp, nodep, "Wide Constant w/ no temp"); UASSERT_OBJ(m_wideTempRefp, nodep, "Wide Constant w/ no temp");
emitConstant(nodep, m_wideTempRefp, ""); emitConstantW(nodep, m_wideTempRefp);
m_wideTempRefp = nullptr; // We used it, fail if set it a second time m_wideTempRefp = nullptr; // We used it, fail if set it a second time
} else { } else {
emitConstant(nodep, nullptr, ""); emitConstant(nodep);
} }
} }
void visit(AstThisRef* nodep) override { void visit(AstThisRef* nodep) override {

View File

@ -18,6 +18,8 @@
#include "V3Number.h" #include "V3Number.h"
#include "V3File.h"
#include <algorithm> #include <algorithm>
#include <cerrno> #include <cerrno>
#include <cmath> #include <cmath>
@ -877,6 +879,65 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const VL_MT_STAB
} }
} }
string V3Number::emitC() const VL_MT_STABLE {
constexpr size_t bufsize = 1000;
char sbuf[bufsize];
string result;
if (isNull()) {
return "VlNull{}";
} else if (isDouble()) {
const double dnum = toDouble();
if (std::isinf(dnum)) {
if (std::signbit(dnum)) result += '-';
result += "std::numeric_limits<double>::infinity()";
} else if (std::isnan(dnum)) {
if (std::signbit(dnum)) result += '-';
result += "std::numeric_limits<double>::quiet_NaN()";
} else {
const char* const fmt = (static_cast<int>(dnum) == dnum && -1000 < dnum && dnum < 1000)
? "%3.1f" // Force decimal point
: "%.17e"; // %e always yields a float literal
VL_SNPRINTF(sbuf, bufsize, fmt, dnum);
return sbuf;
}
} else if (isString()) {
const string strg = toString();
const string quoted
= V3OutFormatter::quoteNameControls(strg, V3OutFormatter::Language::LA_C);
// Note: putsQuoted does not track indentation, so we use this instead
return '"' + quoted + "\"s";
} else if (words() > 2) {
// Note the double {{ initializer. The first { starts the initializer of the VlWide,
// and the second starts the initializer of m_storage within the VlWide.
// Alternative is to have constructor with std::initializer_list
result = "VlWide<" + std::to_string(words()) + ">{{";
if (words() > 4) result += '\n';
for (int n = 0; n < words(); ++n) {
if (n) result += ((n % 4) ? ", " : ",\n");
VL_SNPRINTF(sbuf, bufsize, "0x%08" PRIx32, edataWord(n));
result += sbuf;
}
if (words() > 4) result += '\n';
result += "}}";
} else if (words() == 2) { // Quad
const uint64_t qnum = static_cast<uint64_t>(toUQuad());
const char* const fmt = (qnum < 10) ? ("%" PRIx64 "ULL") : ("0x%016" PRIx64 "ULL");
VL_SNPRINTF(sbuf, bufsize, fmt, qnum);
return sbuf;
} else {
// Always emit unsigned, if signed, will call correct signed functions
// The 'U' must be here, to avoid <= comparisons etc ending up signed
const uint32_t unum = toUInt();
const char* const fmt = (unum < 10) ? ("%" PRIu32 "U")
: (width() > 16) ? ("0x%08" PRIx32 "U")
: (width() > 8) ? ("0x%04" PRIx32 "U")
: ("0x%02" PRIx32 "U");
VL_SNPRINTF(sbuf, bufsize, fmt, unum);
return sbuf;
}
return result;
}
string V3Number::toDecimalS() const VL_MT_STABLE { string V3Number::toDecimalS() const VL_MT_STABLE {
// Correct number of zero bits/width matters // Correct number of zero bits/width matters
if (isNegative()) { if (isNegative()) {

View File

@ -595,6 +595,7 @@ public:
string displayed(const AstNode* nodep, const string& vformat) const VL_MT_STABLE; string displayed(const AstNode* nodep, const string& vformat) const VL_MT_STABLE;
string displayed(FileLine* fl, const string& vformat) const VL_MT_STABLE; string displayed(FileLine* fl, const string& vformat) const VL_MT_STABLE;
static bool displayedFmtLegal(char format, bool isScan); // Is this a valid format letter? static bool displayedFmtLegal(char format, bool isScan); // Is this a valid format letter?
string emitC() const VL_MT_STABLE;
int width() const VL_MT_SAFE { return m_data.width(); } int width() const VL_MT_SAFE { return m_data.width(); }
int widthToFit() const; // Minimum width that can represent this number (~== log2(num)+1) int widthToFit() const; // Minimum width that can represent this number (~== log2(num)+1)
bool sized() const VL_MT_SAFE { return m_data.m_sized; } bool sized() const VL_MT_SAFE { return m_data.m_sized; }

View File

@ -898,7 +898,7 @@ int _mon_check_putget_str(p_cb_data cb_data) {
// setup and install // setup and install
for (int i = 1; i <= 6; i++) { for (int i = 1; i <= 6; i++) {
char buf[32]; char buf[32];
snprintf(buf, sizeof(buf), TestSimulator::rooted("arr[%d].arr"), i); VL_SNPRINTF(buf, sizeof(buf), TestSimulator::rooted("arr[%d].arr"), i);
CHECK_RESULT_NZ(data[i].scope = vpi_handle_by_name((PLI_BYTE8*)buf, NULL)); CHECK_RESULT_NZ(data[i].scope = vpi_handle_by_name((PLI_BYTE8*)buf, NULL));
CHECK_RESULT_NZ(data[i].sig = vpi_handle_by_name((PLI_BYTE8*)"sig", data[i].scope)); CHECK_RESULT_NZ(data[i].sig = vpi_handle_by_name((PLI_BYTE8*)"sig", data[i].scope));
CHECK_RESULT_NZ(data[i].rfr = vpi_handle_by_name((PLI_BYTE8*)"rfr", data[i].scope)); CHECK_RESULT_NZ(data[i].rfr = vpi_handle_by_name((PLI_BYTE8*)"rfr", data[i].scope));
@ -910,7 +910,7 @@ int _mon_check_putget_str(p_cb_data cb_data) {
for (int i = 1; i <= 6; i++) { for (int i = 1; i <= 6; i++) {
char buf[32]; char buf[32];
snprintf(buf, sizeof(buf), TestSimulator::rooted("subs[%d].subsub"), i); VL_SNPRINTF(buf, sizeof(buf), TestSimulator::rooted("subs[%d].subsub"), i);
CHECK_RESULT_NZ(data[i].scope = vpi_handle_by_name((PLI_BYTE8*)buf, NULL)); CHECK_RESULT_NZ(data[i].scope = vpi_handle_by_name((PLI_BYTE8*)buf, NULL));
} }