From 1f0c1d47a9d675c8ae40bbb6df3ae8114bb4a091 Mon Sep 17 00:00:00 2001 From: Christian Hecken Date: Wed, 12 Nov 2025 02:38:11 +0100 Subject: [PATCH 1/3] Internals: Add isForceable() to VerilatedVarProps Allows runtime checking whether a signal is forceable without needing to check the existence of the `__VforceEn` and `__VforceVal` signals. This will be useful for a later implementation of `vpiForceFlag` for `vpi_put_value`. --- include/verilated.h | 3 ++- include/verilated_sym_props.h | 1 + src/V3AstNodes.cpp | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/verilated.h b/include/verilated.h index 02c022176..acfab9cb0 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -154,7 +154,8 @@ enum VerilatedVarFlags { // Flags VLVF_PUB_RD = (1 << 8), // Public readable VLVF_PUB_RW = (1 << 9), // Public writable - VLVF_DPI_CLAY = (1 << 10) // DPI compatible C standard layout + VLVF_DPI_CLAY = (1 << 10), // DPI compatible C standard layout + VLVF_FORCEABLE = (1 << 11) // Forceable }; // IEEE 1800-2023 Table 20-6 diff --git a/include/verilated_sym_props.h b/include/verilated_sym_props.h index 53600e7a1..65988fccb 100644 --- a/include/verilated_sym_props.h +++ b/include/verilated_sym_props.h @@ -156,6 +156,7 @@ public: return bits; } bool isPublicRW() const { return ((m_vlflags & VLVF_PUB_RW) != 0); } + bool isForceable() const { return ((m_vlflags & VLVF_FORCEABLE) != 0); } // DPI compatible C standard layout bool isDpiCLayout() const { return ((m_vlflags & VLVF_DPI_CLAY) != 0); } int udims() const VL_MT_SAFE { return m_unpacked.size(); } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 6a5295ad1..58b30787d 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -630,6 +630,9 @@ string AstVar::vlEnumDir() const { } else if (isSigUserRdPublic()) { out += "|VLVF_PUB_RD"; } + if (isForceable()) { + out += "|VLVF_FORCEABLE"; + } // if (const AstBasicDType* const bdtypep = basicp()) { if (bdtypep->keyword().isDpiCLayout()) out += "|VLVF_DPI_CLAY"; From b0adf5f7af28cfba6489afcdaaad52fb71e84736 Mon Sep 17 00:00:00 2001 From: Christian Hecken Date: Fri, 14 Nov 2025 00:33:42 +0100 Subject: [PATCH 2/3] Add vpi_put/vpi_get support for forcing signals This commit includes PR #6688. It adds support for `vpiForceFlag` and `vpiReleaseFlag` to `vpi_put_value`. `vpi_get_value` is updated to return the value of either the signal itself, or the `__VforceVal` control signal, depending on whether the forcing is active. The functionality is tested in the tests `t_vpi_force`, which tests that forcing and releasing works on a clocked register being forced with a VpiIntVal, as well as `t_vpi_forceable_bad`, which tests that attempting to force a signal without marking it forceable causes an error. The tests were run under Verilator (based on dc00bf248 with the commit for isForceable applied), `Icarus Verilog version 13.0 (devel) (s20251012-20-gcc496c3cf)`, and `xrun 24.09-a071`. The implementation is simply done using string concatenation of the signal name with the __VforceEn and __VforceVal suffixes. The signal vop that the vpi_put/get functions operate on is then pointed towards either the base signal or the __VforceVal signal (for vpi_put) or the __VforceRd signal (for vpi_get). While it works and passes the newly implemented tests, this solution is quite brittle and in part re-implements existing functionality by recreating the `__VforceRd` signal, so once #6705 is completed, it should extend `VerilatedVar` to hold information about the force control signals, which is provided at Verilation time, rather than a runtime lookup. Because `valuep` should get set to the value of the signal after forcing when `vpi_put_value` is called with `vpiReleaseFlag`, this commit also adds information about `isContinuously` to the `VerilatedVarFlags`. Lastly, since unpacked arrays cannot be forced (#4735), a Verilation time check for this was added and tested in `t_forceable_unpacked_bad`, which simplifies the error handling in `vpi_put_value` and `vpi_get_value`. The same was done for forceable strings, which is tested in `t_forceable_string_bad`. Fixes #5933 --- .clang-tidy | 2 +- include/verilated.h | 3 +- include/verilated_sym_props.h | 1 + include/verilated_vpi.cpp | 380 ++++++-- src/V3AstNodes.cpp | 5 +- src/V3Force.cpp | 7 + test_regress/t/t_forceable_unpacked_bad.out | 5 + test_regress/t/t_forceable_unpacked_bad.py | 16 + test_regress/t/t_forceable_unpacked_bad.v | 9 + test_regress/t/t_vpi_force.cpp | 907 ++++++++++++++++++++ test_regress/t/t_vpi_force.py | 22 + test_regress/t/t_vpi_force.v | 732 ++++++++++++++++ test_regress/t/t_vpi_forceable_bad.cpp | 65 ++ test_regress/t/t_vpi_forceable_bad.out | 2 + test_regress/t/t_vpi_forceable_bad.py | 24 + test_regress/t/t_vpi_forceable_bad.v | 51 ++ 16 files changed, 2173 insertions(+), 58 deletions(-) create mode 100644 test_regress/t/t_forceable_unpacked_bad.out create mode 100755 test_regress/t/t_forceable_unpacked_bad.py create mode 100644 test_regress/t/t_forceable_unpacked_bad.v create mode 100644 test_regress/t/t_vpi_force.cpp create mode 100755 test_regress/t/t_vpi_force.py create mode 100644 test_regress/t/t_vpi_force.v create mode 100644 test_regress/t/t_vpi_forceable_bad.cpp create mode 100644 test_regress/t/t_vpi_forceable_bad.out create mode 100755 test_regress/t/t_vpi_forceable_bad.py create mode 100644 test_regress/t/t_vpi_forceable_bad.v diff --git a/.clang-tidy b/.clang-tidy index c15dd3e2b..be6fec5d6 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,4 +1,4 @@ -Checks: '*,-hicpp*,-android-cloexec-fopen,-cert-dcl50-cpp,-cert-env33-c,-cert-err34-c,-cert-err58-cpp,-clang-analyzer-core.UndefinedBinaryOperatorResult,-clang-analyzer-security*,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-no-malloc,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-type-const-cast,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-static-cast-downcast,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-special-member-functions,-fuchsia-*,-google-default-arguments,-google-readability-todo,-google-runtime-references,-llvm-header-guard,-llvm-include-order,-misc-string-integer-assignment,-misc-string-literal-with-embedded-nul,-modernize-use-auto,-modernize-use-trailing-return-type,-readability-braces-around-statements,-readability-container-size-empty,-readability-delete-null-pointer,-readability-else-after-return,-readability-implicit-bool-conversion,-readability-named-parameter,-readability-static-accessed-through-instance,-llvmlibc-*,-altera-*' +Checks: '*,-hicpp*,-android-cloexec-fopen,-cert-dcl50-cpp,-cert-env33-c,-cert-err34-c,-cert-err58-cpp,-clang-analyzer-core.UndefinedBinaryOperatorResult,-clang-analyzer-security*,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-no-malloc,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-type-const-cast,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-static-cast-downcast,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-special-member-functions,-fuchsia-*,-google-default-arguments,-google-readability-todo,-google-runtime-references,-llvm-header-guard,-llvm-include-order,-misc-string-integer-assignment,-misc-string-literal-with-embedded-nul,-modernize-use-auto,-modernize-use-trailing-return-type,-readability-braces-around-statements,-readability-container-size-empty,-readability-delete-null-pointer,-readability-else-after-return,-readability-implicit-bool-conversion,-readability-named-parameter,-readability-static-accessed-through-instance,-llvmlibc-*,-altera-*,-boost-use-ranges' WarningsAsErrors: '' HeaderFilterRegex: '' FormatStyle: none diff --git a/include/verilated.h b/include/verilated.h index acfab9cb0..48cdafd92 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -155,7 +155,8 @@ enum VerilatedVarFlags { VLVF_PUB_RD = (1 << 8), // Public readable VLVF_PUB_RW = (1 << 9), // Public writable VLVF_DPI_CLAY = (1 << 10), // DPI compatible C standard layout - VLVF_FORCEABLE = (1 << 11) // Forceable + VLVF_FORCEABLE = (1 << 11), // Forceable + VLVF_CONTINUOUSLY = (1 << 12) // Is continously assigned }; // IEEE 1800-2023 Table 20-6 diff --git a/include/verilated_sym_props.h b/include/verilated_sym_props.h index 65988fccb..2015d6ce8 100644 --- a/include/verilated_sym_props.h +++ b/include/verilated_sym_props.h @@ -157,6 +157,7 @@ public: } bool isPublicRW() const { return ((m_vlflags & VLVF_PUB_RW) != 0); } bool isForceable() const { return ((m_vlflags & VLVF_FORCEABLE) != 0); } + bool isContinuously() const { return ((m_vlflags & VLVF_CONTINUOUSLY) != 0); } // DPI compatible C standard layout bool isDpiCLayout() const { return ((m_vlflags & VLVF_DPI_CLAY) != 0); } int udims() const VL_MT_SAFE { return m_unpacked.size(); } diff --git a/include/verilated_vpi.cpp b/include/verilated_vpi.cpp index c8ee28401..bacc82ff7 100644 --- a/include/verilated_vpi.cpp +++ b/include/verilated_vpi.cpp @@ -24,12 +24,12 @@ /// //========================================================================= +#include "verilatedos.h" #define VERILATOR_VERILATED_VPI_CPP_ -#include "verilated_vpi.h" - #include "verilated.h" #include "verilated_imp.h" +#include "verilated_vpi.h" #include "vltstd/vpi_user.h" @@ -880,6 +880,7 @@ struct VerilatedVpiTimedCbsCmp final { }; class VerilatedVpiError; +void vl_vpi_put_word(const VerilatedVpioVar* vop, QData word, size_t bitCount, size_t addOffset); class VerilatedVpiImp final { enum { CB_ENUM_MAX_VALUE = cbAtEndOfSimTime + 1 }; // Maximum callback reason @@ -1075,6 +1076,47 @@ public: } s().m_inertialPuts.clear(); } + static std::pair getForceControlSignals(const VerilatedVpioVarBase* vop); + + static std::size_t vlTypeSize(VerilatedVarType vltype); + static void setAllBitsToValue(const VerilatedVpioVar* vop, uint8_t bitValue) { + assert(bitValue == 0 || bitValue == 1); + const uint64_t word = (bitValue == 1) ? -1ULL : 0ULL; + const std::size_t wordSize = vlTypeSize(vop->varp()->vltype()); + assert(wordSize > 0); + const uint32_t varBits = vop->bitSize(); + const std::size_t numChunks = (varBits / wordSize); + for (std::size_t i{0}; i < numChunks; ++i) { + vl_vpi_put_word(vop, word, wordSize, i * wordSize); + } + // addOffset == varBits would trigger assertion in vl_vpi_var_access_info even if + // bitCount == 0, so first check if there is a remainder + if (varBits % wordSize != 0) + vl_vpi_put_word(vop, word, varBits % wordSize, numChunks * wordSize); + } + + // Recreates the __VforceRd signal's data vector, since __VforceRd is not publicly accessible + // in Verilated code. + template + static std::vector + createReadDataVector(const void* const baseSignalDatap, + const std::pair forceControlDatap, + const std::size_t bitCount) { + const void* const forceEnableDatap = forceControlDatap.first; + const void* const forceValueDatap = forceControlDatap.second; + assert(bitCount > 0); + const std::size_t numWords = (bitCount + (8 * sizeof(T)) - 1) / (8 * sizeof(T)); // Ceil + std::vector readData(numWords); + for (std::size_t i{0}; i < numWords; ++i) { + const T forceEnableWord = reinterpret_cast(forceEnableDatap)[i]; + const T forceValueWord = reinterpret_cast(forceValueDatap)[i]; + const T baseSignalWord = reinterpret_cast(baseSignalDatap)[i]; + const T readDataWord + = (forceEnableWord & forceValueWord) | (~forceEnableWord & baseSignalWord); + readData[i] = readDataWord; + } + return readData; + } }; //====================================================================== @@ -1229,6 +1271,47 @@ VerilatedVpiError* VerilatedVpiImp::error_info() VL_MT_UNSAFE_ONE { return s().m_errorInfop; } +std::pair +VerilatedVpiImp::getForceControlSignals(const VerilatedVpioVarBase* const vop) { + const std::string signalName = vop->fullname(); + const std::string forceEnableSignalName = signalName + "__VforceEn"; + const std::string forceValueSignalName = signalName + "__VforceVal"; + + vpiHandle const forceEnableSignalp // NOLINT(misc-misplaced-const) + = vpi_handle_by_name(const_cast(forceEnableSignalName.c_str()), nullptr); + vpiHandle const forceValueSignalp // NOLINT(misc-misplaced-const) + = vpi_handle_by_name(const_cast(forceValueSignalName.c_str()), nullptr); + if (VL_UNLIKELY(!VerilatedVpioVar::castp(forceEnableSignalp))) { + VL_VPI_ERROR_(__FILE__, __LINE__, + "%s: vpi force or release requested for '%s', but vpiHandle '%p' of enable " + "signal '%s' could not be cast to VerilatedVpioVar*. Ensure signal is " + "marked as forceable", + __func__, signalName.c_str(), forceEnableSignalp, + forceEnableSignalName.c_str()); + } + if (VL_UNLIKELY(!VerilatedVpioVar::castp(forceValueSignalp))) { + VL_VPI_ERROR_(__FILE__, __LINE__, + "%s: vpi force or release requested for '%s', but vpiHandle '%p' of value " + "signal '%s' could not be cast to VerilatedVpioVar*. Ensure signal is " + "marked as forceable", + __func__, signalName.c_str(), forceValueSignalp, + forceValueSignalName.c_str()); + } + return {forceEnableSignalp, forceValueSignalp}; +}; + +std::size_t VerilatedVpiImp::vlTypeSize(const VerilatedVarType vltype) { + switch (vltype) { + case VLVT_UINT8: return sizeof(CData); break; + case VLVT_UINT16: return sizeof(SData); break; + case VLVT_UINT32: return sizeof(IData); break; + case VLVT_UINT64: return sizeof(QData); break; + case VLVT_WDATA: return sizeof(EData); break; + default: // LCOV_EXCL_START + VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vltype (%d)", __func__, vltype); + return 0; + } // LCOV_EXCL_STOP +} //====================================================================== // VerilatedVpiError Methods @@ -2604,6 +2687,66 @@ void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) { const int varBits = vop->bitSize(); + // __VforceRd already has the correct value, but that signal is not public and thus not + // present in the scope's m_varsp map, so its value has to be recreated using the __VforceEn + // and __VforceVal signals. + // TODO: Implement a way to retrieve __VforceRd, rather than needing to recreate it. + const auto forceControlSignals = vop->varp()->isForceable() + ? VerilatedVpiImp::getForceControlSignals(vop) + : std::pair{nullptr, nullptr}; + const vpiHandle& forceEnableSignalp = forceControlSignals.first; + const vpiHandle& forceValueSignalp = forceControlSignals.second; + const VerilatedVpioVarBase* const forceEnableSignalVop + = vop->varp()->isForceable() ? VerilatedVpioVar::castp(forceEnableSignalp) : nullptr; + const VerilatedVpioVarBase* const forceValueSignalVop + = vop->varp()->isForceable() ? VerilatedVpioVar::castp(forceValueSignalp) : nullptr; + + t_vpi_error_info getForceControlSignalsError{}; + const bool errorOccurred = vpi_chk_error(&getForceControlSignalsError); + // LCOV_EXCL_START - Cannot test, since getForceControlSignals does not (currently) produce + // any notices or warnings. + if (errorOccurred && getForceControlSignalsError.level < vpiError) { + vpi_printf(getForceControlSignalsError.message); + VL_VPI_ERROR_RESET_(); + } // LCOV_EXCL_STOP + // NOLINTNEXTLINE(readability-simplify-boolean-expr); + if (VL_UNLIKELY((errorOccurred && getForceControlSignalsError.level >= vpiError) + || (vop->varp()->isForceable() + && (!forceEnableSignalp || !forceEnableSignalVop || !forceValueSignalp + || !forceValueSignalVop)))) { + + // Check if getForceControlSignals provided any additional error info + t_vpi_error_info getForceControlSignalsError{}; + const bool gotErrorMessage = vpi_chk_error(&getForceControlSignalsError); + const std::string previousErrorMessage + = gotErrorMessage + ? std::string{" Error message: "} + getForceControlSignalsError.message + : ""; + + VL_VPI_ERROR_(__FILE__, __LINE__, + "%s: Signal '%s' is marked forceable, but force " + "control signals could not be retrieved.%s", + __func__, vop->fullname(), + gotErrorMessage ? previousErrorMessage.c_str() : ""); + return; + } + + const std::function getForceableSignalWord + = [forceEnableSignalVop, forceValueSignalVop](const VerilatedVpioVarBase* baseSignalVop, + size_t bitCount, size_t addOffset) -> QData { + // variables are QData, even though signals may have different representation, because any + // extraneous bits are simply truncated upon implicit casting when this function is called. + const QData baseSignalData = vl_vpi_get_word(baseSignalVop, bitCount, addOffset); + const QData forceEnableData = vl_vpi_get_word(forceEnableSignalVop, bitCount, addOffset); + const QData forceValueData = vl_vpi_get_word(forceValueSignalVop, bitCount, addOffset); + const QData readData + = (forceEnableData & forceValueData) | (~forceEnableData & baseSignalData); + return readData; + }; + + const std::function get_word + = vop->varp()->isForceable() ? getForceableSignalWord : vl_vpi_get_word; + // We used to presume vpiValue.format = vpiIntVal or if single bit vpiScalarVal // This may cause backward compatibility issues with older code. if (valuep->format == vpiVectorVal) { @@ -2621,25 +2764,38 @@ void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) { return; } for (int i = 0; i < words; ++i) { - t_out[i].aval = vl_vpi_get_word(vop, 32, i * 32); + t_out[i].aval = get_word(vop, 32, i * 32); t_out[i].bval = 0; } return; } else if (varp->vltype() == VLVT_UINT64 && varBits > 32) { - const QData data = vl_vpi_get_word(vop, 64, 0); + const QData data = get_word(vop, 64, 0); t_out[1].aval = static_cast(data >> 32ULL); t_out[1].bval = 0; t_out[0].aval = static_cast(data); t_out[0].bval = 0; return; } else { - t_out[0].aval = vl_vpi_get_word(vop, 32, 0); + t_out[0].aval = get_word(vop, 32, 0); t_out[0].bval = 0; return; } } else if (valuep->format == vpiBinStrVal) { t_outDynamicStr.resize(varBits); - const CData* datap = reinterpret_cast(varDatap); + + static thread_local std::vector forceReadCData; + forceReadCData + = vop->varp()->isForceable() + ? VerilatedVpiImp::createReadDataVector( + varDatap, + {forceEnableSignalVop->varDatap(), forceValueSignalVop->varDatap()}, + vop->bitSize()) + : std::vector{}; + const uint8_t* const varCDatap = vop->varp()->isForceable() + ? forceReadCData.data() + : reinterpret_cast(varDatap); + + const CData* datap = varCDatap; for (size_t i = 0; i < varBits; ++i) { const size_t pos = i + vop->bitOffset(); const char val = (datap[pos >> 3] >> (pos & 7)) & 1; @@ -2651,24 +2807,22 @@ void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) { const int chars = (varBits + 2) / 3; t_outDynamicStr.resize(chars); for (size_t i = 0; i < chars; ++i) { - const char val = vl_vpi_get_word(vop, 3, i * 3); + const char val = get_word(vop, 3, i * 3); t_outDynamicStr[chars - i - 1] = '0' + val; } valuep->value.str = const_cast(t_outDynamicStr.c_str()); return; } else if (valuep->format == vpiDecStrVal) { if (varp->vltype() == VLVT_UINT8) { - vl_strprintf(t_outDynamicStr, "%hhu", - static_cast(vl_vpi_get_word(vop, 8, 0))); + vl_strprintf(t_outDynamicStr, "%hhu", static_cast(get_word(vop, 8, 0))); } else if (varp->vltype() == VLVT_UINT16) { vl_strprintf(t_outDynamicStr, "%hu", - static_cast(vl_vpi_get_word(vop, 16, 0))); + static_cast(get_word(vop, 16, 0))); } else if (varp->vltype() == VLVT_UINT32) { - vl_strprintf(t_outDynamicStr, "%u", - static_cast(vl_vpi_get_word(vop, 32, 0))); + vl_strprintf(t_outDynamicStr, "%u", static_cast(get_word(vop, 32, 0))); } else if (varp->vltype() == VLVT_UINT64) { vl_strprintf(t_outDynamicStr, "%llu", // lintok-format-ll - static_cast(vl_vpi_get_word(vop, 64, 0))); + static_cast(get_word(vop, 64, 0))); } valuep->value.str = const_cast(t_outDynamicStr.c_str()); return; @@ -2676,18 +2830,26 @@ void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) { const int chars = (varBits + 3) >> 2; t_outDynamicStr.resize(chars); for (size_t i = 0; i < chars; ++i) { - const char val = vl_vpi_get_word(vop, 4, i * 4); + const char val = get_word(vop, 4, i * 4); t_outDynamicStr[chars - i - 1] = "0123456789abcdef"[static_cast(val)]; } valuep->value.str = const_cast(t_outDynamicStr.c_str()); return; } else if (valuep->format == vpiStringVal) { if (varp->vltype() == VLVT_STRING) { + if (VL_UNLIKELY(varp->isForceable())) { + VL_VPI_ERROR_( + __FILE__, __LINE__, + "Attempting to retrieve value of forceable signal '%s' with data type " + "VLVT_STRING, but strings cannot be forced.", + vop->fullname()); + } + if (varp->isParam()) { valuep->value.str = reinterpret_cast(varDatap); return; } else { - t_outDynamicStr = *(vop->varStringDatap()); + t_outDynamicStr = *vop->varStringDatap(); valuep->value.str = const_cast(t_outDynamicStr.c_str()); return; } @@ -2695,7 +2857,7 @@ void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) { const int chars = VL_BYTES_I(varBits); t_outDynamicStr.resize(chars); for (size_t i = 0; i < chars; ++i) { - const char val = vl_vpi_get_word(vop, 8, i * 8); + const char val = get_word(vop, 8, i * 8); // other simulators replace [leading?] zero chars with spaces, replicate here. t_outDynamicStr[chars - i - 1] = val ? val : ' '; } @@ -2703,10 +2865,16 @@ void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) { return; } } else if (valuep->format == vpiIntVal) { - valuep->value.integer = vl_vpi_get_word(vop, 32, 0); + valuep->value.integer = get_word(vop, 32, 0); return; } else if (valuep->format == vpiRealVal) { - valuep->value.real = *(vop->varRealDatap()); + // Only cover the scalar case, since reals cannot be packed (IEEE 1800, section 7.4.1), and + // unpacked arrays are not supported for forcing in Verilator (#4735). + if (vop->varp()->isForceable() && *forceEnableSignalVop->varCDatap()) + valuep->value.real = *forceValueSignalVop->varRealDatap(); + else + valuep->value.real = *vop->varRealDatap(); + return; } else if (valuep->format == vpiSuppressVal) { return; @@ -2749,55 +2917,158 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_ return nullptr; } const PLI_INT32 delay_mode = flags & 0xfff; - if (const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object)) { - VL_DEBUG_IF_PLI( - VL_DBG_MSGF("- vpi: vpi_put_value name=%s fmt=%d vali=%d\n", vop->fullname(), - valuep->format, valuep->value.integer); - VL_DBG_MSGF("- vpi: varp=%p putatp=%p\n", vop->varp()->datap(), vop->varDatap());); + const PLI_INT32 forceFlag = flags & 0xfff; + if (const VerilatedVpioVar* const baseSignalVop = VerilatedVpioVar::castp(object)) { + VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_put_value name=%s fmt=%d vali=%d\n", + baseSignalVop->fullname(), valuep->format, + valuep->value.integer); + VL_DBG_MSGF("- vpi: varp=%p putatp=%p\n", + baseSignalVop->varp()->datap(), baseSignalVop->varDatap());); - if (VL_UNLIKELY(!vop->varp()->isPublicRW())) { + if (VL_UNLIKELY(!baseSignalVop->varp()->isPublicRW())) { VL_VPI_ERROR_(__FILE__, __LINE__, "vpi_put_value was used on signal marked read-only," " use public_flat_rw instead for %s : %s", - vop->fullname(), vop->scopep()->defname()); + baseSignalVop->fullname(), baseSignalVop->scopep()->defname()); return nullptr; } - if (!vl_check_format(vop->varp(), valuep, vop->fullname(), false)) return nullptr; + + // NOLINTNEXTLINE(readability-simplify-boolean-expr); + if (VL_UNLIKELY((forceFlag == vpiForceFlag || forceFlag == vpiReleaseFlag) + && !baseSignalVop->varp()->isForceable())) { + VL_VPI_ERROR_(__FILE__, __LINE__, + "vpi_put_value was used with %s on non-forceable signal '%s' : '%s'", + forceFlag == vpiForceFlag ? "vpiForceFlag" : "vpiReleaseFlag", + baseSignalVop->fullname(), baseSignalVop->scopep()->defname()); + return nullptr; + } + if (!vl_check_format(baseSignalVop->varp(), valuep, baseSignalVop->fullname(), false)) + return nullptr; if (delay_mode == vpiInertialDelay) { if (!VerilatedVpiPutHolder::canInertialDelay(valuep)) { VL_VPI_WARNING_( __FILE__, __LINE__, "%s: Unsupported p_vpi_value as requested for '%s' with vpiInertialDelay", - __func__, vop->fullname()); + __func__, baseSignalVop->fullname()); return nullptr; } - VerilatedVpiImp::inertialDelay(vop, valuep); + VerilatedVpiImp::inertialDelay(baseSignalVop, valuep); return object; } VerilatedVpiImp::evalNeeded(true); - const int varBits = vop->bitSize(); + const int varBits = baseSignalVop->bitSize(); + + const auto forceControlSignals + = baseSignalVop->varp()->isForceable() + ? VerilatedVpiImp::getForceControlSignals(baseSignalVop) + : std::pair{nullptr, nullptr}; + const vpiHandle& forceEnableSignalp = forceControlSignals.first; + const vpiHandle& forceValueSignalp = forceControlSignals.second; + const VerilatedVpioVar* const forceEnableSignalVop + = baseSignalVop->varp()->isForceable() ? VerilatedVpioVar::castp(forceEnableSignalp) + : nullptr; + const VerilatedVpioVar* const forceValueSignalVop + = baseSignalVop->varp()->isForceable() ? VerilatedVpioVar::castp(forceValueSignalp) + : nullptr; + t_vpi_error_info getForceControlSignalsError{}; + const bool errorOccurred = vpi_chk_error(&getForceControlSignalsError); + // LCOV_EXCL_START - Cannot test, since getForceControlSignals does not (currently) produce + // any notices or warnings. + if (errorOccurred && getForceControlSignalsError.level < vpiError) { + vpi_printf(getForceControlSignalsError.message); + VL_VPI_ERROR_RESET_(); + } // LCOV_EXCL_STOP + // NOLINTNEXTLINE(readability-simplify-boolean-expr); + if (VL_UNLIKELY(baseSignalVop->varp()->isForceable() + && (!forceEnableSignalp || !forceEnableSignalVop || !forceValueSignalp + || !forceValueSignalVop))) { + + // Check if getForceControlSignals provided any additional error info + t_vpi_error_info getForceControlSignalsError{}; + const bool gotErrorMessage = vpi_chk_error(&getForceControlSignalsError); + const std::string previousErrorMessage + = gotErrorMessage + ? std::string{" Error message: "} + getForceControlSignalsError.message + : ""; + + VL_VPI_ERROR_(__FILE__, __LINE__, + "%s: Signal '%s' with vpiHandle '%p' is marked forceable, but force " + "control signals could not be retrieved.%s", + __func__, baseSignalVop->fullname(), object, + gotErrorMessage ? previousErrorMessage.c_str() : ""); + return nullptr; + } + + const VerilatedVpioVar* const valueVop = (forceFlag == vpiForceFlag) + ? VerilatedVpioVar::castp(forceValueSignalp) + : baseSignalVop; + + if (forceFlag == vpiForceFlag) { + // Enable __VforceEn + VerilatedVpiImp::setAllBitsToValue(forceEnableSignalVop, 1); + } + if (forceFlag == vpiReleaseFlag) { + // If signal is continuously assigned, first clear the force enable bits, then get the + // (non-forced) value. Else, get the (still forced) value first, then clear the force + // enable bits. + + if (valueVop->varp()->isContinuously()) + VerilatedVpiImp::setAllBitsToValue(forceEnableSignalVop, 0); + + vl_vpi_get_value(baseSignalVop, valuep); + + t_vpi_error_info baseValueGetError{}; + const bool errorOccurred = vpi_chk_error(&baseValueGetError); + // LCOV_EXCL_START - Cannot test, because missing signal would already trigger error + // earlier, at the getForceControlSignals stage + // NOLINTNEXTLINE(readability-simplify-boolean-expr); + if (VL_UNLIKELY(errorOccurred && baseValueGetError.level >= vpiError)) { + const std::string baseValueSignalName = baseSignalVop->fullname(); + const std::string previousErrorMessage = baseValueGetError.message; + VL_VPI_ERROR_(__FILE__, __LINE__, + "%s: Could not retrieve value of signal '%s' with " + "vpiHandle '%p'. Error message: %s", + __func__, baseValueSignalName.c_str(), object, + previousErrorMessage.c_str()); + return nullptr; + } + // NOLINTNEXTLINE(readability-simplify-boolean-expr); + if (VL_UNCOVERABLE(errorOccurred && baseValueGetError.level < vpiError)) { + vpi_printf(baseValueGetError.message); + VL_VPI_ERROR_RESET_(); + } // LCOV_EXCL_STOP + + if (!valueVop->varp()->isContinuously()) + VerilatedVpiImp::setAllBitsToValue(forceEnableSignalVop, 0); + + // TODO: According to the SystemVerilog specification, + // vpi_put_value should return a handle to the scheduled event + // if the vpiReturnEvent flag is selected, NULL otherwise. + return object; + } + if (valuep->format == vpiVectorVal) { if (VL_UNLIKELY(!valuep->value.vector)) return nullptr; - if (vop->varp()->vltype() == VLVT_WDATA) { + if (valueVop->varp()->vltype() == VLVT_WDATA) { const int words = VL_WORDS_I(varBits); for (int i = 0; i < words; ++i) - vl_vpi_put_word(vop, valuep->value.vector[i].aval, 32, i * 32); + vl_vpi_put_word(valueVop, valuep->value.vector[i].aval, 32, i * 32); return object; - } else if (vop->varp()->vltype() == VLVT_UINT64 && varBits > 32) { + } else if (valueVop->varp()->vltype() == VLVT_UINT64 && varBits > 32) { const QData val = (static_cast(valuep->value.vector[1].aval) << 32) | static_cast(valuep->value.vector[0].aval); - vl_vpi_put_word(vop, val, 64, 0); + vl_vpi_put_word(valueVop, val, 64, 0); return object; } else { - vl_vpi_put_word(vop, valuep->value.vector[0].aval, 32, 0); + vl_vpi_put_word(valueVop, valuep->value.vector[0].aval, 32, 0); return object; } } else if (valuep->format == vpiBinStrVal) { const int len = std::strlen(valuep->value.str); - CData* const datap = reinterpret_cast(vop->varDatap()); + CData* const datap = reinterpret_cast(valueVop->varDatap()); for (int i = 0; i < varBits; ++i) { const bool set = (i < len) && (valuep->value.str[len - i - 1] == '1'); - const size_t pos = vop->bitOffset() + i; + const size_t pos = valueVop->bitOffset() + i; if (set) datap[pos >> 3] |= 1 << (pos & 7); @@ -2814,10 +3085,10 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_ "%s: Non octal character '%c' in '%s' as value %s for %s", __func__, digit + '0', valuep->value.str, VerilatedVpiError::strFromVpiVal(valuep->format), - vop->fullname()); + valueVop->fullname()); digit = 0; } - vl_vpi_put_word(vop, digit, 3, i * 3); + vl_vpi_put_word(valueVop, digit, 3, i * 3); } return object; } else if (valuep->format == vpiDecStrVal) { @@ -2828,16 +3099,17 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_ if (success < 1) { VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Parsing failed for '%s' as value %s for %s", __func__, valuep->value.str, - VerilatedVpiError::strFromVpiVal(valuep->format), vop->fullname()); + VerilatedVpiError::strFromVpiVal(valuep->format), + valueVop->fullname()); return nullptr; } if (success > 1) { - VL_VPI_WARNING_(__FILE__, __LINE__, - "%s: Trailing garbage '%s' in '%s' as value %s for %s", __func__, - remainder, valuep->value.str, - VerilatedVpiError::strFromVpiVal(valuep->format), vop->fullname()); + VL_VPI_WARNING_( + __FILE__, __LINE__, "%s: Trailing garbage '%s' in '%s' as value %s for %s", + __func__, remainder, valuep->value.str, + VerilatedVpiError::strFromVpiVal(valuep->format), valueVop->fullname()); } - vl_vpi_put_word(vop, val, 64, 0); + vl_vpi_put_word(valueVop, val, 64, 0); return object; } else if (valuep->format == vpiHexStrVal) { const int chars = (varBits + 3) >> 2; @@ -2861,19 +3133,20 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_ "%s: Non hex character '%c' in '%s' as value %s for %s", __func__, digit, valuep->value.str, VerilatedVpiError::strFromVpiVal(valuep->format), - vop->fullname()); + valueVop->fullname()); hex = 0; } } else { hex = 0; } // assign hex digit value to destination - vl_vpi_put_word(vop, hex, 4, i * 4); + vl_vpi_put_word(valueVop, hex, 4, i * 4); } return object; } else if (valuep->format == vpiStringVal) { - if (vop->varp()->vltype() == VLVT_STRING) { - *(vop->varStringDatap()) = valuep->value.str; + if (valueVop->varp()->vltype() == VLVT_STRING) { + // Does not use valueVop, because strings are not forceable anyway + *(baseSignalVop->varStringDatap()) = valuep->value.str; return object; } else { const int chars = VL_BYTES_I(varBits); @@ -2881,21 +3154,22 @@ vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_ for (int i = 0; i < chars; ++i) { // prepend with 0 values before placing string the least significant bytes const char c = (i < len) ? valuep->value.str[len - i - 1] : 0; - vl_vpi_put_word(vop, c, 8, i * 8); + vl_vpi_put_word(valueVop, c, 8, i * 8); } } return object; } else if (valuep->format == vpiIntVal) { - vl_vpi_put_word(vop, valuep->value.integer, 64, 0); + vl_vpi_put_word(valueVop, valuep->value.integer, 64, 0); return object; } else if (valuep->format == vpiRealVal) { - if (vop->varp()->vltype() == VLVT_REAL) { - *(vop->varRealDatap()) = valuep->value.real; + if (valueVop->varp()->vltype() == VLVT_REAL) { + *(valueVop->varRealDatap()) = valuep->value.real; return object; } } VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported format (%s) as requested for %s", - __func__, VerilatedVpiError::strFromVpiVal(valuep->format), vop->fullname()); + __func__, VerilatedVpiError::strFromVpiVal(valuep->format), + valueVop->fullname()); return nullptr; } else if (const VerilatedVpioParam* const vop = VerilatedVpioParam::castp(object)) { VL_VPI_WARNING_(__FILE__, __LINE__, "%s: Ignoring vpi_put_value to vpiParameter: %s", diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 58b30787d..5e69a50cf 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -630,9 +630,8 @@ string AstVar::vlEnumDir() const { } else if (isSigUserRdPublic()) { out += "|VLVF_PUB_RD"; } - if (isForceable()) { - out += "|VLVF_FORCEABLE"; - } + if (isForceable()) out += "|VLVF_FORCEABLE"; + if (isContinuously()) out += "|VLVF_CONTINUOUSLY"; // if (const AstBasicDType* const bdtypep = basicp()) { if (bdtypep->keyword().isDpiCLayout()) out += "|VLVF_DPI_CLAY"; diff --git a/src/V3Force.cpp b/src/V3Force.cpp index 7adb42d15..700c38325 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -457,6 +457,13 @@ class ForceConvertVisitor final : public VNVisitor { void visit(AstVarScope* nodep) override { // If this signal is marked externally forceable, create the public force signals if (nodep->varp()->isForceable()) { + if (VN_IS(nodep->varp()->dtypeSkipRefp(), UnpackArrayDType)) { + nodep->varp()->v3warn( + E_UNSUPPORTED, + "Unsupported: Forcing unpacked arrays: " << nodep->varp()->name()); // (#4735) + return; + } + const ForceState::ForceComponentsVarScope& fc = m_state.getForceComponents(nodep); fc.m_enVscp->varp()->sigUserRWPublic(true); fc.m_valVscp->varp()->sigUserRWPublic(true); diff --git a/test_regress/t/t_forceable_unpacked_bad.out b/test_regress/t/t_forceable_unpacked_bad.out new file mode 100644 index 000000000..cdbd0181e --- /dev/null +++ b/test_regress/t/t_forceable_unpacked_bad.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_forceable_unpacked_bad.v:8:9: Unsupported: Forcing unpacked arrays: t__DOT__unpacked + 8 | logic unpacked[1] /*verilator forceable*/; + | ^~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_forceable_unpacked_bad.py b/test_regress/t/t_forceable_unpacked_bad.py new file mode 100755 index 000000000..e30916148 --- /dev/null +++ b/test_regress/t/t_forceable_unpacked_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_forceable_unpacked_bad.v b/test_regress/t/t_forceable_unpacked_bad.v new file mode 100644 index 000000000..e8c3be432 --- /dev/null +++ b/test_regress/t/t_forceable_unpacked_bad.v @@ -0,0 +1,9 @@ +// ====================================================================== +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 +// ====================================================================== + +module t; + logic unpacked[1] /*verilator forceable*/; +endmodule diff --git a/test_regress/t/t_vpi_force.cpp b/test_regress/t/t_vpi_force.cpp new file mode 100644 index 000000000..a20ba2d23 --- /dev/null +++ b/test_regress/t/t_vpi_force.cpp @@ -0,0 +1,907 @@ +// ====================================================================== +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 +// ====================================================================== + +// DESCRIPTION: vpi force and release test +// +// This test checks that forcing a signal using vpi_put_value with vpiForceFlag +// sets it to the correct value, and then releasing it with vpiReleaseFlag +// returns it to the initial state. + +#include "verilated.h" // For VL_PRINTF +#include "verilated_sym_props.h" // For VerilatedVar +#include "verilated_syms.h" // For VerilatedVarNameMap +#include "verilated_vpi.h" // For VerilatedVpi::doInertialPuts(); + +#include "TestSimulator.h" // For is_verilator() +#include "TestVpi.h" // For CHECK_RESULT_NZ +#include "vpi_user.h" + +#include +#include // For std::unique_ptr + +namespace { + +constexpr int maxAllowedErrorLevel = vpiWarning; +const std::string scopeName = "t.test"; + +using signalValueTypes = union { + const char* str; + PLI_INT32 integer; + double real; + const struct t_vpi_vecval* vector; +}; + +using TestSignal = const struct { + const char* signalName; + PLI_INT32 valueType; + signalValueTypes releaseValue; + signalValueTypes forceValue; + std::pair + partialForceValue; // No std::optional on C++14, so the bool inside the pair is used to + // specify if a partial force should be tested for this signal. For a + // partial force, the first part of the signal is left at the release + // value, while the second part is forced to the force value. +}; + +constexpr std::array TestSignals = { + TestSignal{"onebit", + vpiIntVal, + {.integer = 1}, + {.integer = 0}, + {{}, false}}, // Can't partially force just one bit + TestSignal{"intval", + vpiIntVal, + {.integer = -1431655766}, // 1010...1010 + {.integer = 0x55555555}, // 0101...0101 + {{.integer = -1431677611}, true}}, // 1010...010_010...0101 + + TestSignal{"vectorC", + vpiVectorVal, + // NOLINTBEGIN (cppcoreguidelines-avoid-c-arrays) + {.vector = (t_vpi_vecval[]){{0b10101010, 0}}}, + {.vector = (t_vpi_vecval[]){{0b01010101, 0}}}, + {{.vector = (t_vpi_vecval[]){{0b10100101, 0}}}, true}}, + // NOLINTEND (cppcoreguidelines-avoid-c-arrays) + + TestSignal{ + "vectorQ", + vpiVectorVal, + // NOTE: This is a 62 bit signal, so the first two bits of the MSBs (*second* vecval, + // since the LSBs come first) are set to 0, hence the 0x2 and 0x1, respectively. + + // NOLINTBEGIN (cppcoreguidelines-avoid-c-arrays) + {.vector = (t_vpi_vecval[]){{0xAAAAAAAAUL, 0}, {0x2AAAAAAAUL, 0}}}, // (00)1010...1010 + {.vector = (t_vpi_vecval[]){{0x55555555UL, 0}, {0x15555555UL, 0}}}, // (00)0101...0101 + {{.vector = (t_vpi_vecval[]){{0xD5555555UL, 0}, {0x2AAAAAAAUL, 0}}}, + true}}, // 1010...010_010...0101 + // NOLINTEND (cppcoreguidelines-avoid-c-arrays) + + TestSignal{"vectorW", + vpiVectorVal, + // NOLINTBEGIN (cppcoreguidelines-avoid-c-arrays) + {.vector = (t_vpi_vecval[]){{0xAAAAAAAAUL, 0}, // 1010...1010 + {0xAAAAAAAAUL, 0}, + {0xAAAAAAAAUL, 0}, + {0xAAAAAAAAUL, 0}}}, + {.vector = (t_vpi_vecval[]){{0x55555555UL, 0}, // 0101...0101 + {0x55555555UL, 0}, + {0x55555555UL, 0}, + {0x55555555UL, 0}}}, + {{.vector = (t_vpi_vecval[]){{0x55555555UL, 0}, // 1010...010_010...0101 + {0x55555555UL, 0}, + {0xAAAAAAAAUL, 0}, + {0xAAAAAAAAUL, 0}}}, + true}}, + // NOLINTEND (cppcoreguidelines-avoid-c-arrays) + + TestSignal{"real1", + vpiRealVal, + {.real = 1.0}, + {.real = 123456.789}, + {{}, false}}, // reals cannot be packed and individual bits cannot be accessed, so + // there is no way to partially force a real signal. + + TestSignal{"textHalf", vpiStringVal, {.str = "Hf"}, {.str = "T2"}, {{.str = "H2"}, true}}, + TestSignal{"textLong", + vpiStringVal, + {.str = "Long64b"}, + {.str = "44Four44"}, + {{.str = "Lonur44"}, true}}, + TestSignal{"text", + vpiStringVal, + {.str = "Verilog Test module"}, + {.str = "lorem ipsum"}, + {{.str = "Verilog Tesem ipsum"}, true}}, + + TestSignal{"binString", + vpiBinStrVal, + {.str = "10101010"}, + {.str = "01010101"}, + {{.str = "10100101"}, true}}, + TestSignal{"octString", + vpiOctStrVal, + {.str = "25252"}, // 010101010101010 + {.str = "52525"}, // 101010101010101 + {{.str = "25325"}, true}}, // 010101011010101 + TestSignal{"hexString", + vpiHexStrVal, + {.str = "aaaaaaaaaaaaaaaa"}, // 1010...1010 + {.str = "5555555555555555"}, // 0101...0101 + {{.str = "aaaaaaaa55555555"}, true}}, // 1010...010_010...0101 + + TestSignal{"decStringC", + vpiDecStrVal, + {.str = "170"}, // 10101010 + {.str = "85"}, // 01010101 + {{.str = "165"}, true}}, // 10100101 + TestSignal{"decStringS", + vpiDecStrVal, + {.str = "43690"}, // 1010...1010 + {.str = "21845"}, // 0101...0101 + {{.str = "43605"}, true}}, // 1010...010_010...0101 + TestSignal{"decStringI", + vpiDecStrVal, + {.str = "2863311530"}, // 1010...1010 + {.str = "1431655765"}, // 0101...0101 + {{.str = "2863289685"}, true}}, // 1010...010_010...0101 + TestSignal{"decStringQ", + vpiDecStrVal, + {.str = "12297829382473034410"}, // 1010...1010 + {.str = "6148914691236517205"}, // 0101...0101 + {{.str = "12297829381041378645"}, true}}, // 1010...010_010...0101 +}; + +bool vpiCheckErrorLevel(const int maxAllowedErrorLevel) { + t_vpi_error_info errorInfo{}; + const bool errorOccured = vpi_chk_error(&errorInfo); + if (VL_UNLIKELY(errorOccured)) { + VL_PRINTF("%s", errorInfo.message); + return errorInfo.level > maxAllowedErrorLevel; + } + return false; +} + +std::pair vpiGetErrorMessage() { + t_vpi_error_info errorInfo{}; + const bool errorOccured = vpi_chk_error(&errorInfo); + return {errorOccured ? errorInfo.message : std::string{}, errorOccured}; +} + +#ifdef VERILATOR // m_varsp is Verilator-specific and does not make sense for other simulators +std::unique_ptr removeSignalFromScope(const std::string& scopeName, + const std::string& signalName) { + const VerilatedScope* const scopep = Verilated::threadContextp()->scopeFind(scopeName.c_str()); + if (!scopep) return nullptr; + VerilatedVarNameMap* const varsp = scopep->varsp(); + const VerilatedVarNameMap::const_iterator foundSignalIt = varsp->find(signalName.c_str()); + if (foundSignalIt == varsp->end()) return nullptr; + VerilatedVar foundSignal = foundSignalIt->second; + varsp->erase(foundSignalIt); + return std::make_unique(foundSignal); +} + +bool insertSignalIntoScope(const std::pair& scopeAndSignalNames, + const std::unique_ptr signal) { + const std::string& scopeName = scopeAndSignalNames.first; + const std::string& signalName = scopeAndSignalNames.second; + + const VerilatedScope* const scopep = Verilated::threadContextp()->scopeFind(scopeName.c_str()); + if (!scopep) return false; + VerilatedVarNameMap* const varsp = scopep->varsp(); + + // NOTE: The lifetime of the name inserted into varsp must be the same as the scopep, i.e. the + // same as threadContextp. Otherwise, the key in the m_varsp map will be a stale pointer. + // Hence, names of signals being inserted are stored in the static set, and it is assumed that + // the set's lifetime is the same as the threadContextp. + static std::set insertedSignalNames; + const auto insertedSignalName = insertedSignalNames.insert(signalName); + + varsp->insert( + std::pair{insertedSignalName.first->c_str(), *signal}); + return true; +} + +int tryVpiGetWithMissingSignal(const TestVpiHandle& signalToGet, // NOLINT(misc-misplaced-const) + const PLI_INT32 signalFormat, + const std::pair& scopeAndSignalNames, + const std::string& expectedErrorMessage) { + const std::string& scopeName = scopeAndSignalNames.first; + const std::string& signalNameToRemove = scopeAndSignalNames.second; + std::unique_ptr removedSignal + = removeSignalFromScope(scopeName, signalNameToRemove); + CHECK_RESULT_NZ(removedSignal); // NOLINT(concurrency-mt-unsafe) + + s_vpi_value value_s{.format = signalFormat, .value = {}}; + + // Prevent program from terminating, so error message can be collected + Verilated::fatalOnVpiError(false); + vpi_get_value(signalToGet, &value_s); + // Re-enable so tests that should pass properly terminate the simulation on failure + Verilated::fatalOnVpiError(true); + + std::pair receivedError = vpiGetErrorMessage(); + const bool errorOccurred = receivedError.second; + const std::string receivedErrorMessage = receivedError.first; + CHECK_RESULT_NZ(errorOccurred); // NOLINT(concurrency-mt-unsafe) + + // NOLINTNEXTLINE(concurrency-mt-unsafe,performance-avoid-endl) + CHECK_RESULT(receivedErrorMessage, expectedErrorMessage); + bool insertSuccess + = insertSignalIntoScope({scopeName, signalNameToRemove}, std::move(removedSignal)); + CHECK_RESULT_NZ(insertSuccess); // NOLINT(concurrency-mt-unsafe) + return 0; +} + +int tryVpiPutWithMissingSignal(const s_vpi_value value_s, + const TestVpiHandle& signalToPut, // NOLINT(misc-misplaced-const) + const int flag, const std::string& scopeName, + const std::string& signalNameToRemove, + const std::vector& expectedErrorMessageSubstrings) { + std::unique_ptr removedSignal + = removeSignalFromScope(scopeName, signalNameToRemove); + CHECK_RESULT_NZ(removedSignal); // NOLINT(concurrency-mt-unsafe) + + // Prevent program from terminating, so error message can be collected + Verilated::fatalOnVpiError(false); + vpi_put_value(signalToPut, const_cast(&value_s), nullptr, flag); + // Re-enable so tests that should pass properly terminate the simulation on failure + Verilated::fatalOnVpiError(true); + + std::pair receivedError = vpiGetErrorMessage(); + const bool errorOccurred = receivedError.second; + const std::string receivedErrorMessage = receivedError.first; + CHECK_RESULT_NZ(errorOccurred); // NOLINT(concurrency-mt-unsafe) + + const bool allExpectedErrorSubstringsFound + = std::all_of(expectedErrorMessageSubstrings.begin(), expectedErrorMessageSubstrings.end(), + [receivedErrorMessage](const std::string& expectedSubstring) { + return receivedErrorMessage.find(expectedSubstring) != std::string::npos; + }); + CHECK_RESULT_NZ(allExpectedErrorSubstringsFound); // NOLINT(concurrency-mt-unsafe) + bool insertSuccess + = insertSignalIntoScope({scopeName, signalNameToRemove}, std::move(removedSignal)); + CHECK_RESULT_NZ(insertSuccess); // NOLINT(concurrency-mt-unsafe) + return 0; +} + +// Simpler function that expects an exact string instead of a number of substrings, and just a +// signalName instead of a handle. +int expectVpiPutError(const std::string& signalName, s_vpi_value value_s, const int flag, + const std::string& expectedErrorMessage) { + const std::string fullSignalName = std::string{scopeName} + "." + signalName; + TestVpiHandle const signalHandle //NOLINT(misc-misplaced-const) + = vpi_handle_by_name(const_cast(fullSignalName.c_str()), nullptr); + CHECK_RESULT_NZ(signalHandle); // NOLINT(concurrency-mt-unsafe) + + // Prevent program from terminating, so error message can be collected + Verilated::fatalOnVpiError(false); + vpi_put_value(signalHandle, &value_s, nullptr, flag); + // Re-enable so tests that should pass properly terminate the simulation on failure + Verilated::fatalOnVpiError(true); + + std::pair receivedError = vpiGetErrorMessage(); + const bool errorOccurred = receivedError.second; + const std::string receivedErrorMessage = receivedError.first; + CHECK_RESULT_NZ(errorOccurred); // NOLINT(concurrency-mt-unsafe) + + // NOLINTNEXTLINE(concurrency-mt-unsafe,performance-avoid-endl) + CHECK_RESULT(receivedErrorMessage, expectedErrorMessage); + return 0; +} + +#endif + +bool vpiValuesEqual(const std::size_t bitCount, const s_vpi_value& first, + const s_vpi_value& second) { + if (first.format != second.format) return false; + switch (first.format) { + case vpiIntVal: return first.value.integer == second.value.integer; break; + case vpiVectorVal: { + const t_vpi_vecval* const firstVecval = first.value.vector; + const t_vpi_vecval* const secondVecval = second.value.vector; + const std::size_t vectorElements = (bitCount + 31) / 32; // Ceil + for (std::size_t i{0}; i < vectorElements; ++i) { + if (firstVecval[i].aval != secondVecval[i].aval) return false; + } + return true; + } + case vpiRealVal: + return std::abs(first.value.real - second.value.real) + < std::numeric_limits::epsilon(); + break; + case vpiStringVal: + case vpiBinStrVal: + case vpiOctStrVal: + case vpiDecStrVal: + case vpiHexStrVal: { + // Same as CHECK_RESULT_CSTR_STRIP, but should return true when equal, false otherwise + const std::string firstUnpadded = first.value.str + std::strspn(first.value.str, " "); + return std::string{firstUnpadded} == std::string{second.value.str}; + break; + } + default: + VL_PRINTF("Unsupported value format %i passed to vpiValuesEqual\n", first.format); + return false; + } +} + +std::unique_ptr vpiValueWithFormat(const PLI_INT32 signalFormat, + const signalValueTypes value) { + std::unique_ptr value_sp = std::make_unique(); + value_sp->format = signalFormat; + + switch (signalFormat) { + case vpiIntVal: value_sp->value = {.integer = value.integer}; break; + case vpiVectorVal: value_sp->value = {.vector = const_cast(value.vector)}; break; + case vpiRealVal: value_sp->value = {.real = value.real}; break; + case vpiStringVal: + case vpiBinStrVal: + case vpiOctStrVal: + case vpiDecStrVal: + case vpiHexStrVal: value_sp->value = {.str = const_cast(value.str)}; break; + default: + VL_PRINTF("Unsupported value format %i passed to vpiValueWithFormat\n", signalFormat); + return nullptr; + } + + return value_sp; +} + +int checkValue(const std::string& scopeName, const std::string& testSignalName, + const PLI_INT32 signalFormat, const signalValueTypes expectedValue) { + const std::string testSignalFullName + = std::string{scopeName} + "." + std::string{testSignalName}; + TestVpiHandle const signalHandle //NOLINT(misc-misplaced-const) + = vpi_handle_by_name(const_cast(testSignalFullName.c_str()), nullptr); + CHECK_RESULT_NZ(signalHandle); // NOLINT(concurrency-mt-unsafe) + +#ifdef VERILATOR + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(tryVpiGetWithMissingSignal( + signalHandle, signalFormat, {scopeName, testSignalName + "__VforceEn"}, + "vl_vpi_get_value: Signal '" + testSignalFullName + + "' is marked forceable, but force control signals could not be retrieved. Error " + "message: getForceControlSignals: vpi force or release requested for '" + + testSignalFullName + "', but vpiHandle '(nil)' of enable signal '" + + testSignalFullName + + "__VforceEn' could not be cast to VerilatedVpioVar*. Ensure signal is marked as " + "forceable")); + + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(tryVpiGetWithMissingSignal( + signalHandle, signalFormat, {scopeName, testSignalName + "__VforceVal"}, + "vl_vpi_get_value: Signal '" + testSignalFullName + + "' is marked forceable, but force control signals could not be retrieved. Error " + "message: getForceControlSignals: vpi force or release requested for '" + + testSignalFullName + "', but vpiHandle '(nil)' of value signal '" + + testSignalFullName + + "__VforceVal' could not be cast to VerilatedVpioVar*. Ensure signal is marked " + "as " + "forceable")); +#endif + + std::unique_ptr receivedValueSp = vpiValueWithFormat(signalFormat, {}); + CHECK_RESULT_NZ(receivedValueSp); // NOLINT(concurrency-mt-unsafe) + vpi_get_value(signalHandle, receivedValueSp.get()); + + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(vpiCheckErrorLevel(maxAllowedErrorLevel)) + + const std::unique_ptr expectedValueSp + = vpiValueWithFormat(signalFormat, expectedValue); + CHECK_RESULT_NZ(expectedValueSp); // NOLINT(concurrency-mt-unsafe) + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_NZ( + vpiValuesEqual(vpi_get(vpiSize, signalHandle), *receivedValueSp, *expectedValueSp)); + + return 0; +} + +int forceSignal(const std::string& scopeName, const std::string& testSignalName, + const PLI_INT32 signalFormat, const signalValueTypes forceValue) { + const std::string testSignalFullName + = std::string{scopeName} + "." + std::string{testSignalName}; + TestVpiHandle const signalHandle //NOLINT(misc-misplaced-const) + = vpi_handle_by_name(const_cast(testSignalFullName.c_str()), nullptr); + CHECK_RESULT_NZ(signalHandle); // NOLINT(concurrency-mt-unsafe) + + std::unique_ptr value_sp = vpiValueWithFormat(signalFormat, forceValue); + CHECK_RESULT_NZ(value_sp); // NOLINT(concurrency-mt-unsafe) + +#ifdef VERILATOR + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(tryVpiPutWithMissingSignal( + *value_sp, signalHandle, vpiForceFlag, scopeName, testSignalName + "__VforceEn", + {"vpi_put_value: Signal '" + testSignalFullName + "' with vpiHandle ", + // Exact handle address does not matter + " is marked forceable, but force control signals could not be retrieved. Error " + "message: getForceControlSignals: vpi force or release requested for '" + + testSignalFullName + "', but vpiHandle '(nil)' of enable signal '" + + testSignalFullName + + "__VforceEn' could not be cast to VerilatedVpioVar*. Ensure signal is marked " + "as " + "forceable"})); + + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(tryVpiPutWithMissingSignal( + *value_sp, signalHandle, vpiForceFlag, scopeName, testSignalName + "__VforceVal", + {"vpi_put_value: Signal '" + testSignalFullName + "' with vpiHandle ", + // Exact handle address does not matter + " is marked forceable, but force control signals could not be retrieved. Error " + "message: getForceControlSignals: vpi force or release requested for '" + + testSignalFullName + "', but vpiHandle '(nil)' of value signal '" + + testSignalFullName + + "__VforceVal' could not be cast to VerilatedVpioVar*. Ensure signal is marked " + "as " + "forceable"})); +#endif + + vpi_put_value(signalHandle, value_sp.get(), nullptr, vpiForceFlag); + + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(vpiCheckErrorLevel(maxAllowedErrorLevel)) + + return 0; +} + +int releaseSignal(const std::string& scopeName, const std::string& testSignalName, + const PLI_INT32 signalFormat, + const std::pair releaseValue) { + const signalValueTypes expectedReleaseValueInit = releaseValue.first; + const signalValueTypes expectedReleaseValue = releaseValue.second; + const std::string testSignalFullName + = std::string{scopeName} + "." + std::string{testSignalName}; + TestVpiHandle const signalHandle //NOLINT(misc-misplaced-const) + = vpi_handle_by_name(const_cast(testSignalFullName.c_str()), nullptr); + CHECK_RESULT_NZ(signalHandle); // NOLINT(concurrency-mt-unsafe) + + // initialize value_sp to the value that is *not* expected (i.e. forceValue for continuously + // assigned signals, and releaseValue for clocked signals) to ensure the test fails if value_sp + // is not updated + std::unique_ptr value_sp + = vpiValueWithFormat(signalFormat, expectedReleaseValueInit); + CHECK_RESULT_NZ(value_sp); //NOLINT(concurrency-mt-unsafe) + +#ifdef VERILATOR + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(tryVpiPutWithMissingSignal( + *value_sp, signalHandle, vpiReleaseFlag, scopeName, testSignalName + "__VforceEn", + {"vpi_put_value: Signal '" + testSignalFullName + "' with vpiHandle ", + // Exact handle address does not matter + " is marked forceable, but force control signals could not be retrieved. Error " + "message: getForceControlSignals: vpi force or release requested for '" + + testSignalFullName + "', but vpiHandle '(nil)' of enable signal '" + + testSignalFullName + + "__VforceEn' could not be cast to VerilatedVpioVar*. Ensure signal is marked " + "as " + "forceable"})); + + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(tryVpiPutWithMissingSignal( + *value_sp, signalHandle, vpiReleaseFlag, scopeName, testSignalName + "__VforceVal", + {"vpi_put_value: Signal '" + testSignalFullName + "' with vpiHandle ", + // Exact handle address does not matter + " is marked forceable, but force control signals could not be retrieved. Error " + "message: getForceControlSignals: vpi force or release requested for '" + + testSignalFullName + "', but vpiHandle '(nil)' of value signal '" + + testSignalFullName + + "__VforceVal' could not be cast to VerilatedVpioVar*. Ensure signal is marked " + "as " + "forceable"})); +#endif + + vpi_put_value(signalHandle, value_sp.get(), nullptr, vpiReleaseFlag); + + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(vpiCheckErrorLevel(maxAllowedErrorLevel)) + +#ifdef XRUN + // Special case: real1Continuously is continuously assigned, but in xrun the value for the + // s_vpi_value output parameter upon releasing using vpi_put_value is *not* the releaseValue as + // expected, but the forceValue. This ifdef ensures the test still passes on xrun. + const std::unique_ptr expectedValueSp = vpiValueWithFormat( + signalFormat, testSignalName == "real1Continuously" ? signalValueTypes{.real = 123456.789} + : expectedReleaseValue); +#else + const std::unique_ptr expectedValueSp + = vpiValueWithFormat(signalFormat, expectedReleaseValue); +#endif + CHECK_RESULT_NZ(expectedValueSp); // NOLINT(concurrency-mt-unsafe) + + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_NZ(vpiValuesEqual(vpi_get(vpiSize, signalHandle), *value_sp, *expectedValueSp)); + + return 0; +} + +} // namespace + +extern "C" int checkValuesForced(void) { + // Clocked signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + checkValue(scopeName, signal.signalName, signal.valueType, signal.forceValue)); + return 0; + })); + + // Continuously assigned signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + const std::string continouslyAssignedSignal + = std::string{signal.signalName} + "Continuously"; + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + checkValue(scopeName, continouslyAssignedSignal, signal.valueType, + signal.forceValue)); + return 0; + })); + return 0; +} + +extern "C" int checkValuesPartiallyForced(void) { + // Clocked signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + if (signal.partialForceValue.second) + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + checkValue(scopeName, signal.signalName, signal.valueType, + signal.partialForceValue.first)); + return 0; + })); + + // Continuously assigned signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + if (signal.partialForceValue.second) + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + checkValue(scopeName, std::string{signal.signalName} + "Continuously", + signal.valueType, signal.partialForceValue.first)); + return 0; + })); + return 0; +} + +extern "C" int checkValuesReleased(void) { + // Clocked signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + checkValue(scopeName, signal.signalName, signal.valueType, signal.releaseValue)); + return 0; + })); + + // Continuously assigned signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + checkValue(scopeName, std::string{signal.signalName} + "Continuously", + signal.valueType, signal.releaseValue)); + return 0; + })); + return 0; +} + +#ifdef VERILATOR +// This function only makes sense with Verilator, because other simulators fail at elaboration time +// when trying to force a string. The error message check is specific to verilated_vpi.cpp. + +extern "C" int tryCheckingForceableString(void) { + const std::string forceableStringName = std::string{scopeName} + ".str1"; + TestVpiHandle const stringSignalHandle //NOLINT(misc-misplaced-const) + = vpi_handle_by_name(const_cast(forceableStringName.c_str()), nullptr); + CHECK_RESULT_NZ(stringSignalHandle); // NOLINT(concurrency-mt-unsafe) + + s_vpi_value value_s{.format = vpiStringVal, .value = {}}; + + // Prevent program from terminating, so error message can be collected + Verilated::fatalOnVpiError(false); + vpi_get_value(stringSignalHandle, &value_s); + // Re-enable so tests that should pass properly terminate the simulation on failure + Verilated::fatalOnVpiError(true); + + std::pair receivedError = vpiGetErrorMessage(); + const bool errorOccurred = receivedError.second; + const std::string receivedErrorMessage = receivedError.first; + CHECK_RESULT_NZ(errorOccurred); // NOLINT(concurrency-mt-unsafe) + + const std::string expectedErrorMessage + = "Attempting to retrieve value of forceable signal '" + forceableStringName + + "' with data type VLVT_STRING, but strings cannot be forced."; + // NOLINTNEXTLINE(concurrency-mt-unsafe,performance-avoid-endl) + CHECK_RESULT(receivedErrorMessage, expectedErrorMessage); + return 0; +} + +// This function only makes sense with Verilator, because its purpose is testing error messages +// emitted from verilated_vpi. +extern "C" int tryInvalidPutOperations() { + CHECK_RESULT_Z(expectVpiPutError( // NOLINT(concurrency-mt-unsafe) + "str2", {.format = vpiStringVal, .value = {.str = const_cast("foo")}}, + vpiForceFlag, + "vpi_put_value was used with vpiForceFlag on non-forceable signal 't.test.str2' : " + "'Test'")); + + CHECK_RESULT_Z(expectVpiPutError( // NOLINT(concurrency-mt-unsafe) + "octString", {.format = vpiOctStrVal, .value = {.str = const_cast("123A")}}, + vpiForceFlag, + "vpi_put_value: Non octal character 'A' in '123A' as value " + "vpiOctStrVal for t.test.octString__VforceVal")); + + CHECK_RESULT_Z(expectVpiPutError( // NOLINT(concurrency-mt-unsafe) + "decStringC", {.format = vpiDecStrVal, .value = {.str = const_cast("A123")}}, + vpiForceFlag, + "vpi_put_value: Parsing failed for 'A123' as value vpiDecStrVal for " + "t.test.decStringC__VforceVal")); + + CHECK_RESULT_Z(expectVpiPutError( // NOLINT(concurrency-mt-unsafe) + "decStringC", {.format = vpiDecStrVal, .value = {.str = const_cast("123A")}}, + vpiForceFlag, + "vpi_put_value: Trailing garbage 'A' in '123A' as value vpiDecStrVal for " + "t.test.decStringC__VforceVal")); + + CHECK_RESULT_Z(expectVpiPutError( // NOLINT(concurrency-mt-unsafe) + "hexString", {.format = vpiHexStrVal, .value = {.str = const_cast("12AG")}}, + vpiForceFlag, + "vpi_put_value: Non hex character 'G' in '12AG' as value vpiHexStrVal for " + "t.test.hexString__VforceVal")); + + // vop was replaced with baseSignalVop in vpi_put_value, so these tests are required to hit the + // test coverage target and ensure the error messages still work. + CHECK_RESULT_Z(expectVpiPutError( // NOLINT(concurrency-mt-unsafe) + "onebit", {.format = vpiRawFourStateVal, .value = {}}, vpiForceFlag, + "vl_check_format: Unsupported format (vpiRawFourStateVal) for t.test.onebit")); + + CHECK_RESULT_Z(expectVpiPutError( // NOLINT(concurrency-mt-unsafe) + "onebit", {.format = vpiSuppressVal, .value = {}}, vpiForceFlag, + "vpi_put_value: Unsupported format (vpiSuppressVal) as " + "requested for t.test.onebit__VforceVal")); + + CHECK_RESULT_Z(expectVpiPutError( // NOLINT(concurrency-mt-unsafe) + "onebit", {.format = vpiStringVal, .value = {}}, vpiInertialDelay, + "vpi_put_value: Unsupported p_vpi_value as requested for 't.test.onebit' with " + "vpiInertialDelay")); + + return 0; +} + +// This function is just needed for hitting the test coverage target for verilated_vpi.cpp and +// ensuring that vpi_put_value to a string without vpiForceFlag still works. +extern "C" int putString() { + const std::string stringName = std::string{scopeName} + ".str2"; + TestVpiHandle const stringSignalHandle //NOLINT(misc-misplaced-const) + = vpi_handle_by_name(const_cast(stringName.c_str()), nullptr); + CHECK_RESULT_NZ(stringSignalHandle); // NOLINT(concurrency-mt-unsafe) + + s_vpi_value value_s{.format = vpiStringVal, .value = {.str = const_cast("foo")}}; + + vpi_put_value(stringSignalHandle, &value_s, nullptr, vpiNoDelay); + + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(vpiCheckErrorLevel(maxAllowedErrorLevel)) + + value_s.value.str + = const_cast("bar"); // Ensure that test fails if value_s is not updated + + vpi_get_value(stringSignalHandle, &value_s); + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(vpiCheckErrorLevel(maxAllowedErrorLevel)) + + s_vpi_value expectedValueS{.format = vpiStringVal, + .value = {.str = const_cast("foo")}}; + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_NZ(vpiValuesEqual(vpi_get(vpiSize, stringSignalHandle), value_s, expectedValueS)); + + return 0; +} + +// This function is just needed for hitting the test coverage target for verilated_vpi.cpp and +// ensuring that vpiInertialDelay still works after renaming vop to baseSignalVop. +extern "C" int putInertialDelay() { + const std::string fullSignalName = std::string{scopeName} + ".delayed"; + TestVpiHandle const signalHandle //NOLINT(misc-misplaced-const) + = vpi_handle_by_name(const_cast(fullSignalName.c_str()), nullptr); + CHECK_RESULT_NZ(signalHandle); // NOLINT(concurrency-mt-unsafe) + + constexpr int delayedValue = 123; + s_vpi_value value_s{.format = vpiIntVal, .value = {.integer = delayedValue}}; + s_vpi_time time{.type = vpiSimTime, .high = 0, .low = 0, .real = {}}; + vpi_put_value(signalHandle, &value_s, &time, vpiInertialDelay); + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(vpiCheckErrorLevel(maxAllowedErrorLevel)) + + // Check that the put had no immediate effect + + vpi_get_value(signalHandle, &value_s); + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(vpiCheckErrorLevel(maxAllowedErrorLevel)) + + s_vpi_value expectedValueS{.format = vpiIntVal, .value = {.integer = 0}}; + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_NZ(vpiValuesEqual(vpi_get(vpiSize, signalHandle), value_s, expectedValueS)); + + // Check that the put is executed upon doInertialPuts + VerilatedVpi::doInertialPuts(); + + value_s.value.integer = 0; // Ensure that test fails if value_s is not updated + vpi_get_value(signalHandle, &value_s); + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_Z(vpiCheckErrorLevel(maxAllowedErrorLevel)) + + expectedValueS.value.integer = delayedValue; + // NOLINTNEXTLINE(concurrency-mt-unsafe) + CHECK_RESULT_NZ(vpiValuesEqual(vpi_get(vpiSize, signalHandle), value_s, expectedValueS)); + + return 0; +} +#endif + +extern "C" int forceValues(void) { + if (!TestSimulator::is_verilator()) { +#ifdef VERILATOR + printf("TestSimulator indicating not verilator, but VERILATOR macro is defined\n"); + return 1; +#endif + } + + // Clocked signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + forceSignal(scopeName, signal.signalName, signal.valueType, signal.forceValue)); + return 0; + })); + + // Continuously assigned signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + forceSignal(scopeName, std::string{signal.signalName} + "Continuously", + signal.valueType, signal.forceValue)); + return 0; + })); + return 0; +} + +extern "C" int releaseValues(void) { + // Clocked signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + releaseSignal(scopeName, signal.signalName, signal.valueType, + {signal.releaseValue, signal.forceValue})); + return 0; + })); + + // Continuously assigned signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + releaseSignal(scopeName, std::string{signal.signalName} + "Continuously", + signal.valueType, {signal.forceValue, signal.releaseValue})); + return 0; + })); + return 0; +} + +extern "C" int releasePartiallyForcedValues(void) { + // Skip any values that cannot be partially forced. Can't just reuse releaseValues, because the + // output argument s_vpi_value of vpi_put_value with vpiReleaseFlag differs depending on + // whether or not a signal was forced before, and not all signals are forced in the partial + // forcing test. + + // Clocked signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + if (signal.partialForceValue.second) + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + releaseSignal(scopeName, signal.signalName, signal.valueType, + {signal.releaseValue, signal.partialForceValue.first})); + return 0; + })); + + // Continuously assigned signals + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + std::any_of(TestSignals.begin(), TestSignals.end(), [](const TestSignal& signal) { + if (signal.partialForceValue.second) + CHECK_RESULT_Z( // NOLINT(concurrency-mt-unsafe) + releaseSignal(scopeName, std::string{signal.signalName} + "Continuously", + signal.valueType, + {signal.partialForceValue.first, signal.releaseValue})); + return 0; + })); + return 0; +} + +#ifdef IS_VPI + +static int checkValuesForcedVpi() { + TestVpiHandle href = vpi_handle(vpiSysTfCall, 0); + s_vpi_value vpi_value; + + vpi_value.format = vpiIntVal; + vpi_value.value.integer = checkValuesForced(); + vpi_put_value(href, &vpi_value, NULL, vpiNoDelay); + + return 0; +} + +static int checkValuesPartiallyForcedVpi() { + TestVpiHandle href = vpi_handle(vpiSysTfCall, 0); + s_vpi_value vpi_value; + + vpi_value.format = vpiIntVal; + vpi_value.value.integer = checkValuesPartiallyForced(); + vpi_put_value(href, &vpi_value, NULL, vpiNoDelay); + + return 0; +} + +static int checkValuesReleasedVpi() { + TestVpiHandle href = vpi_handle(vpiSysTfCall, 0); + s_vpi_value vpi_value; + + vpi_value.format = vpiIntVal; + vpi_value.value.integer = checkValuesReleased(); + vpi_put_value(href, &vpi_value, NULL, vpiNoDelay); + + return 0; +} + +static int forceValuesVpi() { + TestVpiHandle href = vpi_handle(vpiSysTfCall, 0); + s_vpi_value vpi_value; + + vpi_value.format = vpiIntVal; + vpi_value.value.integer = forceValues(); + vpi_put_value(href, &vpi_value, NULL, vpiNoDelay); + + return 0; +} + +static int releaseValuesVpi() { + TestVpiHandle href = vpi_handle(vpiSysTfCall, 0); + s_vpi_value vpiValue; + + vpiValue.format = vpiIntVal; + vpiValue.value.integer = releaseValues(); + vpi_put_value(href, &vpiValue, NULL, vpiNoDelay); + + return 0; +} + +static int releasePartiallyForcedValuesVpi() { + TestVpiHandle href = vpi_handle(vpiSysTfCall, 0); + s_vpi_value vpiValue; + + vpiValue.format = vpiIntVal; + vpiValue.value.integer = releasePartiallyForcedValues(); + vpi_put_value(href, &vpiValue, NULL, vpiNoDelay); + + return 0; +} + +std::array vpi_systf_data + = {s_vpi_systf_data{vpiSysFunc, vpiIntFunc, (PLI_BYTE8*)"$forceValues", + (PLI_INT32(*)(PLI_BYTE8*))forceValuesVpi, 0, 0, 0}, + s_vpi_systf_data{vpiSysFunc, vpiIntFunc, (PLI_BYTE8*)"$releaseValues", + (PLI_INT32(*)(PLI_BYTE8*))releaseValuesVpi, 0, 0, 0}, + s_vpi_systf_data{vpiSysFunc, vpiIntFunc, (PLI_BYTE8*)"$releasePartiallyForcedValues", + (PLI_INT32(*)(PLI_BYTE8*))releasePartiallyForcedValuesVpi, 0, 0, 0}, + s_vpi_systf_data{vpiSysFunc, vpiIntFunc, (PLI_BYTE8*)"$checkValuesForced", + (PLI_INT32(*)(PLI_BYTE8*))checkValuesForcedVpi, 0, 0, 0}, + s_vpi_systf_data{vpiSysFunc, vpiIntFunc, (PLI_BYTE8*)"$checkValuesPartiallyForced", + (PLI_INT32(*)(PLI_BYTE8*))checkValuesPartiallyForcedVpi, 0, 0, 0}, + s_vpi_systf_data{vpiSysFunc, vpiIntFunc, (PLI_BYTE8*)"$checkValuesReleased", + (PLI_INT32(*)(PLI_BYTE8*))checkValuesReleasedVpi, 0, 0, 0}}; + +// cver entry +extern "C" void vpi_compat_bootstrap(void) { + for (s_vpi_systf_data& systf : vpi_systf_data) vpi_register_systf(&systf); +} + +// icarus entry +void (*vlog_startup_routines[])() = {vpi_compat_bootstrap, 0}; +#endif diff --git a/test_regress/t/t_vpi_force.py b/test_regress/t/t_vpi_force.py new file mode 100755 index 000000000..10cfa8959 --- /dev/null +++ b/test_regress/t/t_vpi_force.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(make_top_shell=False, + make_main=False, + make_pli=True, + verilator_flags2=["--binary --vpi", test.pli_filename], + v_flags2=["+define+USE_VPI_NOT_DPI +define+VERILATOR_COMMENTS"]) + +test.execute(xrun_flags2=["+define+USE_VPI_NOT_DPI"], use_libvpi=True, check_finished=True) + +test.passes() diff --git a/test_regress/t/t_vpi_force.v b/test_regress/t/t_vpi_force.v new file mode 100644 index 000000000..14b37d73a --- /dev/null +++ b/test_regress/t/t_vpi_force.v @@ -0,0 +1,732 @@ +// ====================================================================== +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 +// ====================================================================== + +`define STRINGIFY(x) `"x`" + +`ifdef VERILATOR_COMMENTS +`define PUBLIC_FORCEABLE /*verilator public_flat_rw*/ /*verilator forceable*/ +`else +`define PUBLIC_FORCEABLE +`endif + +module t; + + reg clk; + + initial begin + clk = 0; + forever #1 clk = ~clk; + end + + Test test (.clk(clk)); + + +endmodule + +module Test ( + input clk +); + +`ifdef IVERILOG +`elsif USE_VPI_NOT_DPI +`ifdef VERILATOR +`systemc_header + extern "C" int tryCheckingForceableString(); + extern "C" int putString(); + extern "C" int tryInvalidPutOperations(); + extern "C" int putInertialDelay(); + extern "C" int forceValues(); + extern "C" int releaseValues(); + extern "C" int releasePartiallyForcedValues(); + extern "C" int checkValuesForced(); + extern "C" int checkValuesPartiallyForced(); + extern "C" int checkValuesReleased(); +`verilog +`endif +`else +`ifdef VERILATOR + import "DPI-C" context function int tryCheckingForceableString(); + import "DPI-C" context function int putString(); + import "DPI-C" context function int tryInvalidPutOperations(); + import "DPI-C" context function int putInertialDelay(); +`endif + import "DPI-C" context function int forceValues(); + import "DPI-C" context function int releaseValues(); + import "DPI-C" context function int releasePartiallyForcedValues(); + import "DPI-C" context function int checkValuesPartiallyForced(); + import "DPI-C" context function int checkValuesForced(); + import "DPI-C" context function int checkValuesReleased(); +`endif + + // Non-forceable signal should raise error + string str1 `PUBLIC_FORCEABLE; // std::string + + // Verify that vpi_put_value still works for strings + string str2 /*verilator public_flat_rw*/; // std::string + + // Verify that vpi_put_value still works with vpiInertialDelay + logic [ 31:0] delayed `PUBLIC_FORCEABLE; // IData + + // Clocked signals + + // Force with vpiIntVal + logic onebit `PUBLIC_FORCEABLE; // CData + logic [ 31:0] intval `PUBLIC_FORCEABLE; // IData + + // Force with vpiVectorVal + logic [ 7:0] vectorC `PUBLIC_FORCEABLE; // CData + logic [ 61:0] vectorQ `PUBLIC_FORCEABLE; // QData + logic [127:0] vectorW `PUBLIC_FORCEABLE; // VlWide + + // Force with vpiRealVal + real real1 `PUBLIC_FORCEABLE; // double + + // Force with vpiStringVal + logic [ 15:0] textHalf `PUBLIC_FORCEABLE; // SData + logic [ 63:0] textLong `PUBLIC_FORCEABLE; // QData + logic [511:0] text `PUBLIC_FORCEABLE; // VlWide + + // Force with vpiBinStrVal, vpiOctStrVal, vpiHexStrVal + logic [ 7:0] binString `PUBLIC_FORCEABLE; // CData + logic [ 14:0] octString `PUBLIC_FORCEABLE; // SData + logic [ 63:0] hexString `PUBLIC_FORCEABLE; // QData + + // Force with vpiDecStrVal + logic [ 7:0] decStringC `PUBLIC_FORCEABLE; // CData + logic [ 15:0] decStringS `PUBLIC_FORCEABLE; // SData + logic [ 31:0] decStringI `PUBLIC_FORCEABLE; // IData + logic [ 63:0] decStringQ `PUBLIC_FORCEABLE; // QData + + // Continuously assigned signals: + + // Force with vpiIntVal + wire onebitContinuously `PUBLIC_FORCEABLE; // CData + wire [ 31:0] intvalContinuously `PUBLIC_FORCEABLE; // IData + + // Force with vpiVectorVal + wire [ 7:0] vectorCContinuously `PUBLIC_FORCEABLE; // CData + wire [ 61:0] vectorQContinuously `PUBLIC_FORCEABLE; // QData + wire [127:0] vectorWContinuously `PUBLIC_FORCEABLE; // VlWide + + // Force with vpiRealVal + `ifdef IVERILOG + // Need wreal with Icarus for forcing continuously assigned real + wreal real1Continuously `PUBLIC_FORCEABLE; // double + `else + real real1Continuously `PUBLIC_FORCEABLE; // double + `endif + + // Force with vpiStringVal + wire [ 15:0] textHalfContinuously `PUBLIC_FORCEABLE; // SData + wire [ 63:0] textLongContinuously `PUBLIC_FORCEABLE; // QData + wire [511:0] textContinuously `PUBLIC_FORCEABLE; // VlWide + + // Force with vpiBinStrVal, vpiOctStrVal, vpiHexStrVal + wire [ 7:0] binStringContinuously `PUBLIC_FORCEABLE; // CData + wire [ 14:0] octStringContinuously `PUBLIC_FORCEABLE; // SData + wire [ 63:0] hexStringContinuously `PUBLIC_FORCEABLE; // QData + + // Force with vpiDecStrVal + wire [ 7:0] decStringCContinuously `PUBLIC_FORCEABLE; // CData + wire [ 15:0] decStringSContinuously `PUBLIC_FORCEABLE; // SData + wire [ 31:0] decStringIContinuously `PUBLIC_FORCEABLE; // IData + wire [ 63:0] decStringQContinuously `PUBLIC_FORCEABLE; // QData + + always @(posedge clk) begin + onebit <= 1; + intval <= 32'hAAAAAAAA; + + vectorC <= 8'hAA; + vectorQ <= 62'h2AAAAAAA_AAAAAAAA; + vectorW <= 128'hAAAAAAAA_AAAAAAAA_AAAAAAAA_AAAAAAAA; + + real1 <= 1.0; + + textHalf <= "Hf"; + textLong <= "Long64b"; + text <= "Verilog Test module"; + + binString <= 8'b10101010; + octString <= 15'o25252; // 0b1010... + hexString <= 64'hAAAAAAAAAAAAAAAA; // 0b1010... + + decStringC <= 8'hAA; + decStringS <= 16'hAAAA; + decStringI <= 32'hAAAAAAAA; + decStringQ <= 64'd12297829382473034410; // 0b1010... + end + + assign onebitContinuously = 1; + assign intvalContinuously = 32'hAAAAAAAA; + + assign vectorCContinuously = 8'hAA; + assign vectorQContinuously = 62'h2AAAAAAA_AAAAAAAA; + assign vectorWContinuously = 128'hAAAAAAAA_AAAAAAAA_AAAAAAAA_AAAAAAAA; + + assign real1Continuously = 1.0; + + assign textHalfContinuously = "Hf"; + assign textLongContinuously = "Long64b"; + assign textContinuously = "Verilog Test module"; + + assign binStringContinuously = 8'b10101010; + assign octStringContinuously = 15'o25252; // 0b1010... + assign hexStringContinuously = 64'hAAAAAAAAAAAAAAAA; // 0b1010... + + assign decStringCContinuously = 8'hAA; + assign decStringSContinuously = 16'hAAAA; + assign decStringIContinuously = 32'hAAAAAAAA; + assign decStringQContinuously = 64'd12297829382473034410; // 0b1010... + + task automatic svForceValues(); + force onebit = 0; + force intval = 32'h55555555; + force vectorC = 8'h55; + force vectorQ = 62'h15555555_55555555; + force vectorW = 128'h55555555_55555555_55555555_55555555; + force real1 = 123456.789; + force textHalf = "T2"; + force textLong = "44Four44"; + force text = "lorem ipsum"; + force binString = 8'b01010101; + force octString = 15'o52525; + force hexString = 64'h5555555555555555; + force decStringC = 8'h55; + force decStringS = 16'h5555; + force decStringI = 32'h55555555; + force decStringQ = 64'd6148914691236517205; + + force onebitContinuously = 0; + force intvalContinuously = 32'h55555555; + force vectorCContinuously = 8'h55; + force vectorQContinuously = 62'h15555555_55555555; + force vectorWContinuously = 128'h55555555_55555555_55555555_55555555; + force real1Continuously = 123456.789; + force textHalfContinuously = "T2"; + force textLongContinuously = "44Four44"; + force textContinuously = "lorem ipsum"; + force binStringContinuously = 8'b01010101; + force octStringContinuously = 15'o52525; + force hexStringContinuously = 64'h5555555555555555; + force decStringCContinuously = 8'h55; + force decStringSContinuously = 16'h5555; + force decStringIContinuously = 32'h55555555; + force decStringQContinuously = 64'd6148914691236517205; + endtask + + task automatic svPartiallyForceValues(); + force intval[15:0] = 16'h5555; + + force vectorC[3:0] = 4'h5; + force vectorQ[30:0] = 31'h55555555; + force vectorW[63:0] = 64'h55555555_55555555; + + force textHalf[7:0] = "2"; + force textLong[31:0] = "ur44"; + force text[63:0] = "em ipsum"; + force binString[3:0] = 4'b0101; + + force octString[6:0] = 7'o125; + force hexString[31:0] = 32'h55555555; + + force decStringC[3:0] = 4'h5; + force decStringS[7:0] = 8'h55; + force decStringI[15:0] = 16'h5555; + force decStringQ[31:0] = 32'd1431655765; + + force intvalContinuously[15:0] = 16'h5555; + + force vectorCContinuously[3:0] = 4'h5; + force vectorQContinuously[30:0] = 31'h55555555; + force vectorWContinuously[63:0] = 64'h55555555_55555555; + + force textHalfContinuously[7:0] = "2"; + force textLongContinuously[31:0] = "ur44"; + force textContinuously[63:0] = "em ipsum"; + force binStringContinuously[3:0] = 4'b0101; + + force octStringContinuously[6:0] = 7'o125; + force hexStringContinuously[31:0] = 32'h55555555; + + force decStringCContinuously[3:0] = 4'h5; + force decStringSContinuously[7:0] = 8'h55; + force decStringIContinuously[15:0] = 16'h5555; + force decStringQContinuously[31:0] = 32'd1431655765; + endtask + + task automatic vpiTryCheckingForceableString(); + integer vpiStatus = 1; // Default to failed status to ensure that a function *not* getting + // called also causes simulation termination +`ifdef VERILATOR +`ifdef USE_VPI_NOT_DPI + vpiStatus = $c32("tryCheckingForceableString()"); +`else + vpiStatus = tryCheckingForceableString(); +`endif +`else + $stop; // This task only makes sense with Verilator, since other simulators ignore the "verilator forceable" metacomment. +`endif + + if (vpiStatus != 0) begin + $write("%%Error: t_vpi_force.cpp:%0d:", vpiStatus); + $display( + "C Test failed (forcing string either succeeded even though it should have failed, or produced unexpected error message)"); + $stop; + end + endtask + + task automatic vpiPutString(); + integer vpiStatus = 1; // Default to failed status to ensure that a function *not* getting + // called also causes simulation termination +`ifdef VERILATOR +`ifdef USE_VPI_NOT_DPI + vpiStatus = $c32("putString()"); +`else + vpiStatus = putString(); +`endif +`else + $stop; // This task only makes sense with Verilator, since other simulators ignore the "verilator forceable" metacomment. +`endif + + if (vpiStatus != 0) begin + $write("%%Error: t_vpi_force.cpp:%0d:", vpiStatus); + $display( + "C Test failed (vpi_put_value failed for string)"); + $stop; + end + endtask + + task automatic vpiTryInvalidPutOperations(); + integer vpiStatus = 1; // Default to failed status to ensure that a function *not* getting + // called also causes simulation termination +`ifdef VERILATOR +`ifdef USE_VPI_NOT_DPI + vpiStatus = $c32("tryInvalidPutOperations()"); +`else + vpiStatus = tryInvalidPutOperations(); +`endif +`else + $stop; // This task only makes sense with Verilator, since it tests verilated_vpi.cpp +`endif + + if (vpiStatus != 0) begin + $write("%%Error: t_vpi_force.cpp:%0d:", vpiStatus); + $display( + "C Test failed (invalid vpi_put_value operation either succeeded, even though it should have failed, or produced an unexpected error message.)"); + $stop; + end + endtask + + task automatic vpiPutInertialDelay(); + integer vpiStatus = 1; // Default to failed status to ensure that a function *not* getting + // called also causes simulation termination +`ifdef VERILATOR +`ifdef USE_VPI_NOT_DPI + vpiStatus = $c32("putInertialDelay()"); +`else + vpiStatus = putInertialDelay(); +`endif +`else + $stop; // This task only makes sense with Verilator, since it tests verilated_vpi.cpp +`endif + + if (vpiStatus != 0) begin + $write("%%Error: t_vpi_force.cpp:%0d:", vpiStatus); + $display( + "C Test failed (vpi_put_value with vpiInertialDelay failed)"); + $stop; + end + endtask + + task automatic vpiForceValues(); + integer vpiStatus = 1; +`ifdef VERILATOR +`ifdef USE_VPI_NOT_DPI + vpiStatus = $c32("forceValues()"); +`else + vpiStatus = forceValues(); +`endif +`elsif IVERILOG + vpiStatus = $forceValues; +`elsif USE_VPI_NOT_DPI + vpiStatus = $forceValues; +`else + vpiStatus = forceValues(); +`endif + + if (vpiStatus != 0) begin + $write("%%Error: t_vpi_force.cpp:%0d:", vpiStatus); + $display("C Test failed (could not force value)"); + $stop; + end + endtask + + task automatic svReleaseValues(); + release onebit; + release intval; + release vectorC; + release vectorQ; + release vectorW; + release real1; + release textHalf; + release textLong; + release text; + release binString; + release octString; + release hexString; + release decStringC; + release decStringS; + release decStringI; + release decStringQ; + + release onebitContinuously; + release intvalContinuously; + release vectorCContinuously; + release vectorQContinuously; + release vectorWContinuously; + release real1Continuously; + release textHalfContinuously; + release textLongContinuously; + release textContinuously; + release binStringContinuously; + release octStringContinuously; + release hexStringContinuously; + release decStringCContinuously; + release decStringSContinuously; + release decStringIContinuously; + release decStringQContinuously; + endtask + + task automatic vpiReleaseValues(); + integer vpiStatus = 1; +`ifdef VERILATOR +`ifdef USE_VPI_NOT_DPI + vpiStatus = $c32("releaseValues()"); +`else + vpiStatus = releaseValues(); +`endif +`elsif IVERILOG + vpiStatus = $releaseValues; +`elsif USE_VPI_NOT_DPI + vpiStatus = $releaseValues; +`else + vpiStatus = releaseValues(); +`endif + + if (vpiStatus != 0) begin + $write("%%Error: t_vpi_force.cpp:%0d:", vpiStatus); + $display("C Test failed (could not release value)"); + $stop; + end + endtask + + task automatic vpiReleasePartiallyForcedValues(); + integer vpiStatus = 1; +`ifdef VERILATOR +`ifdef USE_VPI_NOT_DPI + vpiStatus = $c32("releasePartiallyForcedValues()"); +`else + vpiStatus = releasePartiallyForcedValues(); +`endif +`elsif IVERILOG + vpiStatus = $releasePartiallyForcedValues; +`elsif USE_VPI_NOT_DPI + vpiStatus = $releasePartiallyForcedValues; +`else + vpiStatus = releasePartiallyForcedValues(); +`endif + + if (vpiStatus != 0) begin + $write("%%Error: t_vpi_force.cpp:%0d:", vpiStatus); + $display("C Test failed (could not release value)"); + $stop; + end + endtask + + task automatic svCheckValuesForced(); + if(onebit != 0) $stop; + if(intval != 32'h55555555) $stop; + if(vectorC != 8'h55) $stop; + if(vectorQ != 62'h15555555_55555555) $stop; + if(vectorW != 128'h55555555_55555555_55555555_55555555) $stop; + if(real1 != 123456.789) $stop; + if(textHalf != "T2") $stop; + if(textLong != "44Four44") $stop; + if(text != "lorem ipsum") $stop; + if(binString != 8'b01010101) $stop; + if(octString != 15'o52525) $stop; + if(hexString != 64'h5555555555555555) $stop; + if(decStringC != 8'h55) $stop; + if(decStringS != 16'h5555) $stop; + if(decStringI != 32'h55555555) $stop; + if(decStringQ != 64'd6148914691236517205) $stop; + + if(onebitContinuously != 0) $stop; + if(intvalContinuously != 32'h55555555) $stop; + if(vectorCContinuously != 8'h55) $stop; + if(vectorQContinuously != 62'h15555555_55555555) $stop; + if(vectorWContinuously != 128'h55555555_55555555_55555555_55555555) $stop; + if(real1Continuously != 123456.789) $stop; + if(textHalfContinuously != "T2") $stop; + if(textLongContinuously != "44Four44") $stop; + if(textContinuously != "lorem ipsum") $stop; + if(binStringContinuously != 8'b01010101) $stop; + if(octStringContinuously != 15'o52525) $stop; + if(hexStringContinuously != 64'h5555555555555555) $stop; + if(decStringCContinuously != 8'h55) $stop; + if(decStringSContinuously != 16'h5555) $stop; + if(decStringIContinuously != 32'h55555555) $stop; + if(decStringQContinuously != 64'd6148914691236517205) $stop; + endtask + + task automatic vpiCheckValuesForced(); + integer vpiStatus = 1; +`ifdef VERILATOR +`ifdef USE_VPI_NOT_DPI + vpiStatus = $c32("checkValuesForced()"); +`else + vpiStatus = checkValuesForced(); +`endif +`elsif IVERILOG + vpiStatus = $checkValuesForced; +`elsif USE_VPI_NOT_DPI + vpiStatus = $checkValuesForced; +`else + vpiStatus = checkValuesForced(); +`endif + + if (vpiStatus != 0) begin + $write("%%Error: t_vpi_force.cpp:%0d:", vpiStatus); + $display("C Test failed (value after forcing does not match expectation)"); + $stop; + end + endtask + + task automatic svCheckValuesPartiallyForced(); + if (intval != 32'hAAAA_5555) $stop; + if (vectorC != 8'h A5) $stop; + if (vectorQ != 62'h2AAAAAAAD5555555) $stop; + if (vectorW != 128'hAAAAAAAA_AAAAAAAA_55555555_55555555) $stop; + if (textHalf != "H2") $stop; + if (textLong != "Lonur44") $stop; + if (text != "Verilog Tesem ipsum") $stop; + if (binString != 8'b1010_0101) $stop; + if (octString != 15'b01010101_1010101) $stop; + if (hexString != 64'hAAAAAAAA_55555555) $stop; + if (decStringC != 8'hA5) $stop; + if (decStringS != 16'hAA55) $stop; + if (decStringI != 32'hAAAA_5555) $stop; + if (decStringQ != 64'hAAAAAAAA_55555555) $stop; + + if (intvalContinuously != 32'hAAAA_5555) $stop; + if (vectorCContinuously != 8'h A5) $stop; + if (vectorQContinuously != 62'h2AAAAAAAD5555555) $stop; + if (vectorWContinuously != 128'hAAAAAAAA_AAAAAAAA_55555555_55555555) $stop; + if (textHalfContinuously != "H2") $stop; + if (textLongContinuously != "Lonur44") $stop; + if (textContinuously != "Verilog Tesem ipsum") $stop; + if (binStringContinuously != 8'b1010_0101) $stop; + if (octStringContinuously != 15'b01010101_1010101) $stop; + if (hexStringContinuously != 64'hAAAAAAAA_55555555) $stop; + if (decStringCContinuously != 8'hA5) $stop; + if (decStringSContinuously != 16'hAA55) $stop; + if (decStringIContinuously != 32'hAAAA_5555) $stop; + if (decStringQContinuously != 64'hAAAAAAAA_55555555) $stop; + endtask + + task automatic vpiCheckValuesPartiallyForced(); + integer vpiStatus = 1; +`ifdef VERILATOR +`ifdef USE_VPI_NOT_DPI + vpiStatus = $c32("checkValuesPartiallyForced()"); +`else + vpiStatus = checkValuesPartiallyForced(); +`endif +`elsif IVERILOG + vpiStatus = $checkValuesPartiallyForced; +`elsif USE_VPI_NOT_DPI + vpiStatus = $checkValuesPartiallyForced; +`else + vpiStatus = checkValuesPartiallyForced(); +`endif + + if (vpiStatus != 0) begin + $write("%%Error: t_vpi_force.cpp:%0d:", vpiStatus); + $display("C Test failed (value after partial forcing does not match expectation)"); + $stop; + end + endtask + + task automatic svCheckValuesReleased(); + if (onebit != 1) $stop; + if (intval != 32'hAAAAAAAA) $stop; + if (vectorC != 8'hAA) $stop; + if (vectorQ != 62'h2AAAAAAA_AAAAAAAA) $stop; + if (vectorW != 128'hAAAAAAAA_AAAAAAAA_AAAAAAAA_AAAAAAAA) $stop; + if (real1 != 1.0) $stop; + if (textHalf != "Hf") $stop; + if (textLong != "Long64b") $stop; + if (text != "Verilog Test module") $stop; + if (binString != 8'b10101010) $stop; + if (octString != 15'o25252) $stop; + if (hexString != 64'hAAAAAAAAAAAAAAAA) $stop; + if (decStringC != 8'hAA) $stop; + if (decStringS != 16'hAAAA) $stop; + if (decStringI != 32'hAAAAAAAA) $stop; + if (decStringQ != 64'd12297829382473034410) $stop; + + if (onebitContinuously != 1) $stop; + if (intvalContinuously != 32'hAAAAAAAA) $stop; + if (vectorCContinuously != 8'hAA) $stop; + if (vectorQContinuously != 62'h2AAAAAAA_AAAAAAAA) $stop; + if (vectorWContinuously != 128'hAAAAAAAA_AAAAAAAA_AAAAAAAA_AAAAAAAA) $stop; + if (real1Continuously != 1.0) $stop; + if (textHalfContinuously != "Hf") $stop; + if (textLongContinuously != "Long64b") $stop; + if (textContinuously != "Verilog Test module") $stop; + if (binStringContinuously != 8'b10101010) $stop; + if (octStringContinuously != 15'o25252) $stop; + if (hexStringContinuously != 64'hAAAAAAAAAAAAAAAA) $stop; + if (decStringCContinuously != 8'hAA) $stop; + if (decStringSContinuously != 16'hAAAA) $stop; + if (decStringIContinuously != 32'hAAAAAAAA) $stop; + if (decStringQContinuously != 64'd12297829382473034410) $stop; + endtask + + task automatic vpiCheckValuesReleased(); + integer vpiStatus = 1; +`ifdef VERILATOR +`ifdef USE_VPI_NOT_DPI + vpiStatus = $c32("checkValuesReleased()"); +`else + vpiStatus = checkValuesReleased(); +`endif +`elsif IVERILOG + vpiStatus = $checkValuesReleased; +`elsif USE_VPI_NOT_DPI + vpiStatus = $checkValuesReleased; +`else + vpiStatus = checkValuesReleased(); +`endif + + if (vpiStatus != 0) begin + $write("%%Error: t_vpi_force.cpp:%0d:", vpiStatus); + $display("C Test failed (value after releasing does not match expectation)"); + $stop; + end + endtask + + initial begin +`ifdef WAVES +$dumpfile(`STRINGIFY(`TEST_DUMPFILE)); + $dumpvars(); +`endif + +`ifdef VERILATOR + vpiTryCheckingForceableString(); + vpiPutString(); + vpiTryInvalidPutOperations(); + vpiPutInertialDelay(); +`endif + + // Wait a bit before triggering the force to see a change in the traces + #4 vpiForceValues(); + + // Time delay to ensure setting and checking values does not happen + // at the same time, so that the signals can have their values overwritten + // by other processes + #4 vpiCheckValuesForced(); + svCheckValuesForced(); + #4 vpiReleaseValues(); + #4 vpiCheckValuesReleased(); + svCheckValuesReleased(); + + // Force through VPI, release through Verilog + #4 vpiForceValues(); + #4 vpiCheckValuesForced(); + svCheckValuesForced(); + #4 svReleaseValues(); + #4 vpiCheckValuesReleased(); + svCheckValuesReleased(); + + // Force through Verilog, release through VPI + #4 svForceValues(); + #4 vpiCheckValuesForced(); + svCheckValuesForced(); + #4 vpiReleaseValues(); + #4 vpiCheckValuesReleased(); + svCheckValuesReleased(); + + // Force only some bits, check if __VforceRd yields correct signal, + // release through VPI + #4 svPartiallyForceValues(); + #4 vpiCheckValuesPartiallyForced(); + svCheckValuesPartiallyForced(); + #4 vpiReleasePartiallyForcedValues(); + #4 vpiCheckValuesReleased(); + svCheckValuesReleased(); + + // Force only some bits, check if __VforceRd yields correct signal, + // release through Verilog + #4 svPartiallyForceValues(); + #4 vpiCheckValuesPartiallyForced(); + svCheckValuesPartiallyForced(); + #4 svReleaseValues(); + #4 vpiCheckValuesReleased(); + svCheckValuesReleased(); + + + #5 $display("*-* All Finished *-*"); + $finish; + end + +`ifdef TEST_VERBOSE + always @(posedge clk or negedge clk) begin + $display("time: %0t\tclk:%b", $time, clk); + + $display("str1: %s", str1); + $display("str2: %s", str2); + $display("delayed: %x", delayed); + + $display("onebit: %x", onebit); + $display("intval: %x", intval); + $display("vectorC: %x", vectorC); + $display("vectorQ: %x", vectorQ); + $display("vectorW: %x", vectorW); + $display("real1: %f", real1); + $display("textHalf: %s", textHalf); + $display("textLong: %s", textLong); + $display("text: %s", text); + $display("binString: %x", binString); + $display("octString: %x", octString); + $display("hexString: %x", hexString); + $display("decStringC: %x", decStringC); + $display("decStringS: %x", decStringS); + $display("decStringI: %x", decStringI); + $display("decStringQ: %x", decStringQ); + + $display("onebitContinuously: %x", onebitContinuously); + $display("intvalContinuously: %x", intvalContinuously); + $display("vectorCContinuously: %x", vectorCContinuously); + $display("vectorQContinuously: %x", vectorQContinuously); + $display("vectorWContinuously: %x", vectorWContinuously); + $display("real1Continuously: %f", real1Continuously); + $display("textHalfContinuously: %s", textHalfContinuously); + $display("textLongContinuously: %s", textLongContinuously); + $display("textContinuously: %s", textContinuously); + $display("binStringContinuously: %x", binStringContinuously); + $display("octStringContinuously: %x", octStringContinuously); + $display("hexStringContinuously: %x", hexStringContinuously); + $display("decStringCContinuously: %x", decStringCContinuously); + $display("decStringSContinuously: %x", decStringSContinuously); + $display("decStringIContinuously: %x", decStringIContinuously); + $display("decStringQContinuously: %x", decStringQContinuously); + + $display("========================\n"); + end +`endif + +endmodule diff --git a/test_regress/t/t_vpi_forceable_bad.cpp b/test_regress/t/t_vpi_forceable_bad.cpp new file mode 100644 index 000000000..72be078e7 --- /dev/null +++ b/test_regress/t/t_vpi_forceable_bad.cpp @@ -0,0 +1,65 @@ +// ====================================================================== +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 +// ====================================================================== + +// DESCRIPTION: Test failure of trying to force a non-forceable signal +// +// This test checks that attempting to force a signal that is not marked as +// forceable causes an error under Verilator, and does not cause an error in +// other simulators that do not need this metacomment to be able to force +// signals. + +#include "verilated.h" + +#include "TestSimulator.h" // For is_verilator() +#include "TestVpi.h" // For CHECK_RESULT_NZ +#include "vpi_user.h" + +extern "C" int forceValue(void) { + if (!TestSimulator::is_verilator()) { +#ifdef VERILATOR + printf("TestSimulator indicating not verilator, but VERILATOR macro is defined\n"); + return 1; +#endif + } + + PLI_BYTE8 testSignalName[] = "t.nonForceableSignal"; + vpiHandle signal = vpi_handle_by_name(testSignalName, nullptr); + CHECK_RESULT_NZ(signal); // NOLINT(concurrency-mt-unsafe) + + s_vpi_value value_s; + value_s.format = vpiIntVal; + value_s.value.integer = 0; + vpi_put_value(signal, &value_s, nullptr, vpiForceFlag); + // NOLINTNEXTLINE(concurrency-mt-unsafe); + CHECK_RESULT_Z(vpi_chk_error(nullptr)) + + return 0; +} + +#ifdef IS_VPI +static int force_value_vpi() { + TestVpiHandle href = vpi_handle(vpiSysTfCall, 0); + s_vpi_value vpi_value; + + vpi_value.format = vpiIntVal; + vpi_value.value.integer = forceValue(); + vpi_put_value(href, &vpi_value, NULL, vpiNoDelay); + + return 0; +} + +std::array vpi_systf_data + = {s_vpi_systf_data{vpiSysFunc, vpiIntFunc, (PLI_BYTE8*)"$forceValue", + (PLI_INT32(*)(PLI_BYTE8*))force_value_vpi, 0, 0, 0}}; + +// cver entry +extern "C" void vpi_compat_bootstrap(void) { + for (s_vpi_systf_data& systf : vpi_systf_data) vpi_register_systf(&systf); +} + +// icarus entry +void (*vlog_startup_routines[])() = {vpi_compat_bootstrap, 0}; +#endif diff --git a/test_regress/t/t_vpi_forceable_bad.out b/test_regress/t/t_vpi_forceable_bad.out new file mode 100644 index 000000000..79b3d52d6 --- /dev/null +++ b/test_regress/t/t_vpi_forceable_bad.out @@ -0,0 +1,2 @@ +%Error: .../verilated_vpi.cpp:2939: vpi_put_value was used with vpiForceFlag on non-forceable signal 't.nonForceableSignal' : 't' +Aborting... diff --git a/test_regress/t/t_vpi_forceable_bad.py b/test_regress/t/t_vpi_forceable_bad.py new file mode 100755 index 000000000..d68c13d6d --- /dev/null +++ b/test_regress/t/t_vpi_forceable_bad.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(make_top_shell=False, + make_main=False, + make_pli=True, + verilator_flags2=["--binary --vpi", test.pli_filename]) + +test.execute(use_libvpi=True, + fails=test.vlt_all, + expect_filename=test.golden_filename, + check_finished=test.iv) # or check_finished=test.xrun + +test.passes() diff --git a/test_regress/t/t_vpi_forceable_bad.v b/test_regress/t/t_vpi_forceable_bad.v new file mode 100644 index 000000000..b646eef1c --- /dev/null +++ b/test_regress/t/t_vpi_forceable_bad.v @@ -0,0 +1,51 @@ +// ====================================================================== +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 +// ====================================================================== + +module t; + +`ifdef IVERILOG +`elsif USE_VPI_NOT_DPI +`ifdef VERILATOR +`systemc_header + extern "C" int forceValue(); +`verilog +`endif +`else + import "DPI-C" context function int forceValue(); +`endif + + wire nonForceableSignal /*verilator public_flat_rw*/ = 1'b0; + integer vpiStatus = 1; + + initial begin + +`ifdef VERILATOR +`ifdef USE_VPI_NOT_DPI + vpiStatus = $c32("forceValue()"); +`else + vpiStatus = forceValue(); +`endif +`elsif IVERILOG + vpiStatus = $forceValue; +`elsif USE_VPI_NOT_DPI + vpiStatus = $forceValue; +`else + vpiStatus = forceValue(); +`endif + + if (vpiStatus != 0) begin + $write("%%Error: t_vpi_forceable_bad.cpp:%0d:", vpiStatus); + $display("C Test failed (could not force value)"); + $stop; + end + vpiStatus = 1; // Reset status to ensure that a function *not* getting + // called also causes failure + + $display("*-* All Finished *-*"); + $finish; + end + +endmodule From f39e56111e6a0efe8d9326310721eeac79259578 Mon Sep 17 00:00:00 2001 From: Christian Hecken Date: Tue, 16 Dec 2025 11:50:36 +0100 Subject: [PATCH 3/3] Add Verilation time check for forceable strings --- include/verilated_vpi.cpp | 8 ----- src/V3Force.cpp | 7 +++++ test_regress/t/t_forceable_string_bad.out | 5 ++++ test_regress/t/t_forceable_string_bad.py | 16 ++++++++++ test_regress/t/t_forceable_string_bad.v | 9 ++++++ test_regress/t/t_vpi_force.cpp | 36 ++--------------------- test_regress/t/t_vpi_force.v | 30 +------------------ test_regress/t/t_vpi_forceable_bad.out | 2 +- 8 files changed, 42 insertions(+), 71 deletions(-) create mode 100644 test_regress/t/t_forceable_string_bad.out create mode 100755 test_regress/t/t_forceable_string_bad.py create mode 100644 test_regress/t/t_forceable_string_bad.v diff --git a/include/verilated_vpi.cpp b/include/verilated_vpi.cpp index bacc82ff7..e1baa6f43 100644 --- a/include/verilated_vpi.cpp +++ b/include/verilated_vpi.cpp @@ -2837,14 +2837,6 @@ void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) { return; } else if (valuep->format == vpiStringVal) { if (varp->vltype() == VLVT_STRING) { - if (VL_UNLIKELY(varp->isForceable())) { - VL_VPI_ERROR_( - __FILE__, __LINE__, - "Attempting to retrieve value of forceable signal '%s' with data type " - "VLVT_STRING, but strings cannot be forced.", - vop->fullname()); - } - if (varp->isParam()) { valuep->value.str = reinterpret_cast(varDatap); return; diff --git a/src/V3Force.cpp b/src/V3Force.cpp index 700c38325..ca588fe17 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -464,6 +464,13 @@ class ForceConvertVisitor final : public VNVisitor { return; } + const AstBasicDType* const bdtypep = nodep->varp()->basicp(); + const bool strtype = bdtypep && bdtypep->keyword() == VBasicDTypeKwd::STRING; + if (strtype) { + nodep->varp()->v3error( + "Forcing strings is not permitted: " << nodep->varp()->name()); + } + const ForceState::ForceComponentsVarScope& fc = m_state.getForceComponents(nodep); fc.m_enVscp->varp()->sigUserRWPublic(true); fc.m_valVscp->varp()->sigUserRWPublic(true); diff --git a/test_regress/t/t_forceable_string_bad.out b/test_regress/t/t_forceable_string_bad.out new file mode 100644 index 000000000..6fb21ca01 --- /dev/null +++ b/test_regress/t/t_forceable_string_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_forceable_string_bad.v:8:10: Forcing strings is not permitted: t__DOT__str + 8 | string str /*verilator forceable*/; + | ^~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_forceable_string_bad.py b/test_regress/t/t_forceable_string_bad.py new file mode 100755 index 000000000..e30916148 --- /dev/null +++ b/test_regress/t/t_forceable_string_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_forceable_string_bad.v b/test_regress/t/t_forceable_string_bad.v new file mode 100644 index 000000000..668ccbfcc --- /dev/null +++ b/test_regress/t/t_forceable_string_bad.v @@ -0,0 +1,9 @@ +// ====================================================================== +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty. +// SPDX-License-Identifier: CC0-1.0 +// ====================================================================== + +module t; + string str /*verilator forceable*/; +endmodule diff --git a/test_regress/t/t_vpi_force.cpp b/test_regress/t/t_vpi_force.cpp index a20ba2d23..e24e33236 100644 --- a/test_regress/t/t_vpi_force.cpp +++ b/test_regress/t/t_vpi_force.cpp @@ -585,43 +585,13 @@ extern "C" int checkValuesReleased(void) { } #ifdef VERILATOR -// This function only makes sense with Verilator, because other simulators fail at elaboration time -// when trying to force a string. The error message check is specific to verilated_vpi.cpp. - -extern "C" int tryCheckingForceableString(void) { - const std::string forceableStringName = std::string{scopeName} + ".str1"; - TestVpiHandle const stringSignalHandle //NOLINT(misc-misplaced-const) - = vpi_handle_by_name(const_cast(forceableStringName.c_str()), nullptr); - CHECK_RESULT_NZ(stringSignalHandle); // NOLINT(concurrency-mt-unsafe) - - s_vpi_value value_s{.format = vpiStringVal, .value = {}}; - - // Prevent program from terminating, so error message can be collected - Verilated::fatalOnVpiError(false); - vpi_get_value(stringSignalHandle, &value_s); - // Re-enable so tests that should pass properly terminate the simulation on failure - Verilated::fatalOnVpiError(true); - - std::pair receivedError = vpiGetErrorMessage(); - const bool errorOccurred = receivedError.second; - const std::string receivedErrorMessage = receivedError.first; - CHECK_RESULT_NZ(errorOccurred); // NOLINT(concurrency-mt-unsafe) - - const std::string expectedErrorMessage - = "Attempting to retrieve value of forceable signal '" + forceableStringName - + "' with data type VLVT_STRING, but strings cannot be forced."; - // NOLINTNEXTLINE(concurrency-mt-unsafe,performance-avoid-endl) - CHECK_RESULT(receivedErrorMessage, expectedErrorMessage); - return 0; -} - // This function only makes sense with Verilator, because its purpose is testing error messages // emitted from verilated_vpi. extern "C" int tryInvalidPutOperations() { CHECK_RESULT_Z(expectVpiPutError( // NOLINT(concurrency-mt-unsafe) - "str2", {.format = vpiStringVal, .value = {.str = const_cast("foo")}}, + "str1", {.format = vpiStringVal, .value = {.str = const_cast("foo")}}, vpiForceFlag, - "vpi_put_value was used with vpiForceFlag on non-forceable signal 't.test.str2' : " + "vpi_put_value was used with vpiForceFlag on non-forceable signal 't.test.str1' : " "'Test'")); CHECK_RESULT_Z(expectVpiPutError( // NOLINT(concurrency-mt-unsafe) @@ -670,7 +640,7 @@ extern "C" int tryInvalidPutOperations() { // This function is just needed for hitting the test coverage target for verilated_vpi.cpp and // ensuring that vpi_put_value to a string without vpiForceFlag still works. extern "C" int putString() { - const std::string stringName = std::string{scopeName} + ".str2"; + const std::string stringName = std::string{scopeName} + ".str1"; TestVpiHandle const stringSignalHandle //NOLINT(misc-misplaced-const) = vpi_handle_by_name(const_cast(stringName.c_str()), nullptr); CHECK_RESULT_NZ(stringSignalHandle); // NOLINT(concurrency-mt-unsafe) diff --git a/test_regress/t/t_vpi_force.v b/test_regress/t/t_vpi_force.v index 14b37d73a..0b7f26f3c 100644 --- a/test_regress/t/t_vpi_force.v +++ b/test_regress/t/t_vpi_force.v @@ -34,7 +34,6 @@ module Test ( `elsif USE_VPI_NOT_DPI `ifdef VERILATOR `systemc_header - extern "C" int tryCheckingForceableString(); extern "C" int putString(); extern "C" int tryInvalidPutOperations(); extern "C" int putInertialDelay(); @@ -48,7 +47,6 @@ module Test ( `endif `else `ifdef VERILATOR - import "DPI-C" context function int tryCheckingForceableString(); import "DPI-C" context function int putString(); import "DPI-C" context function int tryInvalidPutOperations(); import "DPI-C" context function int putInertialDelay(); @@ -61,11 +59,8 @@ module Test ( import "DPI-C" context function int checkValuesReleased(); `endif - // Non-forceable signal should raise error - string str1 `PUBLIC_FORCEABLE; // std::string - // Verify that vpi_put_value still works for strings - string str2 /*verilator public_flat_rw*/; // std::string + string str1 /*verilator public_flat_rw*/; // std::string // Verify that vpi_put_value still works with vpiInertialDelay logic [ 31:0] delayed `PUBLIC_FORCEABLE; // IData @@ -257,27 +252,6 @@ module Test ( force decStringQContinuously[31:0] = 32'd1431655765; endtask - task automatic vpiTryCheckingForceableString(); - integer vpiStatus = 1; // Default to failed status to ensure that a function *not* getting - // called also causes simulation termination -`ifdef VERILATOR -`ifdef USE_VPI_NOT_DPI - vpiStatus = $c32("tryCheckingForceableString()"); -`else - vpiStatus = tryCheckingForceableString(); -`endif -`else - $stop; // This task only makes sense with Verilator, since other simulators ignore the "verilator forceable" metacomment. -`endif - - if (vpiStatus != 0) begin - $write("%%Error: t_vpi_force.cpp:%0d:", vpiStatus); - $display( - "C Test failed (forcing string either succeeded even though it should have failed, or produced unexpected error message)"); - $stop; - end - endtask - task automatic vpiPutString(); integer vpiStatus = 1; // Default to failed status to ensure that a function *not* getting // called also causes simulation termination @@ -626,7 +600,6 @@ $dumpfile(`STRINGIFY(`TEST_DUMPFILE)); `endif `ifdef VERILATOR - vpiTryCheckingForceableString(); vpiPutString(); vpiTryInvalidPutOperations(); vpiPutInertialDelay(); @@ -688,7 +661,6 @@ $dumpfile(`STRINGIFY(`TEST_DUMPFILE)); $display("time: %0t\tclk:%b", $time, clk); $display("str1: %s", str1); - $display("str2: %s", str2); $display("delayed: %x", delayed); $display("onebit: %x", onebit); diff --git a/test_regress/t/t_vpi_forceable_bad.out b/test_regress/t/t_vpi_forceable_bad.out index 79b3d52d6..5a6f3c927 100644 --- a/test_regress/t/t_vpi_forceable_bad.out +++ b/test_regress/t/t_vpi_forceable_bad.out @@ -1,2 +1,2 @@ -%Error: .../verilated_vpi.cpp:2939: vpi_put_value was used with vpiForceFlag on non-forceable signal 't.nonForceableSignal' : 't' +%Error: .../verilated_vpi.cpp:2931: vpi_put_value was used with vpiForceFlag on non-forceable signal 't.nonForceableSignal' : 't' Aborting...