From c99aa8ede51dfc4b28f520d3c5639ed8c5f81267 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 22 May 2026 20:05:08 +0100 Subject: [PATCH] Fix erroneous implicit conversions of VlWide (#7642) Change WDataInP/WDataOutP to be opaque handles types instead of aliases to raw pointers. This subsequently eliminates needing an implicit cast operator in VlWide, which is replaced with implicit constructors of WDataInP/WDataOutP that can create a handle from a VlWide. This eliminates some unsafe conversions that the previous implicit cast operator unintentionally enabled (e.g. #7618). It also eliminates having to insert ".data()" in various places int he generated code, which simplifies internals (the only place ".data()" should be needed is in calls to variadic functions where the expected type of the argument is not WDataInP/WDataOutP). The handles otherwise behave like pointers, implementing the minimal amount of operators required to code the runtime. The handle is still only a single pointer, and will be passed in registers as before, so this patch should be performance neutral. As part of this removed WData, which used to be an alias for EData. All uses are now either EData*, WDataInP, WDataOutP, or VlWide directly. --- include/verilated.cpp | 77 ++++++----- include/verilated.h | 8 +- include/verilated_dpi.cpp | 12 +- include/verilated_dpi.h | 2 +- include/verilated_force.h | 13 +- include/verilated_fst_c.cpp | 5 +- include/verilated_fst_c.h | 2 +- include/verilated_funcs.h | 122 ++++++++---------- include/verilated_random.cpp | 4 +- include/verilated_saif_c.cpp | 10 +- include/verilated_saif_c.h | 2 +- include/verilated_trace.h | 10 +- include/verilated_trace_imp.h | 6 +- include/verilated_types.h | 229 ++++++++++++++++++++++----------- include/verilated_vcd_c.cpp | 6 +- include/verilated_vcd_c.h | 2 +- include/verilatedos.h | 6 +- src/V3AstNodeExpr.h | 2 +- src/V3EmitCFunc.cpp | 25 +--- src/V3EmitCFunc.h | 6 +- src/V3EmitCHeaders.cpp | 19 ++- test_regress/t/t_func_public.v | 2 +- test_regress/t/t_var_sc_bv.cpp | 30 ++--- 23 files changed, 328 insertions(+), 272 deletions(-) diff --git a/include/verilated.cpp b/include/verilated.cpp index 2303927e6..82869fab1 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -815,7 +815,7 @@ double VL_ISTOR_D_W(int lbits, const WDataInP lwp) VL_MT_SAFE { if (!VL_SIGN_W(lbits, lwp)) return VL_ITOR_D_W(lbits, lwp); const int words = VL_WORDS_I(lbits); VL_DEBUG_IFDEF(assert(words <= VL_MULS_MAX_WORDS);); - uint32_t pos[VL_MULS_MAX_WORDS + 1]; // Fixed size, as MSVC++ doesn't allow [words] here + VlWide pos; VL_NEGATE_W(words, pos, lwp); _vl_clean_inplace_w(lbits, pos); return -VL_ITOR_D_W(lbits, pos); @@ -829,9 +829,12 @@ std::string VL_DECIMAL_NW(int width, const WDataInP lwp) VL_MT_SAFE { const int maxdecwidth = (width + 3) * 4 / 3; // Or (maxdecwidth+7)/8], but can't have more than 4 BCD bits per word std::vector bcd(VL_WORDS_I(maxdecwidth)); - VL_ZERO_W(maxdecwidth, bcd.data()); + WDataOutP bcdp = WDataOutP::external(bcd.data()); + VL_ZERO_W(maxdecwidth, bcdp); std::vector tmp(VL_WORDS_I(maxdecwidth)); std::vector tmp2(VL_WORDS_I(maxdecwidth)); + WDataOutP tmpp = WDataOutP::external(tmp.data()); + WDataOutP tmp2p = WDataOutP::external(tmp2.data()); int from_bit = width - 1; // Skip all leading zeros for (; from_bit >= 0 && !(VL_BITRSHIFT_W(lwp, from_bit) & 1); --from_bit) {} @@ -840,15 +843,15 @@ std::string VL_DECIMAL_NW(int width, const WDataInP lwp) VL_MT_SAFE { // Any digits >= 5 need an add 3 (via tmp) for (int nibble_bit = 0; nibble_bit < maxdecwidth; nibble_bit += 4) { if ((VL_BITRSHIFT_W(bcd, nibble_bit) & 0xf) >= 5) { - VL_ZERO_W(maxdecwidth, tmp2.data()); + VL_ZERO_W(maxdecwidth, tmp2p); tmp2[VL_BITWORD_E(nibble_bit)] |= VL_EUL(0x3) << VL_BITBIT_E(nibble_bit); - VL_ASSIGN_W(maxdecwidth, tmp.data(), bcd.data()); - VL_ADD_W(VL_WORDS_I(maxdecwidth), bcd.data(), tmp.data(), tmp2.data()); + VL_ASSIGN_W(maxdecwidth, tmpp, bcdp); + VL_ADD_W(VL_WORDS_I(maxdecwidth), bcdp, tmpp, tmp2p); } } // Shift; bcd = bcd << 1 - VL_ASSIGN_W(maxdecwidth, tmp.data(), bcd.data()); - VL_SHIFTL_WWI(maxdecwidth, maxdecwidth, 32, bcd.data(), tmp.data(), 1); + VL_ASSIGN_W(maxdecwidth, tmpp, bcdp); + VL_SHIFTL_WWI(maxdecwidth, maxdecwidth, 32, bcdp, tmpp, 1); // bcd[0] = lwp[from_bit] if (VL_BITISSET_W(lwp, from_bit)) bcd[0] |= 1; } @@ -951,7 +954,7 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc, va_list ap) VL_MT_SAFE { // Format a Verilog $write style format into the output list // The format must be pre-processed (and lower cased) by Verilator. - // Arguments are each {"VFormatAttr character, int width, arg-value (or WDataIn* if wide)"} + // Arguments are each {"VFormatAttr character, int width, arg-value (or EData* if wide)"} // // Uses a single buffer internally; presumes only one usage per printf. // Also assumes variables < 64 are not wide, this assumption is @@ -1083,7 +1086,7 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc, void* thingp = nullptr; QData ld = 0; std::vector strwide; - WDataInP lwp = nullptr; + WDataInP lwp{nullptr}; int lsb = 0; double real = 0.0; if (formatAttr == VL_VFORMATATTR_COMPLEX) { // printed as string @@ -1093,8 +1096,9 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc, real = va_arg(ap, double); ld = VL_RTOIROUND_Q_D(real); strwide.resize(2); - VL_SET_WQ(strwide, ld); - lwp = strwide.data(); + WDataOutP strwidep = WDataOutP::external(strwide.data()); + VL_SET_WQ(strwidep, ld); + lwp = strwidep; lbits = 64; // Not changint fmt == 'p' to fmt = 'g', as need fmts correct } else if (formatAttr == VL_VFORMATATTR_STRING) { @@ -1105,10 +1109,11 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc, if (lbits <= VL_QUADSIZE) { ld = VL_VA_ARG_Q_(ap, lbits); strwide.resize(2); - VL_SET_WQ(strwide, ld); - lwp = strwide.data(); + WDataOutP strwidep = WDataOutP::external(strwide.data()); + VL_SET_WQ(strwidep, ld); + lwp = strwidep; } else { - lwp = va_arg(ap, WDataInP); + lwp = WDataInP::external(va_arg(ap, EData*)); ld = lwp[0]; } if (fmt == 'p') { @@ -1194,8 +1199,9 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc, } else { if (VL_SIGN_E(lbits, lwp[VL_WORDS_I(lbits) - 1])) { std::vector neg(VL_WORDS_I(lbits)); - VL_NEGATE_W(VL_WORDS_I(lbits), neg.data(), lwp); - append = "-"s + VL_DECIMAL_NW(lbits, neg.data()); + WDataOutP negp = WDataOutP::external(neg.data()); + VL_NEGATE_W(VL_WORDS_I(lbits), negp, lwp); + append = "-"s + VL_DECIMAL_NW(lbits, negp); } else { append = VL_DECIMAL_NW(lbits, lwp); } @@ -1262,9 +1268,10 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc, if (truncFront < 0) truncFront = 0; lbits = chars * 8; strwide.resize(VL_WORDS_I(lbits)); - lwp = strwide.data(); + WDataOutP strwidep = WDataOutP::external(strwide.data()); + lwp = strwidep; lsb = lbits - 1; - VL_NTOI_W(lbits, strwide.data(), *strp, truncFront); + VL_NTOI_W(lbits, strwidep, *strp, truncFront); } if (widthSet || left) { @@ -1459,7 +1466,7 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf const std::string& format, int argc, va_list ap) VL_MT_SAFE { // Read a Verilog $sscanf/$fscanf style format into the output list // The format must be pre-processed (and lower cased) by Verilator - // Arguments are in "width, arg-value (or WDataIn* if wide)" form + // Arguments are in "width, arg-value (or EData* if wide)" form static thread_local std::string t_tmp; int floc = fbits - 1; IData got = 0; @@ -1546,7 +1553,8 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf VlWide qowp; VL_SET_WQ(qowp, 0ULL); - WDataOutP owp = (obits <= 64) ? qowp : static_cast(thingp); + WDataOutP owp = WDataOutP::external((obits <= 64) ? qowp.data() + : static_cast(thingp)); for (int i = 0; i < VL_WORDS_I(obits); ++i) owp[i] = 0; t_tmp.clear(); @@ -1649,7 +1657,7 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf case 'u': { // Read packed 2-value binary data const int bytes = VL_BYTES_I(obits); - char* const out = reinterpret_cast(owp); + char* const out = reinterpret_cast(owp.datap()); if (!_vl_vsss_read_bin(fp, floc, fromp, fstr, out, bytes)) goto done; const int last = bytes % 4; if (last != 0 @@ -1659,7 +1667,7 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf } case 'z': { // Read packed 4-value binary data - char* out = reinterpret_cast(owp); + char* out = reinterpret_cast(owp.datap()); int bytes = VL_BYTES_I(obits); while (bytes > 0) { const int abytes = std::min(4, bytes); @@ -1801,7 +1809,7 @@ IData VL_FERROR_IN(IData, std::string& outputr) VL_MT_SAFE { IData VL_FERROR_IW(IData fpi, int obits, WDataOutP outwp) VL_MT_SAFE { std::string output; const IData ret = VL_FERROR_IN(fpi, output /*ref*/); - _vl_string_to_vint(obits, outwp, output.length(), output.c_str()); + _vl_string_to_vint(obits, outwp.datap(), output.length(), output.c_str()); return ret; } @@ -1866,7 +1874,7 @@ void VL_SFORMAT_NX(int obits, QData& destr, const std::string& format, int argc, _vl_string_to_vint(obits, &destr, t_output.length(), t_output.c_str()); } -void VL_SFORMAT_NX(int obits, void* destp, const std::string& format, int argc, ...) VL_MT_SAFE { +void VL_SFORMAT_NX(int obits, EData* destp, const std::string& format, int argc, ...) VL_MT_SAFE { static thread_local std::string t_output; // static only for speed t_output = ""; va_list ap; @@ -1928,9 +1936,10 @@ IData VL_FSCANF_INX(IData fpi, const std::string& format, int argc, ...) VL_MT_S FILE* const fp = VL_CVT_I_FP(fpi); if (VL_UNLIKELY(!fp)) return ~0U; // -1 + WDataInP fromp{nullptr}; va_list ap; va_start(ap, argc); - const IData got = _vl_vsscanf(fp, 0, nullptr, "", format, argc, ap); + const IData got = _vl_vsscanf(fp, 0, fromp, "", format, argc, ap); va_end(ap); return got; } @@ -1965,10 +1974,11 @@ IData VL_SSCANF_IWNX(int lbits, const WDataInP lwp, const std::string& format, i } IData VL_SSCANF_INNX(int, const std::string& ld, const std::string& format, int argc, ...) VL_MT_SAFE { + WDataInP fromp{nullptr}; va_list ap; va_start(ap, argc); const IData got - = _vl_vsscanf(nullptr, static_cast(ld.length() * 8), nullptr, ld, format, argc, ap); + = _vl_vsscanf(nullptr, static_cast(ld.length() * 8), fromp, ld, format, argc, ap); va_end(ap); return got; } @@ -2052,7 +2062,8 @@ IData VL_FREAD_I(int width, int array_lsb, int array_size, void* memp, IData fpi if (shift == start_shift) *datap = 0; *datap |= ((static_cast(c) << static_cast(shift)) & VL_MASK_Q(width)); } else { - WDataOutP datap = &(reinterpret_cast(memp))[entry * VL_WORDS_I(width)]; + const WDataOutP datap = WDataOutP::external( + &(reinterpret_cast(memp))[entry * VL_WORDS_I(width)]); if (shift == start_shift) VL_ZERO_W(width, datap); datap[VL_BITWORD_E(shift)] |= (static_cast(c) << VL_BITBIT_E(shift)); } @@ -2545,7 +2556,7 @@ void VlReadMem::setData(void* valuep, const std::string& rhs) { *datap = ((*datap << static_cast(shift)) + static_cast(value)) & VL_MASK_Q(m_bits); } else { - WDataOutP datap = reinterpret_cast(valuep); + const WDataOutP datap = WDataOutP::external(reinterpret_cast(valuep)); if (!innum) VL_ZERO_W(m_bits, datap); _vl_shiftl_inplace_w(m_bits, datap, static_cast(shift)); datap[0] |= value; @@ -2617,7 +2628,7 @@ void VlWriteMem::print(QData addr, bool addrstamp, const void* valuep) { fprintf(m_fp, "%s\n", formatBinary(32, lo)); } } else { - const WDataInP datap = reinterpret_cast(valuep); + const WDataInP datap = WDataInP::external(reinterpret_cast(valuep)); // output as a sequence of VL_EDATASIZE'd words // from MSB to LSB. Mask off the MSB word which could // contain junk above the top of valid data. @@ -2685,8 +2696,8 @@ void VL_READMEM_N(bool hex, // Hex format, else binary QData* const datap = &(reinterpret_cast(memp))[entry]; rmem.setData(datap, value); } else { - WDataOutP datap - = &(reinterpret_cast(memp))[entry * VL_WORDS_I(bits)]; + EData* const datap + = &(reinterpret_cast(memp))[entry * VL_WORDS_I(bits)]; rmem.setData(datap, value); } } @@ -2728,8 +2739,8 @@ void VL_WRITEMEM_N(bool hex, // Hex format, else binary const QData* const datap = &(reinterpret_cast(memp))[row_offset]; wmem.print(addr, false, datap); } else { - const WDataInP memDatap = reinterpret_cast(memp); - const WDataInP datap = &memDatap[row_offset * VL_WORDS_I(bits)]; + const EData* const datap + = &(reinterpret_cast(memp))[row_offset * VL_WORDS_I(bits)]; wmem.print(addr, false, datap); } } diff --git a/include/verilated.h b/include/verilated.h index b90f80bc5..dcb5144cd 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -120,8 +120,7 @@ using CData = uint8_t; ///< Data representing 'bit' of 1-8 packed bits using SData = uint16_t; ///< Data representing 'bit' of 9-16 packed bits using IData = uint32_t; ///< Data representing 'bit' of 17-32 packed bits using QData = uint64_t; ///< Data representing 'bit' of 33-64 packed bits -using EData = uint32_t; ///< Data representing one element of WData array -using WData = EData; ///< Data representing >64 packed bits (used as pointer) +using EData = uint32_t; ///< Data representing one element of VlWide // F = float; // No typedef needed; Verilator uses float // D = double; // No typedef needed; Verilator uses double // N = std::string; // No typedef needed; Verilator uses string @@ -129,9 +128,6 @@ using WData = EData; ///< Data representing >64 packed bits (used as poin // R = VlQueue; // clang-format on -using WDataInP = const WData*; ///< 'bit' of >64 packed bits as array input to a function -using WDataOutP = WData*; ///< 'bit' of >64 packed bits as array output from a function - enum VerilatedVarType : uint8_t { VLVT_UNKNOWN = 0, VLVT_PTR, // Pointer to something @@ -139,7 +135,7 @@ enum VerilatedVarType : uint8_t { VLVT_UINT16, // AKA SData VLVT_UINT32, // AKA IData VLVT_UINT64, // AKA QData - VLVT_WDATA, // AKA WData + VLVT_WDATA, // AKA VlWide VLVT_STRING, // C++ string VLVT_REAL // AKA double }; diff --git a/include/verilated_dpi.cpp b/include/verilated_dpi.cpp index c97c56640..23abb4594 100644 --- a/include/verilated_dpi.cpp +++ b/include/verilated_dpi.cpp @@ -70,7 +70,9 @@ svLogic svGetBitselLogic(const svLogicVecVal* sp, int bit) { | (((sp[VL_BITWORD_I(bit)].bval >> VL_BITBIT_I(bit)) & 1) << 1)); } -void svPutBitselBit(svBitVecVal* dp, int bit, svBit s) { VL_ASSIGNBIT_WI(bit, dp, s); } +void svPutBitselBit(svBitVecVal* dp, int bit, svBit s) { + VL_ASSIGNBIT_WI(bit, WDataOutP::external(dp), s); +} void svPutBitselLogic(svLogicVecVal* dp, int bit, svLogic s) { // Verilator doesn't support X/Z so only aval dp[VL_BITWORD_I(bit)].aval = ((dp[VL_BITWORD_I(bit)].aval & ~(VL_UL(1) << VL_BITBIT_I(bit))) @@ -290,7 +292,7 @@ static void _vl_svGetBitArrElemVecVal(svBitVecVal* d, const svOpenArrayHandle s, break; } case VLVT_WDATA: { - WDataInP wdatap = (reinterpret_cast(datap)); + WDataInP wdatap = WDataInP::external(reinterpret_cast(datap)); for (int i = 0; i < VL_WORDS_I(varp->entBits()); ++i) d[i] = wdatap[i]; return; } @@ -329,7 +331,7 @@ static void _vl_svGetLogicArrElemVecVal(svLogicVecVal* d, const svOpenArrayHandl break; } case VLVT_WDATA: { - WDataInP wdatap = (reinterpret_cast(datap)); + WDataInP wdatap = WDataInP::external(reinterpret_cast(datap)); for (int i = 0; i < VL_WORDS_I(varp->entBits()); ++i) { d[i].aval = wdatap[i]; d[i].bval = 0; @@ -355,7 +357,7 @@ static void _vl_svPutBitArrElemVecVal(const svOpenArrayHandle d, const svBitVecV case VLVT_UINT32: *(reinterpret_cast(datap)) = s[0]; return; case VLVT_UINT64: *(reinterpret_cast(datap)) = VL_SET_QII(s[1], s[0]); break; case VLVT_WDATA: { - WDataOutP wdatap = (reinterpret_cast(datap)); + WDataOutP wdatap = WDataOutP::external(reinterpret_cast(datap)); for (int i = 0; i < VL_WORDS_I(varp->entBits()); ++i) wdatap[i] = s[i]; return; } @@ -377,7 +379,7 @@ static void _vl_svPutLogicArrElemVecVal(const svOpenArrayHandle d, const svLogic case VLVT_UINT32: *(reinterpret_cast(datap)) = s[0].aval; return; case VLVT_UINT64: *(reinterpret_cast(datap)) = VL_SET_QII(s[1].aval, s[0].aval); break; case VLVT_WDATA: { - WDataOutP wdatap = (reinterpret_cast(datap)); + WDataOutP wdatap = WDataOutP::external(reinterpret_cast(datap)); for (int i = 0; i < VL_WORDS_I(varp->entBits()); ++i) wdatap[i] = s[i].aval; return; } diff --git a/include/verilated_dpi.h b/include/verilated_dpi.h index cb6efff68..fd8f177ee 100644 --- a/include/verilated_dpi.h +++ b/include/verilated_dpi.h @@ -63,7 +63,7 @@ static inline void VL_SET_SVBV_W(int obits, svBitVecVal* owp, const WDataInP lwp } static inline void VL_SET_SVBV_I(int, svBitVecVal* owp, const IData ld) VL_MT_SAFE { owp[0] = ld; } static inline void VL_SET_SVBV_Q(int, svBitVecVal* owp, const QData ld) VL_MT_SAFE { - VL_SET_WQ(owp, ld); + VL_SET_WQ(WDataOutP::external(owp), ld); } // Convert svLogicVecVal to Verilator internal data diff --git a/include/verilated_force.h b/include/verilated_force.h index 8160fdffb..d50d27c18 100644 --- a/include/verilated_force.h +++ b/include/verilated_force.h @@ -145,7 +145,7 @@ private: return (rhsVal >> rhsLsb) & mask; } - const EData* const rhswp = static_cast(entry.m_rhsDatap); + WDataInP rhswp = WDataInP::external(static_cast(entry.m_rhsDatap)); return VL_SEL_QWII(rhsWidth, rhswp, rhsLsb, width) & mask; } @@ -198,7 +198,7 @@ private: template typename std::enable_if::value>::type applyEntries(T& val) const { for (const auto& entry : m_entries) { - applyEntry(val.data(), entry, entry.m_lsb, entry.m_msb, 0); + applyEntry(val, entry, entry.m_lsb, entry.m_msb, 0); } } @@ -262,14 +262,14 @@ public: IData readSelI(int lbits, WDataInP valp, int lsb, int width) const { IData result; - readSel(lbits, valp, &result, lsb, width); + readSel(lbits, valp, WDataOutP::external(reinterpret_cast(&result)), lsb, width); result &= VL_MASK_I(width); return result; } QData readSelQ(int lbits, WDataInP valp, int lsb, int width) const { QData result; - readSel(lbits, valp, reinterpret_cast(&result), lsb, width); + readSel(lbits, valp, WDataOutP::external(reinterpret_cast(&result)), lsb, width); result &= VL_MASK_Q(width); return result; } @@ -277,9 +277,8 @@ public: template VlWide readSelW(int lbits, WDataInP valp, int lsb, int width) const { VlWide result; - WDataOutP const reswp = result.data(); - readSel(lbits, valp, reswp, lsb, width); - reswp[N_Words - 1] &= VL_MASK_E(width); + readSel(lbits, valp, result, lsb, width); + result[N_Words - 1] &= VL_MASK_E(width); return result; } diff --git a/include/verilated_fst_c.cpp b/include/verilated_fst_c.cpp index 5bf3db12b..0e3706725 100644 --- a/include/verilated_fst_c.cpp +++ b/include/verilated_fst_c.cpp @@ -412,11 +412,10 @@ void VerilatedFstBuffer::emitQData(uint32_t code, QData newval, int) { } VL_ATTR_ALWINLINE -void VerilatedFstBuffer::emitWData(uint32_t code, const WData* newvalp, int) { +void VerilatedFstBuffer::emitWData(uint32_t code, WDataInP newval, int) { VL_DEBUG_IFDEF(assert(m_symbolp[code]);); // LCOV_EXCL_BR_LINE m_owner.emitTimeChangeMaybe(); - // call emitValueChange(handle, uint32_t*) - m_fst->emitValueChange(m_symbolp[code], newvalp); + m_fst->emitValueChange(m_symbolp[code], newval.datap()); } VL_ATTR_ALWINLINE diff --git a/include/verilated_fst_c.h b/include/verilated_fst_c.h index caf185bce..de1b0eeca 100644 --- a/include/verilated_fst_c.h +++ b/include/verilated_fst_c.h @@ -232,7 +232,7 @@ class VerilatedFstBuffer VL_NOT_FINAL { VL_ATTR_ALWINLINE void emitSData(uint32_t code, SData newval, int); VL_ATTR_ALWINLINE void emitIData(uint32_t code, IData newval, int); VL_ATTR_ALWINLINE void emitQData(uint32_t code, QData newval, int); - VL_ATTR_ALWINLINE void emitWData(uint32_t code, const WData* newvalp, int); + VL_ATTR_ALWINLINE void emitWData(uint32_t code, WDataInP newval, int); VL_ATTR_ALWINLINE void emitDouble(uint32_t code, double newval); }; diff --git a/include/verilated_funcs.h b/include/verilated_funcs.h index 44a658c37..2bb4ee4e1 100644 --- a/include/verilated_funcs.h +++ b/include/verilated_funcs.h @@ -169,7 +169,7 @@ extern void VL_SFORMAT_NX(int obits, IData& destr, const std::string& format, in ...) VL_MT_SAFE; extern void VL_SFORMAT_NX(int obits, QData& destr, const std::string& format, int argc, ...) VL_MT_SAFE; -extern void VL_SFORMAT_NX(int obits, void* destp, const std::string& format, int argc, +extern void VL_SFORMAT_NX(int obits, EData* destp, const std::string& format, int argc, ...) VL_MT_SAFE; extern void VL_STACKTRACE() VL_MT_SAFE; @@ -194,20 +194,20 @@ extern const char* vl_mc_scan_plusargs(const char* prefixp) VL_MT_SAFE; // PLIi #define VL_BITRSHIFT_W(data, bit) ((data)[VL_BITWORD_E(bit)] >> VL_BITBIT_E(bit)) // Create two 32-bit words from quadword -// WData is always at least 2 words; does not clean upper bits -#define VL_SET_WQ(owp, data) \ - do { \ - (owp)[0] = static_cast(data); \ - (owp)[1] = static_cast((data) >> VL_EDATASIZE); \ - } while (false) -#define VL_SET_WI(owp, data) \ - do { \ - (owp)[0] = static_cast(data); \ - (owp)[1] = 0; \ - } while (false) -#define VL_SET_QW(lwp) \ - ((static_cast((lwp)[0])) \ - | (static_cast((lwp)[1]) << (static_cast(VL_EDATASIZE)))) +// VLWide is always at least 2 words; does not clean upper bits +static inline WDataOutP VL_SET_WQ(WDataOutP owp, QData data) VL_PURE { + owp[0] = static_cast(data); + owp[1] = static_cast(data >> VL_EDATASIZE); + return owp; +} +static inline WDataOutP VL_SET_WI(WDataOutP owp, IData data) VL_PURE { + owp[0] = static_cast(data); + owp[1] = 0; + return owp; +} +static inline QData VL_SET_QW(WDataInP lwp) VL_PURE { + return (static_cast(lwp[1]) << VL_EDATASIZE) | static_cast(lwp[0]); +} #define VL_SET_QII(ld, rd) ((static_cast(ld) << 32ULL) | static_cast(rd)) // Return FILE* from IData @@ -376,15 +376,18 @@ std::string vl_timescaled_double(double value, const char* format = "%0.0f%s") V VL_ATTR_ALWINLINE static WDataOutP VL_MEMSET_ZERO_W(WDataOutP owp, int words) VL_MT_SAFE { - return static_cast(std::memset(owp, 0, words * sizeof(EData))); + for (size_t i = 0; i < words; ++i) owp[i] = 0; + return owp; } VL_ATTR_ALWINLINE static WDataOutP VL_MEMSET_ONES_W(WDataOutP owp, int words) VL_MT_SAFE { - return static_cast(std::memset(owp, 0xff, words * sizeof(EData))); + for (size_t i = 0; i < words; ++i) owp[i] = ~static_cast(0); + return owp; } VL_ATTR_ALWINLINE static WDataOutP VL_MEMCPY_W(WDataOutP owp, WDataInP const iwp, int words) VL_MT_SAFE { - return static_cast(std::memcpy(owp, iwp, words * sizeof(EData))); + for (size_t i = 0; i < words; ++i) owp[i] = iwp[i]; + return owp; } // Output clean @@ -502,12 +505,12 @@ static inline void VL_ASSIGNBIT_WO(int bit, WDataOutP owp) VL_MT_SAFE { while (lsb < (obits) - BITS_PER_DIGIT) { \ const uint32_t data = *chunkp; \ ++chunkp; \ - _vl_insert_WI((owp).data(), data, lsb + BITS_PER_DIGIT - 1, lsb); \ + _vl_insert_WI(owp, data, lsb + BITS_PER_DIGIT - 1, lsb); \ lsb += BITS_PER_DIGIT; \ } \ if (lsb < (obits)) { \ const uint32_t msb_data = *chunkp; \ - _vl_insert_WI((owp).data(), msb_data, (obits) - 1, lsb); \ + _vl_insert_WI(owp, msb_data, (obits) - 1, lsb); \ } \ (owp)[words - 1] &= VL_MASK_E(obits); \ } @@ -558,16 +561,16 @@ static inline void VL_ASSIGNBIT_WO(int bit, WDataOutP owp) VL_MT_SAFE { while (lsb + BITS_PER_DIGIT < (obits)) { \ static_assert(std::is_same::value, "IData and EData mismatch"); \ const uint32_t data \ - = VL_SEL_IWII(lsb + BITS_PER_DIGIT + 1, (rwp).data(), lsb, BITS_PER_DIGIT); \ + = VL_SEL_IWII(lsb + BITS_PER_DIGIT + 1, rwp, lsb, BITS_PER_DIGIT); \ *chunkp = data & VL_MASK_E(BITS_PER_DIGIT); \ ++chunkp; \ lsb += BITS_PER_DIGIT; \ } \ if (lsb < (obits)) { \ - const uint32_t msb_data = VL_SEL_IWII((obits) + 1, (rwp).data(), lsb, (obits) - lsb); \ + const uint32_t msb_data = VL_SEL_IWII((obits) + 1, rwp, lsb, (obits) - lsb); \ *chunkp = msb_data & VL_MASK_E((obits) - lsb); \ } \ - _butemp.set(0, *(rwp).data() & 1); /* force update the sign */ \ + _butemp.set(0, rwp[0] & 1); /* force update the sign */ \ (svar).write(_butemp); \ } @@ -596,7 +599,7 @@ static inline WDataOutP VL_EXTEND_WQ(int obits, int, WDataOutP owp, QData ld) VL static inline WDataOutP VL_EXTEND_WW(int obits, int lbits, WDataOutP owp, WDataInP const lwp) VL_MT_SAFE { const int lwords = VL_WORDS_I(lbits); - VL_PREFETCH_RD(lwp); + VL_PREFETCH_RD(lwp.datap()); VL_MEMSET_ZERO_W(owp + lwords, VL_WORDS_I(obits) - lwords); return VL_MEMCPY_W(owp, lwp, lwords); } @@ -636,7 +639,7 @@ static inline WDataOutP VL_EXTENDS_WQ(int obits, int lbits, WDataOutP owp, QData static inline WDataOutP VL_EXTENDS_WW(int obits, int lbits, WDataOutP owp, WDataInP const lwp) VL_MT_SAFE { const int lwords = VL_WORDS_I(lbits); - VL_PREFETCH_RD(lwp); + VL_PREFETCH_RD(lwp.datap()); owp[lwords - 1] = lwp[lwords - 1]; if (VL_SIGN_E(lbits, lwp[lwords - 1])) { owp[lwords - 1] |= ~VL_MASK_E(lbits); @@ -1152,29 +1155,25 @@ static inline QData VL_MULS_QQQ(int lbits, QData lhs, QData rhs) VL_PURE { return lhs_signed * rhs_signed; } -static inline WDataOutP VL_MULS_WWW(int lbits, WDataOutP owp, WDataInP const lwp, - WDataInP const rwp) VL_MT_SAFE { +static inline WDataOutP VL_MULS_WWW(int lbits, WDataOutP owp, WDataInP lwp, + WDataInP rwp) VL_MT_SAFE { const int words = VL_WORDS_I(lbits); VL_DEBUG_IFDEF(assert(words <= VL_MULS_MAX_WORDS);); - // cppcheck-suppress variableScope - WData lwstore[VL_MULS_MAX_WORDS]; // Fixed size, as MSVC++ doesn't allow [words] here - // cppcheck-suppress variableScope - WData rwstore[VL_MULS_MAX_WORDS]; - WDataInP lwusp = lwp; - WDataInP rwusp = rwp; + VlWide lwstore; + VlWide rwstore; const EData lneg = VL_SIGN_E(lbits, lwp[words - 1]); if (lneg) { // Negate lhs - lwusp = lwstore; VL_NEGATE_W(words, lwstore, lwp); lwstore[words - 1] &= VL_MASK_E(lbits); // Clean it + lwp = lwstore; } const EData rneg = VL_SIGN_E(lbits, rwp[words - 1]); if (rneg) { // Negate rhs - rwusp = rwstore; VL_NEGATE_W(words, rwstore, rwp); rwstore[words - 1] &= VL_MASK_E(lbits); // Clean it + rwp = rwstore; } - VL_MUL_W(words, owp, lwusp, rwusp); + VL_MUL_W(words, owp, lwp, rwp); owp[words - 1] &= VL_MASK_E( lbits); // Clean. Note it's ok for the multiply to overflow into the sign bit if ((lneg ^ rneg) & 1) { // Negate output (not using NEGATE, as owp==lwp) @@ -1222,49 +1221,41 @@ static inline QData VL_MODDIVS_QQQ(int lbits, QData lhs, QData rhs) VL_PURE { return lhs_signed % rhs_signed; } -static inline WDataOutP VL_DIVS_WWW(int lbits, WDataOutP owp, WDataInP const lwp, - WDataInP const rwp) VL_MT_SAFE { +static inline WDataOutP VL_DIVS_WWW(int lbits, WDataOutP owp, WDataInP lwp, + WDataInP rwp) VL_MT_SAFE { const int lwords = VL_WORDS_I(lbits); const EData lsign = VL_SIGN_E(lbits, lwp[lwords - 1]); const EData rsign = VL_SIGN_E(lbits, rwp[lwords - 1]); VL_DEBUG_IFDEF(assert(lwords <= VL_MULS_MAX_WORDS);); - // cppcheck-suppress variableScope - WData lwstore[VL_MULS_MAX_WORDS]; // Fixed size, as MSVC++ doesn't allow [words] here - // cppcheck-suppress variableScope - WData rwstore[VL_MULS_MAX_WORDS]; - WDataInP ltup = lwp; - WDataInP rtup = rwp; - if (lsign) ltup = _vl_clean_inplace_w(lbits, VL_NEGATE_W(lwords, lwstore, lwp)); - if (rsign) rtup = _vl_clean_inplace_w(lbits, VL_NEGATE_W(lwords, rwstore, rwp)); + VlWide lwstore; + VlWide rwstore; + if (lsign) lwp = _vl_clean_inplace_w(lbits, VL_NEGATE_W(lwords, lwstore, lwp)); + if (rsign) rwp = _vl_clean_inplace_w(lbits, VL_NEGATE_W(lwords, rwstore, rwp)); if ((lsign && !rsign) || (!lsign && rsign)) { - WData qNoSign[VL_MULS_MAX_WORDS]; - VL_DIV_WWW(lbits, qNoSign, ltup, rtup); + VlWide qNoSign; + VL_DIV_WWW(lbits, qNoSign, lwp, rwp); _vl_clean_inplace_w(lbits, VL_NEGATE_W(lwords, owp, qNoSign)); return owp; } - return VL_DIV_WWW(lbits, owp, ltup, rtup); + return VL_DIV_WWW(lbits, owp, lwp, rwp); } -static inline WDataOutP VL_MODDIVS_WWW(int lbits, WDataOutP owp, WDataInP const lwp, - WDataInP const rwp) VL_MT_SAFE { +static inline WDataOutP VL_MODDIVS_WWW(int lbits, WDataOutP owp, WDataInP lwp, + WDataInP rwp) VL_MT_SAFE { const int lwords = VL_WORDS_I(lbits); const EData lsign = VL_SIGN_E(lbits, lwp[lwords - 1]); const EData rsign = VL_SIGN_E(lbits, rwp[lwords - 1]); VL_DEBUG_IFDEF(assert(lwords <= VL_MULS_MAX_WORDS);); - // cppcheck-suppress variableScope - WData lwstore[VL_MULS_MAX_WORDS]; // Fixed size, as MSVC++ doesn't allow [words] here - // cppcheck-suppress variableScope - WData rwstore[VL_MULS_MAX_WORDS]; - WDataInP ltup = lwp; - WDataInP rtup = rwp; - if (lsign) ltup = _vl_clean_inplace_w(lbits, VL_NEGATE_W(lwords, lwstore, lwp)); - if (rsign) rtup = _vl_clean_inplace_w(lbits, VL_NEGATE_W(lwords, rwstore, rwp)); + VlWide lwstore; + VlWide rwstore; + if (lsign) lwp = _vl_clean_inplace_w(lbits, VL_NEGATE_W(lwords, lwstore, lwp)); + if (rsign) rwp = _vl_clean_inplace_w(lbits, VL_NEGATE_W(lwords, rwstore, rwp)); if (lsign) { // Only dividend sign matters for modulus - WData qNoSign[VL_MULS_MAX_WORDS]; - VL_MODDIV_WWW(lbits, qNoSign, ltup, rtup); + VlWide qNoSign; + VL_MODDIV_WWW(lbits, qNoSign, lwp, rwp); _vl_clean_inplace_w(lbits, VL_NEGATE_W(lwords, owp, qNoSign)); return owp; } - return VL_MODDIV_WWW(lbits, owp, ltup, rtup); + return VL_MODDIV_WWW(lbits, owp, lwp, rwp); } #define VL_POW_IIQ(obits, lbits, rbits, lhs, rhs) VL_POW_QQQ(obits, lbits, rbits, lhs, rhs) @@ -2890,9 +2881,9 @@ static inline void VL_SET_QUEUE_BIT(VlQueue>& queue, int dstElem VlWide& element = queue.atWrite(elemIdx); if (value) { - VL_ASSIGNBIT_WO(actualBitPos, element.data()); + VL_ASSIGNBIT_WO(actualBitPos, element); } else { - VL_ASSIGNBIT_WI(actualBitPos, element.data(), 0); + VL_ASSIGNBIT_WI(actualBitPos, element, 0); } } @@ -3384,7 +3375,8 @@ static inline void VL_SELASSIGN_WW(int rbits, int obits, WDataOutP iowp, WDataIn if (w == obits) return; obits -= w; } - _vl_insert_WW(iowp, rwp + wordoff + (lsb != 0), upperbits + obits - 1, upperbits, rbits); + _vl_insert_WW(iowp, rwp + static_cast(wordoff + (lsb != 0)), upperbits + obits - 1, + upperbits, rbits); } //====================================================================== diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index 19817b8d0..b35769d83 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -349,7 +349,7 @@ void VlRandomVar::emitConcreteValue(std::ostream& s) const { } else if (w <= VL_QUADSIZE) { bit = (*static_cast(dp) >> i) & 1; } else { - const EData* const wp = static_cast(dp); + const WDataInP wp = WDataInP::external(static_cast(dp)); bit = (wp[VL_BITWORD_E(i)] >> VL_BITBIT_E(i)) & 1; } s << (bit ? '1' : '0'); @@ -381,7 +381,7 @@ bool VlRandomVar::set(const std::string& idx, const std::string& val) const { VL_SET_WQ(qiwp, 0ULL); if (!idx.empty() && !parseSMTNum(64, qiwp, idx)) return false; const int nidx = qiwp[0]; - if (obits > VL_QUADSIZE) owp = reinterpret_cast(datap(nidx)); + if (obits > VL_QUADSIZE) owp = WDataOutP::external(reinterpret_cast(datap(nidx))); if (!parseSMTNum(obits, owp, val)) return false; if (obits <= VL_BYTESIZE) { diff --git a/include/verilated_saif_c.cpp b/include/verilated_saif_c.cpp index d6632dca7..18f579ec2 100644 --- a/include/verilated_saif_c.cpp +++ b/include/verilated_saif_c.cpp @@ -117,7 +117,7 @@ public: updateLastTime(time); } - VL_ATTR_ALWINLINE void emitWData(uint64_t time, const WData* newvalp, uint32_t bits); + VL_ATTR_ALWINLINE void emitWData(uint64_t time, WDataInP newval, uint32_t bits); VL_ATTR_ALWINLINE void updateLastTime(uint64_t val) { m_lastTime = val; } // ACCESSORS @@ -229,13 +229,13 @@ void VerilatedSaifActivityVar::emitBit(const uint64_t time, const CData newval) } VL_ATTR_ALWINLINE -void VerilatedSaifActivityVar::emitWData(const uint64_t time, const WData* newvalp, +void VerilatedSaifActivityVar::emitWData(const uint64_t time, WDataInP newval, const uint32_t bits) { assert(m_lastTime <= time); const uint64_t dt = time - m_lastTime; for (std::size_t i = 0; i < std::min(m_width, bits); ++i) { const size_t wordIndex = i / VL_EDATASIZE; - m_bits[i].aggregateVal(dt, (newvalp[wordIndex] >> VL_BITBIT_E(i)) & 1); + m_bits[i].aggregateVal(dt, (newval[wordIndex] >> VL_BITBIT_E(i)) & 1); } updateLastTime(time); @@ -670,12 +670,12 @@ void VerilatedSaifBuffer::emitQData(const uint32_t code, const QData newval, con } VL_ATTR_ALWINLINE -void VerilatedSaifBuffer::emitWData(const uint32_t code, const WData* newvalp, const int bits) { +void VerilatedSaifBuffer::emitWData(const uint32_t code, WDataInP newval, const int bits) { assert(m_owner.m_activityAccumulators.at(m_fidx)->m_activity.count(code) && "Activity must be declared earlier"); VerilatedSaifActivityVar& activity = m_owner.m_activityAccumulators.at(m_fidx)->m_activity.at(code); - activity.emitWData(m_owner.currentTime(), newvalp, bits); + activity.emitWData(m_owner.currentTime(), newval, bits); } VL_ATTR_ALWINLINE diff --git a/include/verilated_saif_c.h b/include/verilated_saif_c.h index 3ad4e630c..63cc717c0 100644 --- a/include/verilated_saif_c.h +++ b/include/verilated_saif_c.h @@ -247,7 +247,7 @@ class VerilatedSaifBuffer VL_NOT_FINAL { VL_ATTR_ALWINLINE void emitSData(uint32_t code, SData newval, int bits); VL_ATTR_ALWINLINE void emitIData(uint32_t code, IData newval, int bits); VL_ATTR_ALWINLINE void emitQData(uint32_t code, QData newval, int bits); - VL_ATTR_ALWINLINE void emitWData(uint32_t code, const WData* newvalp, int bits); + VL_ATTR_ALWINLINE void emitWData(uint32_t code, WDataInP newval, int bits); VL_ATTR_ALWINLINE void emitDouble(uint32_t code, double newval); }; diff --git a/include/verilated_trace.h b/include/verilated_trace.h index 748e45f7e..de8f89946 100644 --- a/include/verilated_trace.h +++ b/include/verilated_trace.h @@ -378,7 +378,7 @@ public: // duck-typed void emitSData(uint32_t code, SData newval, int bits) = 0; // duck-typed void emitIData(uint32_t code, IData newval, int bits) = 0; // duck-typed void emitQData(uint32_t code, QData newval, int bits) = 0; - // duck-typed void emitWData(uint32_t code, const WData* newvalp, int bits) = 0; + // duck-typed void emitWData(uint32_t code, WDataInP newval, int bits) = 0; // duck-typed void emitDouble(uint32_t code, double newval) = 0; VL_ATTR_ALWINLINE uint32_t* oldp(uint32_t code) { return m_sigs_oldvalp + code; } @@ -389,7 +389,7 @@ public: void fullSData(uint32_t* oldp, SData newval, int bits); void fullIData(uint32_t* oldp, IData newval, int bits); void fullQData(uint32_t* oldp, QData newval, int bits); - void fullWData(uint32_t* oldp, const WData* newvalp, int bits); + void fullWData(uint32_t* oldp, WDataInP newval, int bits); void fullDouble(uint32_t* oldp, double newval); void fullEvent(uint32_t* oldp, const VlEventBase* newvalp); void fullEventTriggered(uint32_t* oldp); @@ -417,10 +417,10 @@ public: const uint64_t diff = old ^ newval; if (VL_UNLIKELY(diff)) fullQData(oldp, newval, bits); } - VL_ATTR_ALWINLINE void chgWData(uint32_t* oldp, const WData* newvalp, int bits) { + VL_ATTR_ALWINLINE void chgWData(uint32_t* oldp, WDataInP newval, int bits) { for (int i = 0; i < (bits + 31) / 32; ++i) { - if (VL_UNLIKELY(oldp[i] ^ newvalp[i])) { - fullWData(oldp, newvalp, bits); + if (VL_UNLIKELY(oldp[i] ^ newval[i])) { + fullWData(oldp, newval, bits); return; } } diff --git a/include/verilated_trace_imp.h b/include/verilated_trace_imp.h index 1916e5e9a..0843f1843 100644 --- a/include/verilated_trace_imp.h +++ b/include/verilated_trace_imp.h @@ -602,11 +602,11 @@ void VerilatedTraceBuffer::fullQData(uint32_t* oldp, QData newval, int } template <> -void VerilatedTraceBuffer::fullWData(uint32_t* oldp, const WData* newvalp, int bits) { +void VerilatedTraceBuffer::fullWData(uint32_t* oldp, WDataInP newval, int bits) { const uint32_t code = oldp - m_sigs_oldvalp; - for (int i = 0; i < VL_WORDS_I(bits); ++i) oldp[i] = newvalp[i]; + for (int i = 0; i < VL_WORDS_I(bits); ++i) oldp[i] = newval[i]; if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return; - emitWData(code, newvalp, bits); + emitWData(code, newval, bits); } template <> diff --git a/include/verilated_types.h b/include/verilated_types.h index 6e7833ae3..08a6b93bb 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -64,16 +64,166 @@ class VlUnpacked; } while (false) #endif +//=================================================================== +/// Verilog wide packed bit container. +/// Similar to std::array, but lighter weight, only methods needed +/// by Verilator, to help compile time. +/// +/// A 'struct' as we want this to be an aggregate type that allows +/// static aggregate initialization. Consider data members private. +/// +/// For example a Verilog "bit [94:0]" will become a VlWide<3> because 3*32 +/// bits are needed to hold the 95 bits. The MSB (bit 96) must always be +/// zero in memory, but during intermediate operations in the Verilated +/// internals is unpredictable. + +template +struct VlWide final { + static constexpr size_t Words = N_Words; + + // MEMBERS + // This should be the only data member, otherwise generated static initializers need updating + EData m_storage[N_Words]; // Contents of the packed array + + // CONSTRUCTORS + // Default constructors and destructor are used. Note however that C++20 requires that + // aggregate types do not have a user declared constructor, not even an explicitly defaulted + // one. + + // OPERATOR METHODS + // Default copy assignment operators are used. + bool operator==(const VlWide& that) const VL_PURE { + for (size_t i = 0; i < N_Words; ++i) { + if (m_storage[i] != that.m_storage[i]) return false; + } + return true; + } + bool operator!=(const VlWide& that) const VL_PURE { return !(*this == that); } + operator bool() const VL_PURE { + for (size_t i = 0; i < N_Words; ++i) { + if (m_storage[i]) return true; + } + return false; + } + EData& operator[](size_t index) VL_MT_SAFE { return m_storage[index]; } + const EData& operator[](size_t index) const VL_MT_SAFE { return m_storage[index]; } + + // METHODS + EData& at(size_t index) VL_MT_SAFE { return m_storage[index]; } + const EData& at(size_t index) const VL_MT_SAFE { return m_storage[index]; } + size_t size() const VL_PURE { return N_Words; } + EData* data() VL_MT_SAFE { return &m_storage[0]; } + const EData* data() const VL_MT_SAFE { return &m_storage[0]; } + inline bool operator<(const VlWide& rhs) const VL_PURE; +}; + +// Type trait to check if a type is VlWide +template +struct VlIsVlWide : public std::false_type {}; +template +struct VlIsVlWide> : public std::true_type {}; + +// Opaque handles to backing store of a VlWide, These are used to pass the +// backing store to runtime functions without having to template all the +// runtime functions on the VlWide template patameters. They created via +// implicit constructors from any VlWide, so a VlWide can be passed directly +// to functions that take one of these handles. The handles are 'opaque' in +// the sense that they are not primitive C++ types. They have necessary +// operators defined to allow them to behave like pointers to the extent +// it is necessary to implement the runtime functions. They are still only +// a single pointer underneath, so they will be passed in registers. + +// Read-Write VlWide handle +class WDataOutP final { + EData* m_datap; // The backing store - non const as the handle is assignable + + // Use WDataOutP::external() to create a handle from a raw pointer + explicit WDataOutP(EData* datap) VL_PURE : m_datap{datap} {} + +public: + // FACTORY METHODS + // Create a handle from a raw pointer - only to be used in verilated*.h + static WDataOutP external(EData* datap) VL_PURE { return WDataOutP{datap}; } + + // CONSTRUCTORS + // Implicit conversion from 'VlWide' + template + // cppcheck-suppress noExplicitConstructor + /* implicit */ WDataOutP(VlWide& vlWide) VL_PURE : m_datap{vlWide.data()} {} + WDataOutP(const WDataOutP& other) VL_PURE = default; + WDataOutP(WDataOutP&& other) VL_PURE = default; + WDataOutP& operator=(const WDataOutP& other) VL_PURE = default; + + // METHODS + EData* datap() const VL_PURE { return m_datap; } + operator bool() const VL_PURE { return m_datap; } + EData& operator[](size_t index) const VL_PURE { return m_datap[index]; } + WDataOutP operator+(size_t index) const VL_PURE { return WDataOutP(m_datap + index); } + WDataOutP operator+(int index) const VL_PURE { return WDataOutP(m_datap + index); } +}; +static_assert(sizeof(WDataOutP) == sizeof(EData*), "WDataOutP should be a single pointer"); + +// Read-Only VlWide handle +class WDataInP final { + const EData* m_datap; // The backing store - non const as the handle is assignable + + // Use WDataInP::external() to create a handle from a raw pointer + explicit WDataInP(const EData* datap) VL_PURE : m_datap{datap} {} + +public: + // FACTORY METHODS + // Create a handle from a raw pointer - only to be used in verilated*.h + static WDataInP external(const EData* datap) VL_PURE { return WDataInP{datap}; } + + // CONSTRUCTORS + // Implicit conversion from 'VlWide' + template + // cppcheck-suppress noExplicitConstructor + /* implicit */ WDataInP(VlWide& vlWide) VL_PURE : m_datap{vlWide.data()} {} + // Implicit conversion from 'const VlWide' + template + // cppcheck-suppress noExplicitConstructor + /* implicit */ WDataInP(const VlWide& vlWide) VL_PURE : m_datap{vlWide.data()} {} + // Implicit conversion from 'WDataOutP' + // cppcheck-suppress noExplicitConstructor + /* implicit */ WDataInP(const WDataOutP& owp) VL_PURE : m_datap{owp.datap()} {} + // Initialize with 'nullptr' + explicit WDataInP(std::nullptr_t) VL_PURE : m_datap{nullptr} {} + WDataInP(const WDataInP& other) VL_PURE = default; + WDataInP(WDataInP&& other) VL_PURE = default; + WDataInP& operator=(const WDataInP& other) VL_PURE = default; + + // METHODS + const EData* datap() const VL_PURE { return m_datap; } + operator bool() const VL_PURE { return m_datap; } + const EData& operator[](size_t index) const VL_PURE { return m_datap[index]; } + WDataInP operator+(size_t index) const VL_PURE { return WDataInP(m_datap + index); } + WDataInP operator+(int index) const VL_PURE { return WDataInP(m_datap + index); } +}; +static_assert(sizeof(WDataInP) == sizeof(EData*), "WDataInP should be a single pointer"); + +static int _vl_cmp_w(int words, WDataInP const lwp, WDataInP const rwp) VL_PURE; + +template +bool VlWide::operator<(const VlWide& rhs) const VL_PURE { + return _vl_cmp_w(N_Words, *this, rhs) < 0; +} + //=================================================================== // String formatters (required by below containers) +extern std::string VL_TO_STRING_W(int words, const WDataInP obj); + extern std::string VL_TO_STRING(CData lhs); extern std::string VL_TO_STRING(SData lhs); extern std::string VL_TO_STRING(IData lhs); extern std::string VL_TO_STRING(QData lhs); extern std::string VL_TO_STRING(double lhs); inline std::string VL_TO_STRING(const std::string& obj) { return "\"" + obj + "\""; } -extern std::string VL_TO_STRING_W(int words, const WDataInP obj); +template +inline std::string VL_TO_STRING(const VlWide& obj) { + return VL_TO_STRING_W(N_Words, obj); +} //========================================================================= // Declare net data types @@ -383,79 +533,6 @@ public: void print(QData addr, bool addrstamp, const void* valuep); }; -//=================================================================== -/// Verilog wide packed bit container. -/// Similar to std::array, but lighter weight, only methods needed -/// by Verilator, to help compile time. -/// -/// A 'struct' as we want this to be an aggregate type that allows -/// static aggregate initialization. Consider data members private. -/// -/// For example a Verilog "bit [94:0]" will become a VlWide<3> because 3*32 -/// bits are needed to hold the 95 bits. The MSB (bit 96) must always be -/// zero in memory, but during intermediate operations in the Verilated -/// internals is unpredictable. - -static int _vl_cmp_w(int words, WDataInP const lwp, WDataInP const rwp) VL_PURE; - -template -struct VlWide; - -// Type trait to check if a type is VlWide -template -struct VlIsVlWide : public std::false_type {}; - -template -struct VlIsVlWide> : public std::true_type {}; - -template -struct VlWide final { - static constexpr size_t Words = N_Words; - - // MEMBERS - // This should be the only data member, otherwise generated static initializers need updating - EData m_storage[N_Words]; // Contents of the packed array - - // CONSTRUCTORS - // Default constructors and destructor are used. Note however that C++20 requires that - // aggregate types do not have a user declared constructor, not even an explicitly defaulted - // one. - - // OPERATOR METHODS - // Default copy assignment operators are used. - operator WDataOutP() VL_PURE { return &m_storage[0]; } // This also allows [] - operator WDataInP() const VL_PURE { return &m_storage[0]; } // This also allows [] - bool operator==(const VlWide& that) const VL_PURE { - for (size_t i = 0; i < N_Words; ++i) { - if (m_storage[i] != that.m_storage[i]) return false; - } - return true; - } - bool operator!=(const VlWide& that) const VL_PURE { return !(*this == that); } - - // METHODS - const EData& at(size_t index) const { return m_storage[index]; } - EData& at(size_t index) { return m_storage[index]; } - size_t size() const { return N_Words; } - WData* data() { return &m_storage[0]; } - const WData* data() const { return &m_storage[0]; } - bool operator<(const VlWide& rhs) const { - return _vl_cmp_w(N_Words, data(), rhs.data()) < 0; - } -}; - -// Convert a C array to std::array reference by pointer magic, without copy. -// Data type (second argument) is so the function template can automatically generate. -template -VlWide& VL_CVT_W_A(const WDataInP inp, const VlWide&) { - return *((VlWide*)inp); -} - -template -std::string VL_TO_STRING(const VlWide& obj) { - return VL_TO_STRING_W(N_Words, obj.data()); -} - template class VlClassRef; @@ -1356,8 +1433,8 @@ public: // METHODS // Raw access - WData* data() { return &m_storage[0]; } - const WData* data() const { return &m_storage[0]; } + T_Value* data() { return &m_storage[0]; } + const T_Value* data() const { return &m_storage[0]; } constexpr std::size_t size() const { return N_Depth; } diff --git a/include/verilated_vcd_c.cpp b/include/verilated_vcd_c.cpp index 1ee7443f0..c0ca9281b 100644 --- a/include/verilated_vcd_c.cpp +++ b/include/verilated_vcd_c.cpp @@ -668,17 +668,17 @@ void VerilatedVcdBuffer::emitQData(uint32_t code, QData newval, int bits) { } VL_ATTR_ALWINLINE -void VerilatedVcdBuffer::emitWData(uint32_t code, const WData* newvalp, int bits) { +void VerilatedVcdBuffer::emitWData(uint32_t code, WDataInP newval, int bits) { int words = VL_WORDS_I(bits); char* wp = m_writep; *wp++ = 'b'; // Handle the most significant word const int bitsInMSW = VL_BITBIT_E(bits) ? VL_BITBIT_E(bits) : VL_EDATASIZE; - cvtEDataToStr(wp, newvalp[--words] << (VL_EDATASIZE - bitsInMSW)); + cvtEDataToStr(wp, newval[--words] << (VL_EDATASIZE - bitsInMSW)); wp += bitsInMSW; // Handle the remaining words while (words > 0) { - cvtEDataToStr(wp, newvalp[--words]); + cvtEDataToStr(wp, newval[--words]); wp += VL_EDATASIZE; } finishLine(code, wp); diff --git a/include/verilated_vcd_c.h b/include/verilated_vcd_c.h index f71e2513c..931035bed 100644 --- a/include/verilated_vcd_c.h +++ b/include/verilated_vcd_c.h @@ -251,7 +251,7 @@ class VerilatedVcdBuffer VL_NOT_FINAL { VL_ATTR_ALWINLINE void emitSData(uint32_t code, SData newval, int bits); VL_ATTR_ALWINLINE void emitIData(uint32_t code, IData newval, int bits); VL_ATTR_ALWINLINE void emitQData(uint32_t code, QData newval, int bits); - VL_ATTR_ALWINLINE void emitWData(uint32_t code, const WData* newvalp, int bits); + VL_ATTR_ALWINLINE void emitWData(uint32_t code, WDataInP newval, int bits); VL_ATTR_ALWINLINE void emitDouble(uint32_t code, double newval); }; diff --git a/include/verilatedos.h b/include/verilatedos.h index b93909309..03cdd8bdb 100644 --- a/include/verilatedos.h +++ b/include/verilatedos.h @@ -438,8 +438,8 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read() // Constants for VL_SFORMATF; see V3Number.h VFormatAttr // Character codes are upper case so harder to confuse with format %codes. // (...) indicates what is passed as arguments in emitted code -#define VL_VFORMATATTR_UNSIGNED '#' // (int widthMin, IData/WData/etc) Use standard format -#define VL_VFORMATATTR_SIGNED '~' // (int widthMin, IData/WData/etc) Signed number; for %d showing sign +#define VL_VFORMATATTR_UNSIGNED '#' // (int widthMin, IData/VlWide/etc) Use standard format +#define VL_VFORMATATTR_SIGNED '~' // (int widthMin, IData/VlWide/etc) Signed number; for %d showing sign #define VL_VFORMATATTR_COMPLEX '!' // (std::string*); for non-POD; e.g. struct, requires %p typically #define VL_VFORMATATTR_DOUBLE 'D' // (double); promote %p to %f #define VL_VFORMATATTR_SCOPE 'M' // (char* name, char* scope); for scopes @@ -462,7 +462,7 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read() #define VL_SHORTSIZE 16 ///< Bits in a SData / short #define VL_IDATASIZE 32 ///< Bits in an IData / word #define VL_QUADSIZE 64 ///< Bits in a QData / quadword -#define VL_EDATASIZE 32 ///< Bits in an EData (WData entry) +#define VL_EDATASIZE 32 ///< Bits in an EData (VlWide entry) #define VL_EDATASIZE_LOG2 5 ///< log2(VL_EDATASIZE) #define VL_CACHE_LINE_BYTES 64 ///< Bytes in a cache line (for alignment) diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index ed5a3da98..46d4ac746 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -4873,7 +4873,7 @@ class AstWordSel final : public AstNodeSel { public: AstWordSel(FileLine* fl, AstNodeExpr* fromp, AstNodeExpr* bitp) : ASTGEN_SUPER_WordSel(fl, fromp, bitp) { - dtypeSetUInt32(); // Always used on WData arrays so returns edata size + dtypeSetUInt32(); // Always used on VlWide arrays so returns EData size } ASTGEN_MEMBERS_AstWordSel; void numberOperate(V3Number&, const V3Number&, const V3Number&) override { V3ERROR_NA; } diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 97d8decc7..5b1d2444c 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -227,6 +227,7 @@ bool EmitCFunc::displayEmitHeader(AstNode* nodep) { puts(cvtToStr(dispp->lhsp()->widthMin())); putbs(","); iterateConst(dispp->lhsp()); + emitDatap(dispp->lhsp()); putbs(","); } else if (VN_IS(nodep, SFormatF)) { isStmt = false; @@ -285,6 +286,7 @@ void EmitCFunc::displayNode(AstNode* nodep, AstSFormatF* fmtp, // fmtp is nullp if (exprFormat) { UASSERT_OBJ(exprsp, nodep, "Missing format expression"); iterateConst(exprsp); + emitDatap(exprsp); exprsp = exprsp->nextp(); } else { ofp()->putsQuoted(vformat); @@ -430,15 +432,6 @@ void EmitCFunc::emitCvtPackStr(AstNode* nodep) { } } -void EmitCFunc::emitCvtWideArray(AstNode* nodep, AstNode* fromp) { - putnbs(nodep, "VL_CVT_W_A("); - iterateConst(nodep); - puts(", "); - iterateConst(fromp); - putbs(".atDefault()"); // Not accessed; only to get the proper type of values - puts(")"); -} - void EmitCFunc::emitConstant(AstConst* nodep) { // Put out constant set to the specified variable, or given variable in a string const V3Number& num = nodep->num(); @@ -536,18 +529,16 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing, // Returns string to do resetting, empty to do nothing (which caller should handle) if (AstAssocArrayDType* const adtypep = VN_CAST(dtypep, AssocArrayDType)) { // Access std::array as C array - const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n"; return pre + emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), - depth + 1, suffix + ".atDefault()" + cvtarray, nullptr); + depth + 1, suffix + ".atDefault()", nullptr); } else if (AstWildcardArrayDType* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) { // Access std::array as C array - const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n"; return pre + emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), - depth + 1, suffix + ".atDefault()" + cvtarray, nullptr); + depth + 1, suffix + ".atDefault()", nullptr); } else if (VN_IS(dtypep, CDType)) { return ""; // Constructor does it } else if (VN_IS(dtypep, ClassRefDType)) { @@ -555,19 +546,15 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing, } else if (VN_IS(dtypep, IfaceRefDType)) { return varNameProtected + suffix + " = nullptr;\n"; } else if (const AstDynArrayDType* const adtypep = VN_CAST(dtypep, DynArrayDType)) { - // Access std::array as C array - const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n"; return pre + emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), - depth + 1, suffix + ".atDefault()" + cvtarray, nullptr); + depth + 1, suffix + ".atDefault()", nullptr); } else if (const AstQueueDType* const adtypep = VN_CAST(dtypep, QueueDType)) { - // Access std::array as C array - const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n"; return pre + emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), - depth + 1, suffix + ".atDefault()" + cvtarray, nullptr); + depth + 1, suffix + ".atDefault()", nullptr); } else if (VN_IS(dtypep, SampleQueueDType)) { return ""; } else if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 3c05a6055..81552f724 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -187,7 +187,6 @@ public: void emitDereference(AstNode* nodep, const string& pointer); std::string dereferenceString(const std::string& pointer) const; void emitCvtPackStr(AstNode* nodep); - void emitCvtWideArray(AstNode* nodep, AstNode* fromp); void emitConstant(AstConst* nodep); void emitConstantString(const AstConst* nodep); void emitSetVarConstant(const string& assignString, AstConst* constp); @@ -741,10 +740,7 @@ public: int argNum = 0; for (AstNode* subnodep = nodep->pinsp(); subnodep; subnodep = subnodep->nextp()) { if (comma) puts(", "); - // handle wide arguments to the queues - if (VN_IS(nodep->fromp()->dtypep(), QueueDType) && subnodep->dtypep()->isWide()) { - emitCvtWideArray(subnodep, nodep->fromp()); - } else if (nodep->method() == VCMethod::RANDOMIZER_HARD && argNum == 1) { + 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)) { diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 7947bb070..2ab7cffe7 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -455,31 +455,29 @@ class EmitCHeader final : public EmitCConstInit { puts("}\n"); } else if (VN_IS(dtypep, NodeUOrStructDType)) { const std::string tmp = m_names.get("__Vtmp"); - const std::string suffixName = dtypep->isWide() ? tmp + ".data()" : tmp; if (getfunc) { // Emit `get` func; // auto __tmp = field.get(); puts("auto " + tmp + " = " + fieldname + ".get();\n"); // VL_ASSIGNSEL_XX(rbits, obits, lsb, lhsdata, rhsdata); - emitVlAssign(parentDtypep, dtypep, offset, retOrArg, suffixName, getfunc); + emitVlAssign(parentDtypep, dtypep, offset, retOrArg, tmp, getfunc); } else { // Emit `set` func const std::string tmptype = AstCDType::typeToHold(dtypep->width()); // type tmp; puts(tmptype + " " + tmp + ";\n"); // VL_SELASSIGN_XX(rbits, obits, lhsdata, rhsdata, roffset); - emitVlAssign(dtypep, parentDtypep, offset, suffixName, retOrArg, getfunc); + emitVlAssign(dtypep, parentDtypep, offset, tmp, retOrArg, getfunc); // field.set(__tmp); puts(fieldname + ".set(" + tmp + ");\n"); } } else { UASSERT_OBJ(VN_IS(dtypep, EnumDType) || VN_IS(dtypep, BasicDType), dtypep, "Unsupported type in packed struct or union"); - const std::string suffixName = dtypep->isWide() ? fieldname + ".data()" : fieldname; if (getfunc) { // Emit `get` func; // VL_ASSIGNSEL_XX(rbits, obits, lsb, lhsdata, rhsdata); - emitVlAssign(parentDtypep, dtypep, offset, retOrArg, suffixName, getfunc); + emitVlAssign(parentDtypep, dtypep, offset, retOrArg, fieldname, getfunc); } else { // Emit `set` func // VL_SELASSIGN_XX(rbits, obits, lhsdata, rhsdata, roffset); - emitVlAssign(dtypep, parentDtypep, offset, suffixName, retOrArg, getfunc); + emitVlAssign(dtypep, parentDtypep, offset, fieldname, retOrArg, getfunc); } } } @@ -501,7 +499,6 @@ class EmitCHeader final : public EmitCConstInit { } const std::string retArgName = m_names.get("__v"); - const std::string suffixName = sdtypep->isWide() ? retArgName + ".data()" : retArgName; const std::string retArgType = AstCDType::typeToHold(sdtypep->width()); // Emit `get` member function @@ -510,12 +507,12 @@ class EmitCHeader final : public EmitCConstInit { if (VN_IS(sdtypep, StructDType)) { for (itemp = lastItemp; itemp; itemp = VN_CAST(itemp->backp(), MemberDType)) { emitPackedMember(sdtypep, itemp->dtypep(), itemp->nameProtect(), - std::to_string(itemp->lsb()), /*getfunc=*/true, suffixName); + std::to_string(itemp->lsb()), /*getfunc=*/true, retArgName); } } else { // We only need to fill the widest field of union emitPackedMember(sdtypep, witemp->dtypep(), witemp->nameProtect(), - std::to_string(witemp->lsb()), /*getfunc=*/true, suffixName); + std::to_string(witemp->lsb()), /*getfunc=*/true, retArgName); } puts("return " + retArgName + ";\n"); puts("}\n"); @@ -525,12 +522,12 @@ class EmitCHeader final : public EmitCConstInit { if (VN_IS(sdtypep, StructDType)) { for (itemp = lastItemp; itemp; itemp = VN_CAST(itemp->backp(), MemberDType)) { emitPackedMember(sdtypep, itemp->dtypep(), itemp->nameProtect(), - std::to_string(itemp->lsb()), /*getfunc=*/false, suffixName); + std::to_string(itemp->lsb()), /*getfunc=*/false, retArgName); } } else { // We only need to fill the widest field of union emitPackedMember(sdtypep, witemp->dtypep(), witemp->nameProtect(), - std::to_string(witemp->lsb()), /*getfunc=*/false, suffixName); + std::to_string(witemp->lsb()), /*getfunc=*/false, retArgName); } puts("}\n"); diff --git a/test_regress/t/t_func_public.v b/test_regress/t/t_func_public.v index 059126c6f..301cd4e47 100644 --- a/test_regress/t/t_func_public.v +++ b/test_regress/t/t_func_public.v @@ -120,7 +120,7 @@ module tpub ( if (24'h11bca != got_long) $stop; $c("{ uint64_t qq; this->publicGetQuad(qq); this->got_quad=qq; }"); if (60'haaaa_bbbb_cccc != got_quad) $stop; - $c("{ WData gw[3]; this->publicGetWide(gw); VL_ASSIGN_W(72,this->got_wide,gw); }"); + $c("{ EData gw[3]; this->publicGetWide(gw); VL_ASSIGN_W(72,this->got_wide,WDataInP::external(gw)); }"); if (72'hac_abca_aaaa_bbbb_1234 != got_wide) $stop; //Below doesn't work, because we're calling it inside the loop that sets var_flop // if (12'h321 != var_flop) $stop; diff --git a/test_regress/t/t_var_sc_bv.cpp b/test_regress/t/t_var_sc_bv.cpp index 116ac6aa5..fe9fc213b 100644 --- a/test_regress/t/t_var_sc_bv.cpp +++ b/test_regress/t/t_var_sc_bv.cpp @@ -180,35 +180,35 @@ int main() //////////////////////////////// VL_ASSIGN_WSB(29, out_var, o_29_s); - compareWls(29, input_var.data(), out_var.data()); + compareWls(29, input_var, out_var); VL_ASSIGN_WSB(30, out_var, o_30_s); - compareWls(30, input_var.data(), out_var.data()); + compareWls(30, input_var, out_var); VL_ASSIGN_WSB(31, out_var, o_31_s); - compareWls(31, input_var.data(), out_var.data()); + compareWls(31, input_var, out_var); VL_ASSIGN_WSB(32, out_var, o_32_s); - compareWls(32, input_var.data(), out_var.data()); + compareWls(32, input_var, out_var); VL_ASSIGN_WSB(59, out_var, o_59_s); - compareWls(59, input_var.data(), out_var.data()); + compareWls(59, input_var, out_var); VL_ASSIGN_WSB(60, out_var, o_60_s); - compareWls(60, input_var.data(), out_var.data()); + compareWls(60, input_var, out_var); VL_ASSIGN_WSB(62, out_var, o_62_s); - compareWls(62, input_var.data(), out_var.data()); + compareWls(62, input_var, out_var); VL_ASSIGN_WSB(64, out_var, o_64_s); - compareWls(64, input_var.data(), out_var.data()); + compareWls(64, input_var, out_var); VL_ASSIGN_WSB(119, out_var, o_119_s); - compareWls(119, input_var.data(), out_var.data()); + compareWls(119, input_var, out_var); VL_ASSIGN_WSB(120, out_var, o_120_s); - compareWls(120, input_var.data(), out_var.data()); + compareWls(120, input_var, out_var); VL_ASSIGN_WSB(121, out_var, o_121_s); - compareWls(121, input_var.data(), out_var.data()); + compareWls(121, input_var, out_var); VL_ASSIGN_WSB(127, out_var, o_127_s); - compareWls(127, input_var.data(), out_var.data()); + compareWls(127, input_var, out_var); VL_ASSIGN_WSB(128, out_var, o_128_s); - compareWls(128, input_var.data(), out_var.data()); + compareWls(128, input_var, out_var); VL_ASSIGN_WSB(255, out_var, o_255_s); - compareWls(255, input_var.data(), out_var.data()); + compareWls(255, input_var, out_var); VL_ASSIGN_WSB(256, out_var, o_256_s); - compareWls(256, input_var.data(), out_var.data()); + compareWls(256, input_var, out_var); tb->final(); VL_DO_DANGLING(delete tb, tb);