// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
//
// Code available from: https://verilator.org
//
// Copyright 2009-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
//
//=========================================================================
///
/// \file
/// \brief Verilated VPI implementation code
///
/// This file must be compiled and linked against all Verilated objects
/// that use the VPI.
///
/// Use "verilator --vpi" to add this to the Makefile for the linker.
///
/// For documentation on the exported functions (named vpi_*) that are
/// implemented here, refer to the IEEE DPI chapter.
///
//=========================================================================

#define VERILATOR_VERILATED_VPI_CPP_

#include "verilated_vpi.h"

#include "verilated.h"
#include "verilated_imp.h"

#include "vltstd/vpi_user.h"

#include <cstdarg>
#include <cstdio>
#include <list>
#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>

//======================================================================
// Internal constants

#define VL_DEBUG_IF_PLI VL_DEBUG_IF
constexpr unsigned VL_VPI_LINE_SIZE_ = 8192;

//======================================================================
// Internal macros

#define VL_VPI_INTERNAL_ VerilatedVpiImp::error_info()->setMessage(vpiInternal)->setMessage
#define VL_VPI_SYSTEM_ VerilatedVpiImp::error_info()->setMessage(vpiSystem)->setMessage
#define VL_VPI_ERROR_ VerilatedVpiImp::error_info()->setMessage(vpiError)->setMessage
#define VL_VPI_WARNING_ VerilatedVpiImp::error_info()->setMessage(vpiWarning)->setMessage
#define VL_VPI_NOTICE_ VerilatedVpiImp::error_info()->setMessage(vpiNotice)->setMessage
#define VL_VPI_ERROR_RESET_ VerilatedVpiImp::error_info()->resetError

// Not supported yet
#define VL_VPI_UNIMP_() \
    (VL_VPI_ERROR_(__FILE__, __LINE__, Verilated::catName("Unsupported VPI function: ", __func__)))

//======================================================================
// Implementation

// Base VPI handled object
class VerilatedVpio VL_NOT_FINAL {
    // CONSTANTS
    // Magic value stored in front of object to detect double free etc
    // Must be odd, as aligned pointer can never be odd
    static constexpr uint32_t activeMagic() VL_PURE { return 0xfeed100f; }

    // MEM MANGLEMENT
    // Internal note: Globals may multi-construct, see verilated.cpp top.
    static thread_local uint8_t* t_freeHeadp;

public:
    // CONSTRUCTORS
    VerilatedVpio() = default;
    virtual ~VerilatedVpio() = default;
    static void* operator new(size_t size) VL_MT_SAFE {
        // We new and delete tons of vpi structures, so keep them around
        // To simplify our free list, we use a size large enough for all derived types
        // We reserve word zero for the next pointer, as that's safer in case a
        // dangling reference to the original remains around.
        static constexpr size_t CHUNK_SIZE = 256;
        if (VL_UNCOVERABLE(size > CHUNK_SIZE))
            VL_FATAL_MT(__FILE__, __LINE__, "", "increase CHUNK_SIZE");
        if (VL_LIKELY(t_freeHeadp)) {
            uint8_t* const newp = t_freeHeadp;
            t_freeHeadp = *(reinterpret_cast<uint8_t**>(newp));
            *(reinterpret_cast<uint32_t*>(newp)) = activeMagic();
            return newp + 8;
        }
        // +8: 8 bytes for next
        uint8_t* newp = reinterpret_cast<uint8_t*>(::operator new(CHUNK_SIZE + 8));
        *(reinterpret_cast<uint32_t*>(newp)) = activeMagic();
        return newp + 8;
    }
    static void operator delete(void* obj, size_t /*size*/) VL_MT_SAFE {
        uint8_t* const oldp = (static_cast<uint8_t*>(obj)) - 8;
        if (VL_UNLIKELY(*(reinterpret_cast<uint32_t*>(oldp)) != activeMagic())) {
            VL_FATAL_MT(__FILE__, __LINE__, "",
                        "vpi_release_handle() called on same object twice, or on non-Verilator "
                        "VPI object");
        }
#ifdef VL_VPI_IMMEDIATE_FREE  // Define to aid in finding leaky handles
        ::operator delete(oldp);
#else
        *(reinterpret_cast<void**>(oldp)) = t_freeHeadp;
        t_freeHeadp = oldp;
#endif
    }
    // MEMBERS
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpio* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpio*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    vpiHandle castVpiHandle() { return reinterpret_cast<vpiHandle>(this); }
    // ACCESSORS
    virtual const char* name() const { return "<null>"; }
    virtual const char* fullname() const { return "<null>"; }
    virtual const char* defname() const { return "<null>"; }
    virtual uint32_t type() const { return 0; }
    virtual uint32_t constType() const { return vpiUndefined; }
    virtual uint32_t size() const { return 0; }
    virtual const VerilatedRange* rangep() const { return nullptr; }
    virtual vpiHandle dovpi_scan() { return nullptr; }
    virtual PLI_INT32 dovpi_remove_cb() { return 0; }
};

class VerilatedVpioReasonCb final : public VerilatedVpio {
    // A handle to a timed or non-timed callback created with vpi_register_cb
    // User can call vpi_remove_cb or vpi_release_handle on it
    const uint64_t m_id;  // Unique id/sequence number to find schedule's event
    const QData m_time;  // Scheduled time, or 0 = not timed
    const PLI_INT32 m_reason;  // VPI callback reason code

public:
    // cppcheck-suppress uninitVar  // m_value
    VerilatedVpioReasonCb(uint64_t id, QData time, PLI_INT32 reason)
        : m_id{id}
        , m_time{time}
        , m_reason{reason} {}
    ~VerilatedVpioReasonCb() override = default;
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioReasonCb* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioReasonCb*>(reinterpret_cast<VerilatedVpioReasonCb*>(h));
    }
    uint32_t type() const override { return vpiCallback; }
    PLI_INT32 dovpi_remove_cb() override;
};

class VerilatedVpioConst final : public VerilatedVpio {
    const int32_t m_num;

public:
    explicit VerilatedVpioConst(int32_t num)
        : m_num{num} {}
    ~VerilatedVpioConst() override = default;
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioConst* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioConst*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t type() const override { return vpiConstant; }
    uint32_t constType() const override { return vpiDecConst; }
    int32_t num() const { return m_num; }
};

class VerilatedVpioVarBase VL_NOT_FINAL : public VerilatedVpio {
protected:
    const VerilatedVar* m_varp = nullptr;
    const VerilatedScope* m_scopep = nullptr;
    std::string m_fullname;
    int32_t m_indexedDim = -1;
    const VerilatedRange* get_range() const { return m_varp->range(m_indexedDim + 1); }

public:
    VerilatedVpioVarBase(const VerilatedVar* varp, const VerilatedScope* scopep)
        : m_varp{varp}
        , m_scopep{scopep}
        , m_fullname{std::string{m_scopep->name()} + '.' + m_varp->name()} {}
    explicit VerilatedVpioVarBase(const VerilatedVpioVarBase* varp) {
        if (varp) {
            m_varp = varp->m_varp;
            m_scopep = varp->m_scopep;
            m_fullname = varp->m_fullname;
            m_indexedDim = varp->m_indexedDim;
        }
    }
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioVarBase* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioVarBase*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    const VerilatedVar* varp() const { return m_varp; }
    const VerilatedScope* scopep() const { return m_scopep; }
    // Returns the number of the currently indexed dimension (starting at -1 for none).
    int32_t indexedDim() const { return m_indexedDim; }
    // Returns whether the currently indexed dimension is unpacked.
    bool isIndexedDimUnpacked() const { return indexedDim() + 1 < varp()->udims(); }
    // Returns a maximum accessible dimension number, counting only unpacked dimensions
    // (if onlyUnpacked == true), or both unpacked + packed.
    int32_t maxDim(bool onlyUnpacked) const {
        return onlyUnpacked ? varp()->udims() - 1 : varp()->dims() - 1;
    }
    // Returns a number of elements in the array, stopping at the unpacked-packed boundary.
    uint32_t size() const override {
        const int maxDimNum = maxDim(isIndexedDimUnpacked());
        int size = 1;
        for (int dim = indexedDim() + 1; dim <= maxDimNum; ++dim)
            size *= varp()->range(dim)->elements();
        return size;
    }
    // If the array is unpacked, returns the bitsize of a single underlying packed element.
    // If the array is packed, returns the bitsize of the whole array.
    uint32_t bitSize() const {
        if (isIndexedDimUnpacked())
            return varp()->entBits();
        else
            return size();
    }
    const VerilatedRange* rangep() const override { return get_range(); }
    const char* name() const override { return m_varp->name(); }
    const char* fullname() const override { return m_fullname.c_str(); }
    virtual void* varDatap() const { return m_varp->datap(); }
    CData* varCDatap() const {
        VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_UINT8););
        return reinterpret_cast<CData*>(varDatap());
    }
    SData* varSDatap() const {
        VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_UINT16););
        return reinterpret_cast<SData*>(varDatap());
    }
    IData* varIDatap() const {
        VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_UINT32););
        return reinterpret_cast<IData*>(varDatap());
    }
    QData* varQDatap() const {
        VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_UINT64););
        return reinterpret_cast<QData*>(varDatap());
    }
    EData* varEDatap() const {
        VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_WDATA););
        return reinterpret_cast<EData*>(varDatap());
    }
    double* varRealDatap() const {
        VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_REAL););
        return reinterpret_cast<double*>(varDatap());
    }
    std::string* varStringDatap() const {
        VL_DEBUG_IFDEF(assert(varp()->vltype() == VLVT_STRING););
        return reinterpret_cast<std::string*>(varDatap());
    }
    virtual uint32_t bitOffset() const { return 0; }
};

class VerilatedVpioParam final : public VerilatedVpioVarBase {
public:
    VerilatedVpioParam(const VerilatedVar* varp, const VerilatedScope* scopep)
        : VerilatedVpioVarBase{varp, scopep} {}
    ~VerilatedVpioParam() override = default;

    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioParam* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioParam*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t type() const override { return vpiParameter; }
    uint32_t constType() const override {
        switch (m_varp->vltype()) {
        case VLVT_UINT8:
        case VLVT_UINT16:
        case VLVT_UINT32:
        case VLVT_UINT64:
        case VLVT_WDATA: return vpiDecConst;
        case VLVT_STRING: return vpiStringConst;
        case VLVT_REAL: return vpiRealConst;
        default: return vpiUndefined;
        }
    }
};

class VerilatedVpioRange final : public VerilatedVpio {
    const VerilatedRange* const m_rangep;

public:
    explicit VerilatedVpioRange(const VerilatedRange* rangep)
        : m_rangep{rangep} {}
    ~VerilatedVpioRange() override = default;
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioRange* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioRange*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t type() const override { return vpiRange; }
    uint32_t size() const override { return m_rangep->elements(); }
    const VerilatedRange* rangep() const override { return m_rangep; }
};

class VerilatedVpioRangeIter final : public VerilatedVpio {
    const std::vector<VerilatedRange> m_ranges;
    std::vector<VerilatedRange>::const_iterator m_iter;

public:
    explicit VerilatedVpioRangeIter(const std::vector<VerilatedRange>& ranges)
        : m_ranges{ranges} {
        m_iter = m_ranges.begin();
    }
    ~VerilatedVpioRangeIter() override = default;
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioRangeIter* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioRangeIter*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t type() const override { return vpiIterator; }
    vpiHandle dovpi_scan() override {
        if (VL_UNLIKELY(m_iter == m_ranges.end())) {
            delete this;  // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle
            return nullptr;
        }
        VerilatedRange* const rangep = new VerilatedRange{*m_iter};
        ++m_iter;
        return ((new VerilatedVpioRange{rangep})->castVpiHandle());
    }
};

class VerilatedVpioScope VL_NOT_FINAL : public VerilatedVpio {
protected:
    const VerilatedScope* const m_scopep;
    bool m_toplevel = false;
    const char* m_name;
    const char* m_fullname;
    const char* m_defname;

public:
    explicit VerilatedVpioScope(const VerilatedScope* scopep)
        : m_scopep{scopep} {
        m_fullname = m_scopep->name();
        if (std::strncmp(m_fullname, "TOP.", 4) == 0) m_fullname += 4;
        m_name = m_scopep->identifier();
        m_defname = m_scopep->defname();
    }
    ~VerilatedVpioScope() override = default;
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioScope* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioScope*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t type() const override { return vpiGenScope; }
    const VerilatedScope* scopep() const { return m_scopep; }
    const char* name() const override { return m_name; }
    const char* fullname() const override { return m_fullname; }
    const char* defname() const override { return m_defname; }
    bool toplevel() const { return m_toplevel; }
};

class VerilatedVpioVar VL_NOT_FINAL : public VerilatedVpioVarBase {
    uint8_t* m_prevDatap = nullptr;  // Previous value of data, for cbValueChange
    union {
        uint8_t u8[4];
        uint32_t u32;
    } m_mask;  // memoized variable mask
    uint32_t m_entSize = 0;  // memoized variable size
    uint32_t m_bitOffset = 0;

protected:
    void* m_varDatap = nullptr;  // varp()->datap() adjusted for array entries
    std::vector<int32_t> m_index;

public:
    VerilatedVpioVar(const VerilatedVar* varp, const VerilatedScope* scopep)
        : VerilatedVpioVarBase{varp, scopep} {
        m_mask.u32 = VL_MASK_I(varp->entBits());
        m_entSize = varp->entSize();
        m_varDatap = varp->datap();
    }
    explicit VerilatedVpioVar(const VerilatedVpioVar* varp)
        : VerilatedVpioVarBase{varp} {
        if (varp) {
            m_mask.u32 = varp->m_mask.u32;
            m_entSize = varp->m_entSize;
            m_varDatap = varp->m_varDatap;
            m_index = varp->m_index;
            // Not copying m_prevDatap, must be nullptr
        } else {
            m_mask.u32 = 0;
        }
    }
    ~VerilatedVpioVar() override {
        if (m_prevDatap) VL_DO_CLEAR(delete[] m_prevDatap, m_prevDatap = nullptr);
    }
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioVar* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioVar*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t bitOffset() const override { return m_bitOffset; }
    uint32_t mask() const { return m_mask.u32; }
    uint8_t mask_byte(int idx) const { return m_mask.u8[idx & 3]; }
    uint32_t entSize() const { return m_entSize; }
    const std::vector<int32_t>& index() const { return m_index; }
    VerilatedVpioVar* withIndex(int32_t index) const {
        if (VL_UNLIKELY(indexedDim() + 1 >= varp()->dims())) return nullptr;

        auto ret = new VerilatedVpioVar{this};
        ret->m_index.push_back(index);
        ret->m_indexedDim++;

        int chunkSize = 1;
        for (int dim = maxDim(isIndexedDimUnpacked()); dim > indexedDim() + 1; dim--)
            chunkSize *= varp()->range(dim)->elements();

        if (isIndexedDimUnpacked())
            ret->m_varDatap = (static_cast<uint8_t*>(ret->m_varDatap))
                              + entSize() * chunkSize * (index - get_range()->low());
        else
            ret->m_bitOffset += chunkSize * (index - get_range()->low());

        return ret;
    }
    uint32_t type() const override {
        uint32_t type = vpiReg;
        switch (varp()->vltype()) {
        case VLVT_REAL: type = vpiRealVar; break;
        case VLVT_STRING: type = vpiStringVar; break;
        default: break;
        }
        if (isIndexedDimUnpacked())
            return vpiRegArray;
        else
            return type;
    }
    const char* fullname() const override {
        static thread_local std::string t_out;
        t_out = std::string{scopep()->name()} + "." + name();
        for (auto idx : index()) t_out += "[" + std::to_string(idx) + "]";
        return t_out.c_str();
    }
    void* prevDatap() const { return m_prevDatap; }
    void* varDatap() const override { return m_varDatap; }
    void createPrevDatap() {
        if (VL_UNLIKELY(!m_prevDatap)) {
            m_prevDatap = new uint8_t[entSize()];
            std::memcpy(prevDatap(), m_varDatap, entSize());
        }
    }
};

class VerilatedVpioVarIter final : public VerilatedVpio {
    const VerilatedScope* const m_scopep;
    VerilatedVarNameMap::const_iterator m_it;
    bool m_started = false;
    const VerilatedScope* m_topscopep = nullptr;
    bool m_onlyParams;

public:
    explicit VerilatedVpioVarIter(const VerilatedVpioScope* vop, bool onlyParams = false)
        : m_scopep{vop->scopep()}
        , m_onlyParams{onlyParams} {
        if (VL_UNLIKELY(vop->toplevel()))
            // This is a toplevel, so get TOP scope to search for ports during vpi_scan.
            m_topscopep = Verilated::threadContextp()->scopeFind("TOP");
    }
    ~VerilatedVpioVarIter() override = default;
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioVarIter* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioVarIter*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t type() const override { return vpiIterator; }
    vpiHandle dovpi_scan() override {
        if (VL_UNLIKELY(!m_scopep->varsp())) {
            delete this;  // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle
            return nullptr;  // End of list - only one deep
        }
        while (true) {
            const VerilatedVarNameMap* const varsp = m_scopep->varsp();
            if (VL_UNLIKELY(!m_started)) {
                m_it = varsp->begin();
                m_started = true;
            } else if (VL_UNLIKELY(m_it == varsp->end())) {
                delete this;  // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle
                return nullptr;
            } else {
                ++m_it;
            }
            if (VL_UNLIKELY(m_it == varsp->end())) {
                delete this;  // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle
                return nullptr;
            }
            if (m_onlyParams && !m_it->second.isParam()) continue;
            if (VL_UNLIKELY(m_topscopep)) {
                if (const VerilatedVar* topvarp = m_topscopep->varFind(m_it->second.name())) {
                    if (topvarp->isParam()) {
                        return ((new VerilatedVpioParam{topvarp, m_topscopep})->castVpiHandle());
                    } else {
                        return ((new VerilatedVpioVar{topvarp, m_topscopep})->castVpiHandle());
                    }
                }
            }
            if (m_it->second.isParam()) {
                return ((new VerilatedVpioParam{&(m_it->second), m_scopep})->castVpiHandle());
            } else {
                return ((new VerilatedVpioVar{&(m_it->second), m_scopep})->castVpiHandle());
            }
        }
    }
};

class VerilatedVpioRegIter final : public VerilatedVpio {
    VerilatedVpioVar* m_var;
    std::vector<VerilatedRange> m_ranges;
    std::vector<int> m_nextIndex;
    const int32_t m_maxDim;

public:
    explicit VerilatedVpioRegIter(const VerilatedVpioVar* vop)
        : m_var{new VerilatedVpioVar(vop)}
        , m_maxDim{vop->varp()->udims() - 1} {
        for (auto it = vop->indexedDim() + 1; it <= m_maxDim; ++it)
            m_ranges.push_back(*vop->varp()->range(it));
        for (auto it : m_ranges) m_nextIndex.push_back(it.right());
    }
    ~VerilatedVpioRegIter() override = default;
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioRegIter* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioRegIter*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t type() const override { return vpiIterator; }
    vpiHandle dovpi_scan() override {
        if (VL_UNLIKELY(m_var->indexedDim() >= m_maxDim)) {
            // Trying to iterate over a non-array object
            delete this;
            return nullptr;
        }
        if (m_nextIndex.front() > m_ranges.front().high()
            || m_nextIndex.front() < m_ranges.front().low()) {
            // Finished iterating
            delete this;
            return nullptr;
        }

        VerilatedVpioVar* ret = m_var;
        for (auto it : m_nextIndex) ret = ret->withIndex(it);

        // Increase the index, pretending the dimensions are flattened
        for (int32_t it = m_ranges.size() - 1; it >= 0; it--) {
            m_nextIndex.at(it) += m_ranges.at(it).increment();
            if (m_nextIndex.at(it) <= m_ranges.at(it).high()
                && m_nextIndex.at(it) >= m_ranges.at(it).low())
                break;
            else if (it > 0)
                m_nextIndex.at(it) = m_ranges.at(it).right();
        }

        return ret->castVpiHandle();
    }
};

class VerilatedVpioModule final : public VerilatedVpioScope {

public:
    explicit VerilatedVpioModule(const VerilatedScope* modulep)
        : VerilatedVpioScope{modulep} {
        // Look for '.' not inside escaped identifier
        const std::string scopename = m_fullname;
        std::string::size_type pos = std::string::npos;
        size_t i = 0;
        while (i < scopename.length()) {
            if (scopename[i] == '\\') {
                while (i < scopename.length() && scopename[i] != ' ') ++i;
                ++i;  // Proc ' ', it should always be there. Then grab '.' on next cycle
            } else {
                while (i < scopename.length() && scopename[i] != '.') ++i;
                if (i < scopename.length()) pos = i++;
            }
        }
        if (VL_UNLIKELY(pos == std::string::npos)) m_toplevel = true;
    }
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioModule* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioModule*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t type() const override { return vpiModule; }
};

class VerilatedVpioModuleIter final : public VerilatedVpio {
    const std::vector<const VerilatedScope*>* m_vec;
    std::vector<const VerilatedScope*>::const_iterator m_it;

public:
    explicit VerilatedVpioModuleIter(const std::vector<const VerilatedScope*>& vec)
        : m_vec{&vec} {
        m_it = m_vec->begin();
    }
    ~VerilatedVpioModuleIter() override = default;
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioModuleIter* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioModuleIter*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t type() const override { return vpiIterator; }
    vpiHandle dovpi_scan() override {
        while (true) {
            if (m_it == m_vec->end()) {
                delete this;  // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle
                return nullptr;
            }
            const VerilatedScope* const modp = *m_it++;
            const VerilatedScope::Type itype = modp->type();
            if (itype == VerilatedScope::SCOPE_MODULE) {
                return (new VerilatedVpioModule{modp})->castVpiHandle();
            }
        }
    }
};

class VerilatedVpioScopeIter final : public VerilatedVpio {
    const std::vector<const VerilatedScope*>* m_vec;
    std::vector<const VerilatedScope*>::const_iterator m_it;

public:
    explicit VerilatedVpioScopeIter(const std::vector<const VerilatedScope*>& vec)
        : m_vec{&vec} {
        m_it = m_vec->begin();
    }
    ~VerilatedVpioScopeIter() override = default;
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioScopeIter* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioScopeIter*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t type() const override { return vpiIterator; }
    vpiHandle dovpi_scan() override {
        while (true) {
            if (m_it == m_vec->end()) {
                delete this;  // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle
                return nullptr;
            }
            const VerilatedScope* const modp = *m_it++;
            const VerilatedScope::Type itype = modp->type();
            if (itype == VerilatedScope::SCOPE_OTHER) {
                return (new VerilatedVpioScope{modp})->castVpiHandle();
            } else if (itype == VerilatedScope::SCOPE_MODULE) {
                return (new VerilatedVpioModule{modp})->castVpiHandle();
            }
        }
    }
};

static const char* d_unit = "$unit";
class VerilatedVpioPackage final : public VerilatedVpioScope {
    std::string m_fullname_string;

public:
    explicit VerilatedVpioPackage(const VerilatedScope* modulep)
        : VerilatedVpioScope{modulep} {
        m_fullname_string = std::string{m_fullname} + "::";
        if (m_fullname_string == "\\$unit ::") m_fullname_string = "$unit::";

        if (strcmp(m_name, "\\$unit ") == 0) m_name = d_unit;
    }
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioPackage* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioPackage*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    const char* fullname() const override { return m_fullname_string.c_str(); }
    uint32_t type() const override { return vpiPackage; }
};

class VerilatedVpioInstanceIter final : public VerilatedVpio {
    const std::vector<const VerilatedScope*>* m_vec;
    std::vector<const VerilatedScope*>::const_iterator m_it;

public:
    explicit VerilatedVpioInstanceIter(const std::vector<const VerilatedScope*>& vec)
        : m_vec{&vec} {
        m_it = m_vec->begin();
    }
    ~VerilatedVpioInstanceIter() override = default;
    // cppcheck-suppress duplInheritedMember
    static VerilatedVpioInstanceIter* castp(vpiHandle h) {
        return dynamic_cast<VerilatedVpioInstanceIter*>(reinterpret_cast<VerilatedVpio*>(h));
    }
    uint32_t type() const override { return vpiIterator; }
    vpiHandle dovpi_scan() override {
        while (true) {
            if (m_it == m_vec->end()) {
                delete this;  // IEEE 37.2.2 vpi_scan at end does a vpi_release_handle
                return nullptr;
            }
            const VerilatedScope::Type itype = (*m_it)->type();
            const VerilatedScope* const modp = *m_it++;
            if (itype == VerilatedScope::SCOPE_MODULE) {
                return (new VerilatedVpioModule{modp})->castVpiHandle();
            }
            if (itype == VerilatedScope::SCOPE_PACKAGE) {
                return (new VerilatedVpioPackage{modp})->castVpiHandle();
            }
        }
    }
};

//======================================================================

using VerilatedPliCb = PLI_INT32 (*)(struct t_cb_data*);

class VerilatedVpiCbHolder final {
    // Holds information needed to call a callback
    uint64_t m_id;  // Unique id/sequence number to find schedule's event, 0 = invalid
    s_cb_data m_cbData;
    s_vpi_value m_value;
    VerilatedVpioVar m_varo;  // If a cbValueChange callback, the object we will return

public:
    // cppcheck-suppress uninitVar  // m_value
    VerilatedVpiCbHolder(uint64_t id, const s_cb_data* cbDatap, const VerilatedVpioVar* varop)
        : m_id{id}
        , m_cbData{*cbDatap}
        , m_varo{varop} {
        m_value.format = cbDatap->value ? cbDatap->value->format : vpiSuppressVal;
        m_cbData.value = &m_value;
        if (varop) {
            m_cbData.obj = m_varo.castVpiHandle();
            m_varo.createPrevDatap();
        } else {
            m_cbData.obj = nullptr;
        }
    }
    ~VerilatedVpiCbHolder() = default;
    VerilatedPliCb cb_rtnp() const { return m_cbData.cb_rtn; }
    s_cb_data* cb_datap() { return &m_cbData; }
    uint64_t id() const { return m_id; }
    bool invalid() const { return !m_id; }
    void invalidate() { m_id = 0; }
};

class VerilatedVpiPutHolder final {
    VerilatedVpioVar m_var;
    s_vpi_value m_value;
    union Storage {
        char init = 0;  // to ensure trivial constructor
        std::string str;
        std::vector<s_vpi_vecval> vec;
        ~Storage() noexcept {/* handled by VerilatedVpiPutHolder */};
    } m_storage{};

public:
    VerilatedVpiPutHolder(const VerilatedVpioVar* vop, p_vpi_value valuep)
        : m_var{vop} {
        m_value.format = valuep->format;
        switch (valuep->format) {
        case vpiBinStrVal:  // FALLTHRU
        case vpiOctStrVal:  // FALLTHRU
        case vpiDecStrVal:  // FALLTHRU
        case vpiHexStrVal:  // FALLTHRU
        case vpiStringVal: {
            new (&m_storage.str) std::string{valuep->value.str};
            m_value.value.str = const_cast<char*>(m_storage.str.c_str());
            break;
        }
        case vpiScalarVal: {
            m_value.value.scalar = valuep->value.scalar;
            break;
        }
        case vpiIntVal: {
            m_value.value.integer = valuep->value.integer;
            break;
        }
        case vpiRealVal: {
            m_value.value.real = valuep->value.real;
            break;
        }
        case vpiVectorVal: {
            size_t words = 0;
            switch (vop->varp()->vltype()) {
            case VLVT_UINT8:
            case VLVT_UINT16:
            case VLVT_UINT32: {
                words = 1;
                break;
            }
            case VLVT_UINT64: {
                words = 2;
                break;
            }
            case VLVT_WDATA: {
                words = VL_WORDS_I(vop->varp()->entBits());
                break;
            }
            default: break;
            }
            new (&m_storage.vec)
                std::vector<s_vpi_vecval>{valuep->value.vector, &valuep->value.vector[words]};
            m_value.value.vector = m_storage.vec.data();
            break;
        }
        }
    }

    VerilatedVpiPutHolder(VerilatedVpiPutHolder const& o)
        : m_var{o.m_var}
        , m_value{o.m_value} {
        switch (m_value.format) {
        case vpiBinStrVal:  // FALLTHRU
        case vpiOctStrVal:  // FALLTHRU
        case vpiDecStrVal:  // FALLTHRU
        case vpiHexStrVal:  // FALLTHRU
        case vpiStringVal: {
            new (&m_storage.str) std::string{o.m_storage.str};
            break;
        }
        case vpiVectorVal: {
            new (&m_storage.vec) std::vector<s_vpi_vecval>{o.m_storage.vec};
            break;
        }
        }
    }

    VerilatedVpiPutHolder(VerilatedVpiPutHolder&& o) noexcept
        : m_var{std::move(o.m_var)}
        , m_value{std::move(o.m_value)} {
        switch (m_value.format) {
        case vpiBinStrVal:  // FALLTHRU
        case vpiOctStrVal:  // FALLTHRU
        case vpiDecStrVal:  // FALLTHRU
        case vpiHexStrVal:  // FALLTHRU
        case vpiStringVal: {
            new (&m_storage.str) std::string{std::move(o.m_storage.str)};
            break;
        }
        case vpiVectorVal: {
            new (&m_storage.vec) std::vector<s_vpi_vecval>{std::move(o.m_storage.vec)};
            break;
        }
        }
    }

    ~VerilatedVpiPutHolder() noexcept {
        switch (m_value.format) {
        case vpiBinStrVal:  // FALLTHRU
        case vpiOctStrVal:  // FALLTHRU
        case vpiDecStrVal:  // FALLTHRU
        case vpiHexStrVal:  // FALLTHRU
        case vpiStringVal: m_storage.str.~basic_string(); break;
        case vpiVectorVal: m_storage.vec.~vector(); break;
        }
    }

    VerilatedVpioVar* varp() { return &m_var; }
    p_vpi_value valuep() { return &m_value; }

    static bool canInertialDelay(p_vpi_value valuep) {
        switch (valuep->format) {
        case vpiBinStrVal:  // FALLTHRU
        case vpiOctStrVal:  // FALLTHRU
        case vpiDecStrVal:  // FALLTHRU
        case vpiHexStrVal:  // FALLTHRU
        case vpiStringVal: {
            if (VL_UNLIKELY(!valuep->value.str)) return false;
            break;
        }
        case vpiScalarVal:  // FALLTHRU
        case vpiIntVal:  // FALLTHRU
        case vpiRealVal: break;
        case vpiVectorVal: {
            if (VL_UNLIKELY(!valuep->value.vector)) return false;
            break;
        }
        default: {
            return false;
        }
        }
        return true;
    }
};

struct VerilatedVpiTimedCbsCmp final {
    // Ordering sets keyed by time, then callback unique id
    bool operator()(const std::pair<QData, uint64_t>& a,
                    const std::pair<QData, uint64_t>& b) const {
        if (a.first < b.first) return true;
        if (a.first > b.first) return false;
        return a.second < b.second;
    }
};

class VerilatedVpiError;

class VerilatedVpiImp final {
    enum { CB_ENUM_MAX_VALUE = cbAtEndOfSimTime + 1 };  // Maximum callback reason
    using VpioCbList = std::list<VerilatedVpiCbHolder>;
    using VpioFutureCbs = std::map<std::pair<QData, uint64_t>, VerilatedVpiCbHolder>;

    // All only medium-speed, so use singleton function
    // Callbacks that are past or at current timestamp
    std::array<VpioCbList, CB_ENUM_MAX_VALUE> m_cbCurrentLists;
    VpioCbList m_cbCallList;  // List of callbacks currently being called by callCbs
    VpioFutureCbs m_futureCbs;  // Time based callbacks for future timestamps
    VpioFutureCbs m_nextCbs;  // cbNextSimTime callbacks
    std::list<VerilatedVpiPutHolder> m_inertialPuts;  // Pending vpi puts due to vpiInertialDelay
    VerilatedVpiError* m_errorInfop = nullptr;  // Container for vpi error info
    VerilatedAssertOneThread m_assertOne;  // Assert only called from single thread
    uint64_t m_nextCallbackId = 1;  // Id to identify callback
    bool m_evalNeeded = false;  // Model has had signals updated via vpi_put_value()

    static VerilatedVpiImp& s() {  // Singleton
        static VerilatedVpiImp s_s;
        return s_s;
    }

public:
    static void assertOneCheck() { s().m_assertOne.check(); }
    static uint64_t nextCallbackId() { return ++s().m_nextCallbackId; }

    static void cbCurrentAdd(uint64_t id, const s_cb_data* cb_data_p) {
        // The passed cb_data_p was property of the user, so need to recreate
        if (VL_UNCOVERABLE(cb_data_p->reason >= CB_ENUM_MAX_VALUE)) {
            VL_FATAL_MT(__FILE__, __LINE__, "", "vpi bb reason too large");
        }
        VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_register_cb reason=%d id=%" PRId64 " obj=%p\n",
                                    cb_data_p->reason, id, cb_data_p->obj););
        VerilatedVpioVar* varop = nullptr;
        if (cb_data_p->reason == cbValueChange) varop = VerilatedVpioVar::castp(cb_data_p->obj);
        s().m_cbCurrentLists[cb_data_p->reason].emplace_back(id, cb_data_p, varop);
    }
    static void cbFutureAdd(uint64_t id, const s_cb_data* cb_data_p, QData time) {
        // The passed cb_data_p was property of the user, so need to recreate
        VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_register_cb reason=%d id=%" PRId64 " time=%" PRIu64
                                    " obj=%p\n",
                                    cb_data_p->reason, id, time, cb_data_p->obj););
        s().m_futureCbs.emplace(std::piecewise_construct, std::forward_as_tuple(time, id),
                                std::forward_as_tuple(id, cb_data_p, nullptr));
    }
    static void cbNextAdd(uint64_t id, const s_cb_data* cb_data_p, QData time) {
        // The passed cb_data_p was property of the user, so need to recreate
        VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_register_cb reason=%d(NEXT) id=%" PRId64
                                    " time=%" PRIu64 " obj=%p\n",
                                    cb_data_p->reason, id, time, cb_data_p->obj););
        s().m_nextCbs.emplace(std::piecewise_construct, std::forward_as_tuple(time, id),
                              std::forward_as_tuple(id, cb_data_p, nullptr));
    }
    static void cbReasonRemove(uint64_t id, uint32_t reason, QData time) {
        // Id might no longer exist, if already removed due to call after event, or teardown
        // We do not remove it now as we may be iterating the list,
        // instead set to nullptr and will cleanup later
        // Remove from cbCurrent queue
        for (auto& ir : s().m_cbCurrentLists[reason]) {
            if (ir.id() == id) {
                ir.invalidate();
                return;  // Once found, it won't also be in m_cbCallList, m_futureCbs, or m_nextCbs
            }
        }
        for (auto& ir : s().m_cbCallList) {
            if (ir.id() == id) {
                ir.invalidate();
                return;  // Once found, it won't also be in m_futureCbs or m_nextCbs
            }
        }
        {  // Remove from cbFuture queue
            const auto it = s().m_futureCbs.find(std::make_pair(time, id));
            if (it != s().m_futureCbs.end()) {
                it->second.invalidate();
                return;
            }
        }
        {  // Remove from cbNext
            const auto it = s().m_nextCbs.find(std::make_pair(time, id));
            if (it != s().m_nextCbs.end()) {
                it->second.invalidate();
                return;
            }
        }
    }
    static void moveFutureCbs() VL_MT_UNSAFE_ONE {
        // For any events past current time, move from cbFuture queue to cbCurrent queue
        if (s().m_futureCbs.empty() && s().m_nextCbs.empty()) return;
        // VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: moveFutureCbs\n"); dumpCbs(); );
        const QData time = VL_TIME_Q();
        for (auto it = s().m_futureCbs.begin();  //
             VL_UNLIKELY(it != s().m_futureCbs.end() && it->first.first <= time);) {
            VerilatedVpiCbHolder& hor = it->second;
            const auto last_it = it;
            ++it;
            if (VL_UNLIKELY(!hor.invalid())) {
                VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: moveFutureCbs id=%" PRId64 "\n", hor.id()););
                s().m_cbCurrentLists[hor.cb_datap()->reason].emplace_back(hor);
            }
            s().m_futureCbs.erase(last_it);
        }
        for (auto it = s().m_nextCbs.begin();  //
             VL_UNLIKELY(it != s().m_nextCbs.end() && it->first.first < time);) {
            VerilatedVpiCbHolder& hor = it->second;
            const auto last_it = it;
            ++it;
            if (VL_UNLIKELY(!hor.invalid())) {
                VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: moveFutureCbs id=%" PRId64 "\n", hor.id()););
                s().m_cbCurrentLists[hor.cb_datap()->reason].emplace_back(hor);
            }
            s().m_nextCbs.erase(last_it);
        }
    }
    static QData cbNextDeadline() {
        const auto it = s().m_futureCbs.cbegin();
        if (VL_LIKELY(it != s().m_futureCbs.cend())) return it->first.first;
        return ~0ULL;  // maxquad
    }
    static bool hasCbs(const uint32_t reason) VL_MT_UNSAFE_ONE {
        return !s().m_cbCurrentLists[reason].empty();
    }
    static bool callCbs(const uint32_t reason) VL_MT_UNSAFE_ONE {
        VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: callCbs reason=%u\n", reason););
        assertOneCheck();
        moveFutureCbs();
        if (s().m_cbCurrentLists[reason].empty()) return false;
        // Iterate on old list, making new list empty, to prevent looping over newly added elements
        std::swap(s().m_cbCurrentLists[reason], s().m_cbCallList);
        bool called = false;
        for (VerilatedVpiCbHolder& ihor : s().m_cbCallList) {
            // cbReasonRemove sets to nullptr, so we know on removal the old end() will still exist
            if (VL_LIKELY(!ihor.invalid())) {  // Not deleted earlier
                VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: reason_callback reason=%d id=%" PRId64 "\n",
                                            reason, ihor.id()););
                ihor.invalidate();  // Timed callbacks are one-shot
                (ihor.cb_rtnp())(ihor.cb_datap());
                called = true;
            }
        }
        s().m_cbCallList.clear();
        return called;
    }
    static bool callValueCbs() VL_MT_UNSAFE_ONE {
        assertOneCheck();
        VpioCbList& cbObjList = s().m_cbCurrentLists[cbValueChange];
        bool called = false;
        std::set<VerilatedVpioVar*> update;  // set of objects to update after callbacks
        if (cbObjList.empty()) return called;
        const auto last = std::prev(cbObjList.end());  // prevent looping over newly added elements
        for (auto it = cbObjList.begin(); true;) {
            // cbReasonRemove sets to nullptr, so we know on removal the old end() will still exist
            const bool was_last = it == last;
            if (VL_UNLIKELY(it->invalid())) {  // Deleted earlier, cleanup
                it = cbObjList.erase(it);
                if (was_last) break;
                continue;
            }
            VerilatedVpiCbHolder& ho = *it++;
            VerilatedVpioVar* const varop
                = reinterpret_cast<VerilatedVpioVar*>(ho.cb_datap()->obj);
            void* const newDatap = varop->varDatap();
            void* const prevDatap = varop->prevDatap();  // Was malloced when we added the callback
            VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: value_test %s v[0]=%d/%d %p %p\n",
                                        varop->fullname(), *(static_cast<CData*>(newDatap)),
                                        *(static_cast<CData*>(prevDatap)), newDatap, prevDatap););
            if (std::memcmp(prevDatap, newDatap, varop->entSize()) != 0) {
                VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: value_callback %" PRId64 " %s v[0]=%d\n",
                                            ho.id(), varop->fullname(),
                                            *(static_cast<CData*>(newDatap))););
                update.insert(varop);
                vpi_get_value(ho.cb_datap()->obj, ho.cb_datap()->value);
                (ho.cb_rtnp())(ho.cb_datap());
                called = true;
            }
            if (was_last) break;
        }
        for (const VerilatedVpioVar* const ip : update) {
            std::memcpy(ip->prevDatap(), ip->varDatap(), ip->entSize());
        }
        return called;
    }
    static void dumpCbs() VL_MT_UNSAFE_ONE;
    static VerilatedVpiError* error_info() VL_MT_UNSAFE_ONE;  // getter for vpi error info
    static bool evalNeeded() { return s().m_evalNeeded; }
    static void evalNeeded(bool evalNeeded) { s().m_evalNeeded = evalNeeded; }
    static void inertialDelay(const VerilatedVpioVar* vop, p_vpi_value valuep) {
        s().m_inertialPuts.emplace_back(vop, valuep);
    }
    static void doInertialPuts() {
        for (auto& it : s().m_inertialPuts) {
            vpi_put_value(it.varp()->castVpiHandle(), it.valuep(), nullptr, vpiNoDelay);
        }
        s().m_inertialPuts.clear();
    }
};

//======================================================================
// Statics
// Internal note: Globals may multi-construct, see verilated.cpp top.

thread_local uint8_t* VerilatedVpio::t_freeHeadp = nullptr;

//======================================================================
// VerilatedVpiError
// Internal container for vpi error info

class VerilatedVpiError final {
    t_vpi_error_info m_errorInfo;
    bool m_flag = false;
    char m_buff[VL_VPI_LINE_SIZE_];
    void setError(PLI_BYTE8* message, PLI_BYTE8* code, PLI_BYTE8* file, PLI_INT32 line) {
        m_errorInfo.message = message;
        m_errorInfo.file = file;
        m_errorInfo.line = line;
        m_errorInfo.code = code;
        do_callbacks();
    }
    void do_callbacks() {
        if (getError()->level >= vpiError && Verilated::threadContextp()->fatalOnVpiError()) {
            // Stop on vpi error/unsupported
            vpi_unsupported();
        }
        // We need to run above code first because in the case that the
        // callback executes further vpi functions we will loose the error
        // as it will be overwritten.
        VerilatedVpiImp::callCbs(cbPLIError);
    }

public:
    VerilatedVpiError() {
        m_buff[0] = '\0';
        m_errorInfo.product = const_cast<PLI_BYTE8*>(Verilated::productName());
    }
    ~VerilatedVpiError() = default;
    static void selfTest() VL_MT_UNSAFE_ONE;
    VerilatedVpiError* setMessage(PLI_INT32 level) {
        m_flag = true;
        m_errorInfo.level = level;
        return this;
    }
    void setMessage(const std::string& file, PLI_INT32 line, const char* message, ...) {
        // message cannot be a const string& as va_start cannot use a reference
        static thread_local std::string t_filehold;
        va_list args;
        va_start(args, message);
        VL_VSNPRINTF(m_buff, sizeof(m_buff), message, args);
        va_end(args);
        m_errorInfo.state = vpiPLI;
        t_filehold = file;
        setError(static_cast<PLI_BYTE8*>(m_buff), nullptr,
                 const_cast<PLI_BYTE8*>(t_filehold.c_str()), line);
    }
    p_vpi_error_info getError() {
        if (m_flag) return &m_errorInfo;
        return nullptr;
    }
    void resetError() { m_flag = false; }
    static void vpi_unsupported() {
        // Not supported yet
        const p_vpi_error_info error_info_p = VerilatedVpiImp::error_info()->getError();
        if (error_info_p) {
            VL_FATAL_MT(error_info_p->file, error_info_p->line, "", error_info_p->message);
            return;
        }
        VL_FATAL_MT(__FILE__, __LINE__, "", "vpi_unsupported called without error info set");
    }
    static const char* strFromVpiVal(PLI_INT32 vpiVal) VL_PURE;
    static const char* strFromVpiObjType(PLI_INT32 vpiVal) VL_PURE;
    static const char* strFromVpiMethod(PLI_INT32 vpiVal) VL_PURE;
    static const char* strFromVpiCallbackReason(PLI_INT32 vpiVal) VL_PURE;
    static const char* strFromVpiProp(PLI_INT32 vpiVal) VL_PURE;
    static const char* strFromVpiConstType(PLI_INT32 vpiVal) VL_PURE;
};

//======================================================================
// VerilatedVpi implementation

bool VerilatedVpi::callCbs(uint32_t reason) VL_MT_UNSAFE_ONE {
    return VerilatedVpiImp::callCbs(reason);
}

bool VerilatedVpi::hasCbs(uint32_t reason) VL_MT_UNSAFE_ONE {
    return VerilatedVpiImp::hasCbs(reason);
}

// Historical, before we had multiple kinds of timed callbacks
void VerilatedVpi::callTimedCbs() VL_MT_UNSAFE_ONE { VerilatedVpiImp::callCbs(cbAfterDelay); }

bool VerilatedVpi::callValueCbs() VL_MT_UNSAFE_ONE { return VerilatedVpiImp::callValueCbs(); }

QData VerilatedVpi::cbNextDeadline() VL_MT_UNSAFE_ONE { return VerilatedVpiImp::cbNextDeadline(); }

void VerilatedVpi::dumpCbs() VL_MT_UNSAFE_ONE { VerilatedVpiImp::dumpCbs(); }

PLI_INT32 VerilatedVpioReasonCb::dovpi_remove_cb() {
    VerilatedVpiImp::cbReasonRemove(m_id, m_reason, m_time);
    delete this;  // IEEE 37.2.2 a vpi_remove_cb does a vpi_release_handle
    return 1;
}

void VerilatedVpi::clearEvalNeeded() VL_MT_UNSAFE_ONE { VerilatedVpiImp::evalNeeded(false); }
bool VerilatedVpi::evalNeeded() VL_MT_UNSAFE_ONE { return VerilatedVpiImp::evalNeeded(); }

void VerilatedVpi::doInertialPuts() VL_MT_UNSAFE_ONE { VerilatedVpiImp::doInertialPuts(); }

//======================================================================
// VerilatedVpiImp implementation

void VerilatedVpiImp::dumpCbs() VL_MT_UNSAFE_ONE {
    assertOneCheck();
    VL_DBG_MSGF("- vpi: dumpCbs\n");
    for (uint32_t reason = 0; reason < CB_ENUM_MAX_VALUE; ++reason) {
        VpioCbList& cbObjList = s().m_cbCurrentLists[reason];
        for (auto& ho : cbObjList) {
            if (VL_UNLIKELY(!ho.invalid())) {
                VL_DBG_MSGF("- vpi:   reason=%d=%s  id=%" PRId64 "\n", reason,
                            VerilatedVpiError::strFromVpiCallbackReason(reason), ho.id());
            }
        }
    }
    for (auto& ifuture : s().m_nextCbs) {
        const QData time = ifuture.first.first;
        VerilatedVpiCbHolder& ho = ifuture.second;
        if (VL_UNLIKELY(!ho.invalid())) {
            VL_DBG_MSGF("- vpi:   time=%" PRId64 "(NEXT) reason=%d=%s  id=%" PRId64 "\n", time,
                        ho.cb_datap()->reason,
                        VerilatedVpiError::strFromVpiCallbackReason(ho.cb_datap()->reason),
                        ho.id());
        }
    }
    for (auto& ifuture : s().m_futureCbs) {
        const QData time = ifuture.first.first;
        VerilatedVpiCbHolder& ho = ifuture.second;
        if (VL_UNLIKELY(!ho.invalid())) {
            VL_DBG_MSGF("- vpi:   time=%" PRId64 " reason=%d=%s  id=%" PRId64 "\n", time,
                        ho.cb_datap()->reason,
                        VerilatedVpiError::strFromVpiCallbackReason(ho.cb_datap()->reason),
                        ho.id());
        }
    }
}

VerilatedVpiError* VerilatedVpiImp::error_info() VL_MT_UNSAFE_ONE {
    VerilatedVpiImp::assertOneCheck();
    if (VL_UNLIKELY(!s().m_errorInfop)) s().m_errorInfop = new VerilatedVpiError;
    return s().m_errorInfop;
}

//======================================================================
// VerilatedVpiError Methods

const char* VerilatedVpiError::strFromVpiVal(PLI_INT32 vpiVal) VL_PURE {
    // clang-format off
    static const char* const names[] = {
        "*undefined*",
        "vpiBinStrVal",
        "vpiOctStrVal",
        "vpiDecStrVal",
        "vpiHexStrVal",
        "vpiScalarVal",
        "vpiIntVal",
        "vpiRealVal",
        "vpiStringVal",
        "vpiVectorVal",
        "vpiStrengthVal",
        "vpiTimeVal",
        "vpiObjTypeVal",
        "vpiSuppressVal",
        "vpiShortIntVal",
        "vpiLongIntVal",
        "vpiShortRealVal",
        "vpiRawTwoStateVal",
        "vpiRawFourStateVal",
    };
    // clang-format on
    if (VL_UNCOVERABLE(vpiVal < 0)) return names[0];
    return names[(vpiVal <= vpiRawFourStateVal) ? vpiVal : 0];
}
const char* VerilatedVpiError::strFromVpiObjType(PLI_INT32 vpiVal) VL_PURE {
    // clang-format off
    static const char* const names[] = {
        "*undefined*",
        "vpiAlways",
        "vpiAssignStmt",
        "vpiAssignment",
        "vpiBegin",
        "vpiCase",
        "vpiCaseItem",
        "vpiConstant",
        "vpiContAssign",
        "vpiDeassign",
        "vpiDefParam",
        "vpiDelayControl",
        "vpiDisable",
        "vpiEventControl",
        "vpiEventStmt",
        "vpiFor",
        "vpiForce",
        "vpiForever",
        "vpiFork",
        "vpiFuncCall",
        "vpiFunction",
        "vpiGate",
        "vpiIf",
        "vpiIfElse",
        "vpiInitial",
        "vpiIntegerVar",
        "vpiInterModPath",
        "vpiIterator",
        "vpiIODecl",
        "vpiMemory",
        "vpiMemoryWord",
        "vpiModPath",
        "vpiModule",
        "vpiNamedBegin",
        "vpiNamedEvent",
        "vpiNamedFork",
        "vpiNet",
        "vpiNetBit",
        "vpiNullStmt",
        "vpiOperation",
        "vpiParamAssign",
        "vpiParameter",
        "vpiPartSelect",
        "vpiPathTerm",
        "vpiPort",
        "vpiPortBit",
        "vpiPrimTerm",
        "vpiRealVar",
        "vpiReg",
        "vpiRegBit",
        "vpiRelease",
        "vpiRepeat",
        "vpiRepeatControl",
        "vpiSchedEvent",
        "vpiSpecParam",
        "vpiSwitch",
        "vpiSysFuncCall",
        "vpiSysTaskCall",
        "vpiTableEntry",
        "vpiTask",
        "vpiTaskCall",
        "vpiTchk",
        "vpiTchkTerm",
        "vpiTimeVar",
        "vpiTimeQueue",
        "vpiUdp",
        "vpiUdpDefn",
        "vpiUserSystf",
        "vpiVarSelect",
        "vpiWait",
        "vpiWhile",
        "vpiCondition",
        "vpiDelay",
        "vpiElseStmt",
        "vpiForIncStmt",
        "vpiForInitStmt",
        "vpiHighConn",
        "vpiLhs",
        "vpiIndex",
        "vpiLeftRange",
        "vpiLowConn",
        "vpiParent",
        "vpiRhs",
        "vpiRightRange",
        "vpiScope",
        "vpiSysTfCall",
        "vpiTchkDataTerm",
        "vpiTchkNotifier",
        "vpiTchkRefTerm",
        "vpiArgument",
        "vpiBit",
        "vpiDriver",
        "vpiInternalScope",
        "vpiLoad",
        "vpiModDataPathIn",
        "vpiModPathIn",
        "vpiModPathOut",
        "vpiOperand",
        "vpiPortInst",
        "vpiProcess",
        "vpiVariables",
        "vpiUse",
        "vpiExpr",
        "vpiPrimitive",
        "vpiStmt",
        "vpiAttribute",
        "vpiBitSelect",
        "vpiCallback",
        "vpiDelayTerm",
        "vpiDelayDevice",
        "vpiFrame",
        "vpiGateArray",
        "vpiModuleArray",
        "vpiPrimitiveArray",
        "vpiNetArray",
        "vpiRange",
        "vpiRegArray",
        "vpiSwitchArray",
        "vpiUdpArray",
        "vpiActiveTimeFormat",
        "vpiInTerm",
        "vpiInstanceArray",
        "vpiLocalDriver",
        "vpiLocalLoad",
        "vpiOutTerm",
        "vpiPorts",
        "vpiSimNet",
        "vpiTaskFunc",
        "vpiContAssignBit",
        "vpiNamedEventArray",
        "vpiIndexedPartSelect",
        "vpiBaseExpr",
        "vpiWidthExpr",
        "vpiGenScopeArray",
        "vpiGenScope",
        "vpiGenVar",
        "vpiAutomatics"
    };
    static const char* const sv_names1[] = {
        "vpiPackage",
        "vpiInterface",
        "vpiProgram",
        "vpiInterfaceArray",
        "vpiProgramArray",
        "vpiTypespec",
        "vpiModport",
        "vpiInterfaceTfDecl",
        "vpiRefObj",
        "vpiTypeParameter",
        "vpiLongIntVar",
        "vpiShortIntVar",
        "vpiIntVar",
        "vpiShortRealVar",
        "vpiByteVar",
        "vpiClassVar",
        "vpiStringVar",
        "vpiEnumVar",
        "vpiStructVar",
        "vpiUnionVar",
        "vpiBitVar",
        "vpiClassObj",
        "vpiChandleVar",
        "vpiPackedArrayVar",
        "*undefined*",  // 624 is not defined for object types
        "vpiLongIntTypespec",
        "vpiShortRealTypespec",
        "vpiByteTypespec",
        "vpiShortIntTypespec",
        "vpiIntTypespec",
        "vpiClassTypespec",
        "vpiStringTypespec",
        "vpiChandleTypespec",
        "vpiEnumTypespec",
        "vpiEnumConst",
        "vpiIntegerTypespec",
        "vpiTimeTypespec",
        "vpiRealTypespec",
        "vpiStructTypespec",
        "vpiUnionTypespec",
        "vpiBitTypespec",
        "vpiLogicTypespec",
        "vpiArrayTypespec",
        "vpiVoidTypespec",
        "vpiTypespecMember",
        "vpiDistItem",
        "vpiAliasStmt",
        "vpiThread",
        "vpiMethodFuncCall",
        "vpiMethodTaskCall",
        "vpiClockingBlock",
        "vpiClockingIODecl",
        "vpiClassDefn",
        "vpiConstraint",
        "vpiConstraintOrdering",
        "vpiPropertyDecl",
        "vpiPropertySpec",
        "vpiPropertyExpr",
        "vpiMulticlockSequenceExpr",
        "vpiClockedSeq",
        "vpiPropertyInst",
        "vpiSequenceDecl",
        "vpiCaseProperty",
        "*undefined*", // 663 is not defined for object types
        "vpiSequenceInst",
        "vpiImmediateAssert",
        "vpiReturn",
        "vpiAnyPattern",
        "vpiTaggedPattern",
        "vpiStructPattern",
        "vpiDoWhile",
        "vpiOrderedWait",
        "vpiWaitFork",
        "vpiDisableFork",
        "vpiExpectStmt",
        "vpiForeachStmt",
        "vpiFinal",
        "vpiExtends",
        "vpiDistribution",
        "vpiSeqFormalDecl",
        "vpiEnumNet",
        "vpiIntegerNet",
        "vpiTimeNet",
        "vpiStructNet",
        "vpiBreak",
        "vpiContinue",
        "vpiAssert",
        "vpiAssume",
        "vpiCover",
        "vpiDisableCondition",
        "vpiClockingEvent",
        "vpiReturnStmt",
        "vpiPackedArrayTypespec",
        "vpiPackedArrayNet",
        "vpiImmediateAssume",
        "vpiImmediateCover",
        "vpiSequenceTypespec",
        "vpiPropertyTypespec",
        "vpiEventTypespec",
        "vpiPropFormalDecl",
    };
    // clang-format on
    if (VL_UNCOVERABLE(vpiVal < 0))
        return names[0];
    else if (vpiVal <= vpiAutomatics)
        return names[vpiVal];
    else if (vpiVal >= vpiPackage && vpiVal <= vpiPropFormalDecl)
        return sv_names1[(vpiVal - vpiPackage)];
    else
        return names[0];
}
const char* VerilatedVpiError::strFromVpiMethod(PLI_INT32 vpiVal) VL_PURE {
    // clang-format off
    static const char* const names[] = {
        "vpiCondition",
        "vpiDelay",
        "vpiElseStmt",
        "vpiForIncStmt",
        "vpiForInitStmt",
        "vpiHighConn",
        "vpiLhs",
        "vpiIndex",
        "vpiLeftRange",
        "vpiLowConn",
        "vpiParent",
        "vpiRhs",
        "vpiRightRange",
        "vpiScope",
        "vpiSysTfCall",
        "vpiTchkDataTerm",
        "vpiTchkNotifier",
        "vpiTchkRefTerm",
        "vpiArgument",
        "vpiBit",
        "vpiDriver",
        "vpiInternalScope",
        "vpiLoad",
        "vpiModDataPathIn",
        "vpiModPathIn",
        "vpiModPathOut",
        "vpiOperand",
        "vpiPortInst",
        "vpiProcess",
        "vpiVariables",
        "vpiUse",
        "vpiExpr",
        "vpiPrimitive",
        "vpiStmt"
    };
    // clang-format on
    if (vpiVal > vpiStmt || vpiVal < vpiCondition) return "*undefined*";
    return names[vpiVal - vpiCondition];
}

const char* VerilatedVpiError::strFromVpiCallbackReason(PLI_INT32 vpiVal) VL_PURE {
    // clang-format off
    static const char* const names[] = {
        "*undefined*",
        "cbValueChange",
        "cbStmt",
        "cbForce",
        "cbRelease",
        "cbAtStartOfSimTime",
        "cbReadWriteSynch",
        "cbReadOnlySynch",
        "cbNextSimTime",
        "cbAfterDelay",
        "cbEndOfCompile",
        "cbStartOfSimulation",
        "cbEndOfSimulation",
        "cbError",
        "cbTchkViolation",
        "cbStartOfSave",
        "cbEndOfSave",
        "cbStartOfRestart",
        "cbEndOfRestart",
        "cbStartOfReset",
        "cbEndOfReset",
        "cbEnterInteractive",
        "cbExitInteractive",
        "cbInteractiveScopeChange",
        "cbUnresolvedSystf",
        "cbAssign",
        "cbDeassign",
        "cbDisable",
        "cbPLIError",
        "cbSignal",
        "cbNBASynch",
        "cbAtEndOfSimTime"
    };
    // clang-format on
    if (VL_UNCOVERABLE(vpiVal < 0)) return names[0];
    return names[(vpiVal <= cbAtEndOfSimTime) ? vpiVal : 0];
}

const char* VerilatedVpiError::strFromVpiProp(PLI_INT32 vpiVal) VL_PURE {
    // clang-format off
    static const char* const names[] = {
        "*undefined or other*",
        "vpiType",
        "vpiName",
        "vpiFullName",
        "vpiSize",
        "vpiFile",
        "vpiLineNo",
        "vpiTopModule",
        "vpiCellInstance",
        "vpiDefName",
        "vpiProtected",
        "vpiTimeUnit",
        "vpiTimePrecision",
        "vpiDefNetType",
        "vpiUnconnDrive",
        "vpiDefFile",
        "vpiDefLineNo",
        "vpiScalar",
        "vpiVector",
        "vpiExplicitName",
        "vpiDirection",
        "vpiConnByName",
        "vpiNetType",
        "vpiExplicitScalared",
        "vpiExplicitVectored",
        "vpiExpanded",
        "vpiImplicitDecl",
        "vpiChargeStrength",
        "vpiArray",
        "vpiPortIndex",
        "vpiTermIndex",
        "vpiStrength0",
        "vpiStrength1",
        "vpiPrimType",
        "vpiPolarity",
        "vpiDataPolarity",
        "vpiEdge",
        "vpiPathType",
        "vpiTchkType",
        "vpiOpType",
        "vpiConstType",
        "vpiBlocking",
        "vpiCaseType",
        "vpiFuncType",
        "vpiNetDeclAssign",
        "vpiUserDefn",
        "vpiScheduled",
        "*undefined*",
        "*undefined*",
        "vpiActive",
        "vpiAutomatic",
        "vpiCell",
        "vpiConfig",
        "vpiConstantSelect",
        "vpiDecompile",
        "vpiDefAttribute",
        "vpiDelayType",
        "vpiIteratorType",
        "vpiLibrary",
        "*undefined*",
        "vpiOffset",
        "vpiResolvedNetType",
        "vpiSaveRestartID",
        "vpiSaveRestartLocation",
        "vpiValid",
        "vpiSigned",
        "vpiStop",
        "vpiFinish",
        "vpiReset",
        "vpiSetInteractiveScope",
        "vpiLocalParam",
        "vpiModPathHasIfNone",
        "vpiIndexedPartSelectType",
        "vpiIsMemory",
        "vpiIsProtected"
    };
    // clang-format on
    if (vpiVal == vpiUndefined) return "vpiUndefined";
    return names[(vpiVal <= vpiIsProtected) ? vpiVal : 0];
}
const char* VerilatedVpiError::strFromVpiConstType(PLI_INT32 constType) VL_PURE {
    // clang-format off
    static const char* const names[] = {
        "*undefined*",
        "vpiDecConst",
        "vpiRealConst",
        "vpiBinaryConst",
        "vpiOctConst",
        "vpiHexConst",
        "vpiStringConst",
        "vpiIntConst",
        "vpiTimeConst",
    };
    // clang-format on
    if (VL_UNCOVERABLE(constType < 0)) return names[0];
    return names[(constType <= vpiTimeConst) ? constType : 0];
}

#define SELF_CHECK_RESULT_CSTR(got, exp) \
    if (0 != std::strcmp((got), (exp))) { \
        const std::string msg = "%Error: GOT = '"s + (got) + "'" + "  EXP = '" + (exp) + "'"; \
        VL_FATAL_MT(__FILE__, __LINE__, "", msg.c_str()); \
    }

#define SELF_CHECK_ENUM_STR(fn, enumn) \
    do { \
        const char* const strVal = VerilatedVpiError::fn(enumn); \
        SELF_CHECK_RESULT_CSTR(strVal, #enumn); \
    } while (0)

void VerilatedVpi::selfTest() VL_MT_UNSAFE_ONE { VerilatedVpiError::selfTest(); }
void VerilatedVpiError::selfTest() VL_MT_UNSAFE_ONE {
    VerilatedVpiImp::assertOneCheck();

    SELF_CHECK_ENUM_STR(strFromVpiVal, vpiBinStrVal);
    SELF_CHECK_ENUM_STR(strFromVpiVal, vpiRawFourStateVal);

    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiAlways);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiAssignStmt);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiAssignment);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiBegin);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiCase);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiCaseItem);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiConstant);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiContAssign);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDeassign);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDefParam);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDelayControl);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDisable);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiEventControl);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiEventStmt);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiFor);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiForce);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiForever);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiFork);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiFuncCall);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiFunction);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiGate);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiIf);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiIfElse);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiInitial);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiIntegerVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiInterModPath);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiIterator);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiIODecl);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiMemory);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiMemoryWord);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiModPath);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiModule);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiNamedBegin);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiNamedEvent);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiNamedFork);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiNet);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiNetBit);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiNullStmt);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiOperation);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiParamAssign);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiParameter);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPartSelect);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPathTerm);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPort);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPortBit);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPrimTerm);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiRealVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiReg);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiRegBit);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiRelease);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiRepeat);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiRepeatControl);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSchedEvent);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSpecParam);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSwitch);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSysFuncCall);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSysTaskCall);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTableEntry);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTask);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTaskCall);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTchk);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTchkTerm);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTimeVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTimeQueue);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUdp);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUdpDefn);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUserSystf);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiVarSelect);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiWait);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiWhile);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiCondition);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDelay);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiElseStmt);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiForIncStmt);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiForInitStmt);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiHighConn);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiLhs);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiIndex);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiLeftRange);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiLowConn);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiParent);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiRhs);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiRightRange);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiScope);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSysTfCall);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTchkDataTerm);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTchkNotifier);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTchkRefTerm);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiArgument);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiBit);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDriver);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiInternalScope);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiLoad);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiModDataPathIn);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiModPathIn);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiModPathOut);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiOperand);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPortInst);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiProcess);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiVariables);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUse);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiExpr);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPrimitive);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiStmt);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiAttribute);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiBitSelect);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiCallback);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDelayTerm);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDelayDevice);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiFrame);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiGateArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiModuleArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPrimitiveArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiNetArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiRange);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiRegArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSwitchArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUdpArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiActiveTimeFormat);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiInTerm);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiInstanceArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiLocalDriver);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiLocalLoad);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiOutTerm);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPorts);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSimNet);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTaskFunc);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiContAssignBit);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiNamedEventArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiIndexedPartSelect);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiBaseExpr);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiWidthExpr);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiGenScopeArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiGenScope);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiGenVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiAutomatics);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPackage);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiInterface);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiProgram);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiInterfaceArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiProgramArray);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiModport);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiInterfaceTfDecl);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiRefObj);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTypeParameter);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiLongIntVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiShortIntVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiIntVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiShortRealVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiByteVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClassVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiStringVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiEnumVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiStructVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUnionVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiBitVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClassObj);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiChandleVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPackedArrayVar);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiLongIntTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiShortRealTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiByteTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiShortIntTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiIntTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClassTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiStringTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiChandleTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiEnumTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiEnumConst);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiIntegerTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTimeTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiRealTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiStructTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiUnionTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiBitTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiLogicTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiArrayTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiVoidTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTypespecMember);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDistItem);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiAliasStmt);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiThread);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiMethodFuncCall);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiMethodTaskCall);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClockingBlock);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClockingIODecl);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClassDefn);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiConstraint);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiConstraintOrdering);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPropertyDecl);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPropertySpec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPropertyExpr);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiMulticlockSequenceExpr);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClockedSeq);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPropertyInst);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSequenceDecl);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiCaseProperty);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSequenceInst);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiImmediateAssert);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiReturn);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiAnyPattern);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTaggedPattern);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiStructPattern);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDoWhile);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiOrderedWait);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiWaitFork);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDisableFork);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiExpectStmt);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiForeachStmt);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiFinal);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiExtends);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDistribution);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSeqFormalDecl);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiEnumNet);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiIntegerNet);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiTimeNet);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiStructNet);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiBreak);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiContinue);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiAssert);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiAssume);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiCover);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiDisableCondition);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiClockingEvent);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiReturnStmt);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPackedArrayTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPackedArrayNet);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiImmediateAssume);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiImmediateCover);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiSequenceTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPropertyTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiEventTypespec);
    SELF_CHECK_ENUM_STR(strFromVpiObjType, vpiPropFormalDecl);

    SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiCondition);
    SELF_CHECK_ENUM_STR(strFromVpiMethod, vpiStmt);

    SELF_CHECK_ENUM_STR(strFromVpiCallbackReason, cbValueChange);
    SELF_CHECK_ENUM_STR(strFromVpiCallbackReason, cbAtEndOfSimTime);

    SELF_CHECK_ENUM_STR(strFromVpiProp, vpiType);
    SELF_CHECK_ENUM_STR(strFromVpiProp, vpiProtected);
    SELF_CHECK_ENUM_STR(strFromVpiProp, vpiDirection);
    SELF_CHECK_ENUM_STR(strFromVpiProp, vpiTermIndex);
    SELF_CHECK_ENUM_STR(strFromVpiProp, vpiConstType);
    SELF_CHECK_ENUM_STR(strFromVpiProp, vpiAutomatic);
    SELF_CHECK_ENUM_STR(strFromVpiProp, vpiOffset);
    SELF_CHECK_ENUM_STR(strFromVpiProp, vpiStop);
    SELF_CHECK_ENUM_STR(strFromVpiProp, vpiIsProtected);

    SELF_CHECK_ENUM_STR(strFromVpiConstType, vpiDecConst);
    SELF_CHECK_ENUM_STR(strFromVpiConstType, vpiRealConst);
    SELF_CHECK_ENUM_STR(strFromVpiConstType, vpiBinaryConst);
    SELF_CHECK_ENUM_STR(strFromVpiConstType, vpiOctConst);
    SELF_CHECK_ENUM_STR(strFromVpiConstType, vpiHexConst);
    SELF_CHECK_ENUM_STR(strFromVpiConstType, vpiStringConst);
    SELF_CHECK_ENUM_STR(strFromVpiConstType, vpiIntConst);
    SELF_CHECK_ENUM_STR(strFromVpiConstType, vpiTimeConst);
}

#undef SELF_CHECK_ENUM_STR
#undef SELF_CHECK_RESULT_CSTR

//======================================================================
// callback related

vpiHandle vpi_register_cb(p_cb_data cb_data_p) {
    // Returns handle so user can remove the callback, user must vpi_release_handle it
    // Don't confuse with the callback-activated t_cb_data object handle
    // which is the object causing the callback rather than the callback itself
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    // cppcheck-suppress nullPointer
    if (VL_UNLIKELY(!cb_data_p)) {
        VL_VPI_WARNING_(__FILE__, __LINE__, "%s : callback data pointer is null", __func__);
        return nullptr;
    }
    const PLI_INT32 reason = cb_data_p->reason;
    switch (reason) {
    case cbAfterDelay:  // FALLTHRU // One-shot; time relative
    case cbAtEndOfSimTime:  // FALLTHRU // One-shot; time absolute; supported via vlt_main.cpp
    case cbAtStartOfSimTime:  // FALLTHRU // One-shot; time absolute; supported via vlt_main.cpp
    case cbReadOnlySynch:  // FALLTHRU // One-shot; time relative; supported via vlt_main.cpp
    case cbReadWriteSynch: {  // One-shot; time relative; supported via vlt_main.cpp
        const bool abs = reason == cbAtStartOfSimTime || reason == cbAtEndOfSimTime;
        const QData time = VL_TIME_Q();
        QData abstime = 0;
        if (cb_data_p->time) {
            if (abs) {
                abstime = VL_SET_QII(cb_data_p->time->high, cb_data_p->time->low);
            } else {
                abstime = time + VL_SET_QII(cb_data_p->time->high, cb_data_p->time->low);
            }
        }
        const uint64_t id = VerilatedVpiImp::nextCallbackId();
        VerilatedVpioReasonCb* const vop = new VerilatedVpioReasonCb{id, abstime, reason};
        if (abstime <= time) {
            VerilatedVpiImp::cbCurrentAdd(id, cb_data_p);
        } else {
            VerilatedVpiImp::cbFutureAdd(id, cb_data_p, abstime);
        }
        return vop->castVpiHandle();
    }
    case cbNextSimTime: {  // One-shot; time always next; supported via vlt_main.cpp
        const QData time = VL_TIME_Q();
        const uint64_t id = VerilatedVpiImp::nextCallbackId();
        VerilatedVpioReasonCb* const vop = new VerilatedVpioReasonCb{id, 0, reason};
        VerilatedVpiImp::cbNextAdd(id, cb_data_p, time);
        return vop->castVpiHandle();
    }
    case cbEndOfSimulation:  // FALLTHRU // One-shot; time ignored; supported via vlt_main.cpp
    case cbEnterInteractive:  // FALLTHRU // NOP, but need to return handle, so make object
    case cbExitInteractive:  // FALLTHRU // NOP, but need to return handle, so make object
    case cbInteractiveScopeChange:  // FALLTHRU // NOP, but need to return handle, so make object
    case cbPLIError:  // FALLTHRU // NOP, but need to return handle, so make object
    case cbStartOfSimulation:  // FALLTHRU // One-shot; time ignored; supported via vlt_main.cpp
    case cbValueChange: {  // Multi-shot; supported via vlt_main.cpp
        const uint64_t id = VerilatedVpiImp::nextCallbackId();
        VerilatedVpioReasonCb* const vop = new VerilatedVpioReasonCb{id, 0, reason};
        VerilatedVpiImp::cbCurrentAdd(id, cb_data_p);
        return vop->castVpiHandle();
    }
    default:
        VL_VPI_WARNING_(__FILE__, __LINE__, "%s: Unsupported callback type %s", __func__,
                        VerilatedVpiError::strFromVpiCallbackReason(reason));
        return nullptr;
    }
}

PLI_INT32 vpi_remove_cb(vpiHandle cb_obj) {
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_remove_cb %p\n", cb_obj););
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    VerilatedVpio* const vop = VerilatedVpio::castp(cb_obj);
    if (VL_UNLIKELY(!vop)) return 0;
    return vop->dovpi_remove_cb();
}

void vpi_get_cb_info(vpiHandle /*object*/, p_cb_data /*cb_data_p*/) { VL_VPI_UNIMP_(); }
vpiHandle vpi_register_systf(p_vpi_systf_data /*systf_data_p*/) {
    VL_VPI_UNIMP_();
    return nullptr;
}
void vpi_get_systf_info(vpiHandle /*object*/, p_vpi_systf_data /*systf_data_p*/) {
    VL_VPI_UNIMP_();
}

// for obtaining handles

vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) {
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    if (VL_UNLIKELY(!namep)) return nullptr;
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_handle_by_name %s %p\n", namep, scope););
    const VerilatedVar* varp = nullptr;
    const VerilatedScope* scopep;
    const VerilatedVpioScope* const voScopep = VerilatedVpioScope::castp(scope);
    std::string scopeAndName = namep;
    if (0 == std::strncmp(namep, "$root.", std::strlen("$root."))) {
        namep += std::strlen("$root.");
        scopeAndName = namep;
    } else if (voScopep) {
        const bool scopeIsPackage = VerilatedVpioPackage::castp(scope) != nullptr;
        scopeAndName = std::string{voScopep->fullname()} + (scopeIsPackage ? "" : ".") + namep;
        namep = const_cast<PLI_BYTE8*>(scopeAndName.c_str());
    }
    {
        // This doesn't yet follow the hierarchy in the proper way
        bool isPackage = false;
        scopep = Verilated::threadContextp()->scopeFind(namep);
        if (scopep) {  // Whole thing found as a scope
            if (scopep->type() == VerilatedScope::SCOPE_MODULE) {
                return (new VerilatedVpioModule{scopep})->castVpiHandle();
            } else if (scopep->type() == VerilatedScope::SCOPE_PACKAGE) {
                return (new VerilatedVpioPackage{scopep})->castVpiHandle();
            } else {
                return (new VerilatedVpioScope{scopep})->castVpiHandle();
            }
        }
        std::string basename = scopeAndName;
        std::string scopename;
        std::string::size_type prevpos = std::string::npos;
        std::string::size_type pos = std::string::npos;
        // Split hierarchical names at last '.' not inside escaped identifier
        size_t i = 0;
        while (i < scopeAndName.length()) {
            if (scopeAndName[i] == '\\') {
                while (i < scopeAndName.length() && scopeAndName[i] != ' ') ++i;
                ++i;  // Proc ' ', it should always be there. Then grab '.' on next cycle
            } else {
                while (i < scopeAndName.length()
                       && (scopeAndName[i] != '.'
                           && (i + 1 >= scopeAndName.length() || scopeAndName[i] != ':'
                               || scopeAndName[i + 1] != ':')))
                    ++i;
                if (i < scopeAndName.length()) {
                    prevpos = pos;
                    pos = i++;
                    if (scopeAndName[i - 1] == ':') isPackage = true;
                }
            }
        }
        // Do the split
        if (VL_LIKELY(pos != std::string::npos)) {
            basename.erase(0, pos + (isPackage ? 2 : 1));
            scopename = scopeAndName.substr(0, pos);
            if (scopename == "$unit") scopename = "\\$unit ";
        }
        if (prevpos == std::string::npos) {
            // scopename is a toplevel (no '.' separator), so search in our TOP ports first.
            scopep = Verilated::threadContextp()->scopeFind("TOP");
            if (scopep) varp = scopep->varFind(basename.c_str());
        }
        if (!varp) {
            scopep = Verilated::threadContextp()->scopeFind(scopename.c_str());
            if (!scopep) return nullptr;
            varp = scopep->varFind(basename.c_str());
        }
    }
    if (!varp) return nullptr;

    if (varp->isParam()) {
        return (new VerilatedVpioParam{varp, scopep})->castVpiHandle();
    } else {
        return (new VerilatedVpioVar{varp, scopep})->castVpiHandle();
    }
}

vpiHandle vpi_handle_by_index(vpiHandle object, PLI_INT32 indx) {
    // Used to get array entries
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_handle_by_index %p %d\n", object, indx););
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    const VerilatedVpioVar* const varop = VerilatedVpioVar::castp(object);
    if (VL_LIKELY(varop)) {
        // Case: no dimensions left to index
        if (VL_UNLIKELY(varop->indexedDim() + 1 > varop->varp()->dims() - 1)) return nullptr;

        // Case: index out of range
        if (VL_UNLIKELY(indx < varop->rangep()->low() || indx > varop->rangep()->high()))
            return nullptr;

        return varop->withIndex(indx)->castVpiHandle();
    }
    VL_VPI_INTERNAL_(__FILE__, __LINE__, "%s : can't resolve handle", __func__);
    return nullptr;
}

// for traversing relationships

vpiHandle vpi_handle(PLI_INT32 type, vpiHandle object) {
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_handle %d %p\n", type, object););
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    switch (type) {
    case vpiLeftRange: {
        if (const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object)) {
            if (VL_UNLIKELY(!vop->rangep())) return nullptr;
            return (new VerilatedVpioConst{vop->rangep()->left()})->castVpiHandle();
        } else if (const VerilatedVpioRange* const vop = VerilatedVpioRange::castp(object)) {
            if (VL_UNLIKELY(!vop->rangep())) return nullptr;
            return (new VerilatedVpioConst{vop->rangep()->left()})->castVpiHandle();
        }
        VL_VPI_WARNING_(__FILE__, __LINE__,
                        "%s: Unsupported vpiHandle (%p) for type %s, nothing will be returned",
                        __func__, object, VerilatedVpiError::strFromVpiMethod(type));
        return nullptr;
    }
    case vpiRightRange: {
        if (const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object)) {
            if (VL_UNLIKELY(!vop->rangep())) return nullptr;
            return (new VerilatedVpioConst{vop->rangep()->right()})->castVpiHandle();
        } else if (const VerilatedVpioRange* const vop = VerilatedVpioRange::castp(object)) {
            if (VL_UNLIKELY(!vop->rangep())) return nullptr;
            return (new VerilatedVpioConst{vop->rangep()->right()})->castVpiHandle();
        }
        VL_VPI_WARNING_(__FILE__, __LINE__,
                        "%s: Unsupported vpiHandle (%p) for type %s, nothing will be returned",
                        __func__, object, VerilatedVpiError::strFromVpiMethod(type));
        return nullptr;
    }
    case vpiIndex: {
        const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object);
        if (VL_UNLIKELY(!vop)) return nullptr;
        const int32_t val = vop->index().back();
        return (new VerilatedVpioConst{val})->castVpiHandle();
    }
    case vpiScope: {
        const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object);
        if (VL_UNLIKELY(!vop)) return nullptr;
        return (new VerilatedVpioScope{vop->scopep()})->castVpiHandle();
    }
    case vpiParent: {
        const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object);
        if (VL_UNLIKELY(!vop)) return nullptr;
        return (new VerilatedVpioVar{vop->varp(), vop->scopep()})->castVpiHandle();
    }
    default:
        VL_VPI_WARNING_(__FILE__, __LINE__, "%s: Unsupported type %s, nothing will be returned",
                        __func__, VerilatedVpiError::strFromVpiMethod(type));
        return nullptr;
    }
}

vpiHandle vpi_handle_multi(PLI_INT32 /*type*/, vpiHandle /*refHandle1*/, vpiHandle /*refHandle2*/,
                           ...) {
    VL_VPI_UNIMP_();
    return nullptr;
}

vpiHandle vpi_iterate(PLI_INT32 type, vpiHandle object) {
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_iterate %d %p\n", type, object););
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    switch (type) {
    case vpiRange: {
        const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object);
        if (VL_UNLIKELY(!vop)) return nullptr;

        std::vector<VerilatedRange> ranges;
        const int maxDim = vop->maxDim(vop->isIndexedDimUnpacked());
        for (int dim = vop->indexedDim() + 1; dim <= maxDim; ++dim)
            ranges.emplace_back(*vop->varp()->range(dim));

        // allow one more range layer (regbit)
        if (ranges.empty()) ranges.emplace_back(VerilatedRange(0, 0));
        return ((new VerilatedVpioRangeIter{ranges})->castVpiHandle());
    }
    case vpiReg: {
        const VerilatedVpioScope* const vscopep = VerilatedVpioScope::castp(object);
        if (vscopep) return ((new VerilatedVpioVarIter{vscopep, false})->castVpiHandle());
        const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object);
        if (vop) return ((new VerilatedVpioRegIter{vop})->castVpiHandle());
        return nullptr;
    }
    case vpiParameter: {
        const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object);
        if (VL_UNLIKELY(!vop)) return nullptr;
        return ((new VerilatedVpioVarIter{vop, true})->castVpiHandle());
    }
    case vpiModule: {
        const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object);
        const VerilatedHierarchyMap* const map = VerilatedImp::hierarchyMap();
        const VerilatedScope* const modp = vop ? vop->scopep() : nullptr;
        const auto it = vlstd::as_const(map)->find(const_cast<VerilatedScope*>(modp));
        if (it == map->end()) return nullptr;
        return ((new VerilatedVpioModuleIter{it->second})->castVpiHandle());
    }
    case vpiInternalScope: {
        const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object);
        const VerilatedHierarchyMap* const map = VerilatedImp::hierarchyMap();
        const VerilatedScope* const modp = vop ? vop->scopep() : nullptr;
        const auto it = vlstd::as_const(map)->find(const_cast<VerilatedScope*>(modp));
        if (it == map->end()) return nullptr;
        return ((new VerilatedVpioScopeIter{it->second})->castVpiHandle());
    }
    case vpiInstance: {
        if (object) return nullptr;
        const VerilatedHierarchyMap* const map = VerilatedImp::hierarchyMap();
        const auto it = vlstd::as_const(map)->find(nullptr);
        if (it == map->end()) return nullptr;
        return ((new VerilatedVpioInstanceIter{it->second})->castVpiHandle());
    }
    default:
        VL_VPI_WARNING_(__FILE__, __LINE__, "%s: Unsupported type %s, nothing will be returned",
                        __func__, VerilatedVpiError::strFromVpiObjType(type));
        return nullptr;
    }
}
vpiHandle vpi_scan(vpiHandle object) {
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_scan %p\n", object););
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    VerilatedVpio* const vop = VerilatedVpio::castp(object);
    if (VL_UNLIKELY(!vop)) return nullptr;
    return vop->dovpi_scan();
}

// for processing properties

PLI_INT32 vpi_get(PLI_INT32 property, vpiHandle object) {
    // Leave this in the header file - in many cases the compiler can constant propagate "object"
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_get %d %p\n", property, object););
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    switch (property) {
    case vpiTimePrecision: {
        return Verilated::threadContextp()->timeprecision();
    }
    case vpiTimeUnit: {
        const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object);
        if (!vop)
            return Verilated::threadContextp()->timeunit();  // Null asks for global, not unlikely
        return vop->scopep()->timeunit();
    }
    case vpiType: {
        const VerilatedVpio* const vop = VerilatedVpio::castp(object);
        if (VL_UNLIKELY(!vop)) return vpiUndefined;
        return vop->type();
    }
    case vpiConstType: {
        const VerilatedVpio* const vop = VerilatedVpio::castp(object);
        if (VL_UNLIKELY(!vop)) return vpiUndefined;
        return vop->constType();
    }
    case vpiDirection: {
        // By forethought, the directions already are vpi enumerated
        const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object);
        if (VL_UNLIKELY(!vop)) return vpiUndefined;
        return vop->varp()->vldir();
    }
    case vpiScalar:  // FALLTHRU
    case vpiVector: {
        const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object);
        if (VL_UNLIKELY(!vop)) return vpiUndefined;
        return (property == vpiVector) ^ (vop->varp()->packedRanges().empty() || !vop->rangep());
    }
    case vpiSize: {
        const VerilatedVpioVarBase* const vop = VerilatedVpioVarBase::castp(object);
        if (VL_UNLIKELY(!vop)) return vpiUndefined;
        return vop->size();
    }
    default:
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported property %s, nothing will be returned",
                      __func__, VerilatedVpiError::strFromVpiProp(property));
        return vpiUndefined;
    }
}

PLI_INT64 vpi_get64(PLI_INT32 /*property*/, vpiHandle /*object*/) {
    VL_VPI_UNIMP_();
    return vpiUndefined;
}

PLI_BYTE8* vpi_get_str(PLI_INT32 property, vpiHandle object) {
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_get_str %d %p\n", property, object););
    VerilatedVpiImp::assertOneCheck();
    const VerilatedVpio* const vop = VerilatedVpio::castp(object);
    VL_VPI_ERROR_RESET_();
    if (VL_UNLIKELY(!vop)) return nullptr;
    switch (property) {
    case vpiName: {
        return const_cast<PLI_BYTE8*>(vop->name());
    }
    case vpiFullName: {
        return const_cast<PLI_BYTE8*>(vop->fullname());
    }
    case vpiDefName: {
        return const_cast<PLI_BYTE8*>(vop->defname());
    }
    case vpiType: {
        return const_cast<PLI_BYTE8*>(VerilatedVpiError::strFromVpiObjType(vop->type()));
    }
    case vpiConstType: {
        const PLI_INT32 constType = vpi_get(vpiConstType, object);
        VL_VPI_ERROR_RESET_();
        return const_cast<PLI_BYTE8*>(VerilatedVpiError::strFromVpiConstType(constType));
    }
    default:
        VL_VPI_WARNING_(__FILE__, __LINE__, "%s: Unsupported type %s, nothing will be returned",
                        __func__, VerilatedVpiError::strFromVpiProp(property));
        return nullptr;
    }
}

// delay processing

void vpi_get_delays(vpiHandle /*object*/, p_vpi_delay /*delay_p*/) { VL_VPI_UNIMP_(); }
void vpi_put_delays(vpiHandle /*object*/, p_vpi_delay /*delay_p*/) { VL_VPI_UNIMP_(); }

// value processing
bool vl_check_format(const VerilatedVar* varp, const p_vpi_value valuep, const char* fullname,
                     bool isGetValue) {
    bool status = true;
    if ((valuep->format == vpiVectorVal) || (valuep->format == vpiBinStrVal)
        || (valuep->format == vpiOctStrVal) || (valuep->format == vpiHexStrVal)) {
        switch (varp->vltype()) {
        case VLVT_UINT8:
        case VLVT_UINT16:
        case VLVT_UINT32:
        case VLVT_UINT64:
        case VLVT_WDATA: return status;
        default: status = false;
        }
    } else if (valuep->format == vpiDecStrVal) {
        switch (varp->vltype()) {
        case VLVT_UINT8:
        case VLVT_UINT16:
        case VLVT_UINT32:
        case VLVT_UINT64: return status;
        default: status = false;
        }
    } else if (valuep->format == vpiStringVal) {
        switch (varp->vltype()) {
        case VLVT_UINT8:
        case VLVT_UINT16:
        case VLVT_UINT32:
        case VLVT_UINT64:
        case VLVT_WDATA: return status;
        case VLVT_STRING:
            // string parameter values can't be changed
            if (isGetValue || !varp->isParam()) {
                return status;
            } else {
                status = false;
                break;
            }
        default: status = false;
        }
    } else if (valuep->format == vpiIntVal) {
        switch (varp->vltype()) {
        case VLVT_UINT8:
        case VLVT_UINT16:
        case VLVT_UINT32:
        case VLVT_UINT64:
        case VLVT_WDATA: return status;
        default: status = false;
        }
    } else if (valuep->format == vpiRealVal) {
        switch (varp->vltype()) {
        case VLVT_REAL: return status;
        default: status = false;
        }
    } else if (valuep->format == vpiSuppressVal) {
        return status;
    } else {
        status = false;
    }
    VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported format (%s) for %s", __func__,
                  VerilatedVpiError::strFromVpiVal(valuep->format), fullname);
    return status;
}

static void vl_strprintf(std::string& buffer, char const* fmt, ...) {
    va_list args, args_copy;
    va_start(args, fmt);
    buffer.clear();
    // Make copy of args since we may need to call VL_VSNPRINTF more than once
    va_copy(args_copy, args);
    // Try VL_VSNPRINTF in existing buffer
    const int result
        = VL_VSNPRINTF(const_cast<char*>(buffer.data()), buffer.capacity(), fmt, args_copy);
    va_end(args_copy);
    const int required = result + 1;  // Returned size doesn't include NUL terminator
    // If there wasn't enough space, reallocate and try again
    if (buffer.capacity() < required) {
        buffer.reserve(required * 2);
        VL_VSNPRINTF(const_cast<char*>(buffer.data()), buffer.capacity(), fmt, args);
    }
    va_end(args);
}

// Information about how to access packed array data.
// If underlying type is multi-word (VLVT_WDATA), the packed element might straddle word
// boundaries, in which case m_maskHi != 0.
template <typename T>
struct VarAccessInfo final {
    T* m_datap;  // Typed pointer to packed array base address
    size_t m_bitOffset;  // Data start location (bit offset)
    size_t m_wordOffset;  // Data start location (word offset, VLVT_WDATA only)
    T m_maskLo;  // Access mask for m_datap[m_wordOffset]
    T m_maskHi;  // Access mask for m_datap[m_wordOffset + 1] (VLVT_WDATA only)
};

template <typename T>
VarAccessInfo<T> vl_vpi_var_access_info(const VerilatedVpioVarBase* vop, size_t bitCount,
                                        size_t addOffset) {
    // VarAccessInfo generation
    // vop - variable to access (already indexed)
    // bitCount - how many bits to write/read
    // addOffset - additional offset to apply (within the packed array element)

    const size_t wordBits = sizeof(T) * 8;
    uint32_t varBits = vop->bitSize();

    if (vop->varp()->vltype() == VLVT_REAL) varBits *= sizeof(double) * 8;

    // make sure we're not trying to write outside var bounds
    assert(varBits > addOffset);
    bitCount = std::min(bitCount, varBits - addOffset);

    VarAccessInfo<T> info;
    info.m_datap = reinterpret_cast<T*>(vop->varDatap());
    if (vop->varp()->vltype() == VLVT_WDATA) {
        assert(sizeof(T) == sizeof(EData));
        assert(bitCount <= wordBits);
        info.m_wordOffset = (vop->bitOffset() + addOffset) / wordBits;
        info.m_bitOffset = (vop->bitOffset() + addOffset) % wordBits;
        if (bitCount + info.m_bitOffset <= wordBits) {
            // within single word
            if (bitCount == wordBits)
                info.m_maskLo = ~static_cast<T>(0);
            else
                info.m_maskLo = (static_cast<T>(1) << bitCount) - 1;
            info.m_maskLo = info.m_maskLo << info.m_bitOffset;
            info.m_maskHi = 0;
        } else {
            // straddles word boundary
            info.m_maskLo = (static_cast<T>(1) << (wordBits - info.m_bitOffset)) - 1;
            info.m_maskLo = info.m_maskLo << info.m_bitOffset;
            info.m_maskHi = (static_cast<T>(1) << (bitCount + info.m_bitOffset - wordBits)) - 1;
        }
    } else {
        info.m_wordOffset = 0;
        info.m_bitOffset = vop->bitOffset() + addOffset;
        assert(bitCount + info.m_bitOffset <= wordBits);
        if (bitCount < wordBits) {
            info.m_maskLo = (static_cast<T>(1) << bitCount) - 1;
            info.m_maskLo = info.m_maskLo << info.m_bitOffset;
        } else {
            info.m_maskLo = ~static_cast<T>(0);
        }
        info.m_maskHi = 0;
    }
    return info;
}

template <typename T>
T vl_vpi_get_word_gen(const VerilatedVpioVarBase* vop, size_t bitCount, size_t addOffset) {
    const size_t wordBits = sizeof(T) * 8;
    const VarAccessInfo<T> info = vl_vpi_var_access_info<T>(vop, bitCount, addOffset);
    if (info.m_maskHi)
        return ((info.m_datap[info.m_wordOffset] & info.m_maskLo) >> info.m_bitOffset)
               | ((info.m_datap[info.m_wordOffset + 1] & info.m_maskHi)
                  << (wordBits - info.m_bitOffset));
    else
        return (info.m_datap[info.m_wordOffset] & info.m_maskLo) >> info.m_bitOffset;
}

template <typename T>
void vl_vpi_put_word_gen(const VerilatedVpioVar* vop, T word, size_t bitCount, size_t addOffset) {
    const size_t wordBits = sizeof(T) * 8;
    const VarAccessInfo<T> info = vl_vpi_var_access_info<T>(vop, bitCount, addOffset);

    if (info.m_maskHi) {
        info.m_datap[info.m_wordOffset + 1]
            = (info.m_datap[info.m_wordOffset + 1] & ~info.m_maskHi)
              | ((word >> (wordBits - info.m_bitOffset)) & info.m_maskHi);
    }
    // cppcheck-suppress unreadVariable
    info.m_datap[info.m_wordOffset] = (info.m_datap[info.m_wordOffset] & ~info.m_maskLo)
                                      | ((word << info.m_bitOffset) & info.m_maskLo);
}

// bitCount: maximum number of bits to read, will stop earlier if it reaches the var bounds
// addOffset: additional read bitoffset
QData vl_vpi_get_word(const VerilatedVpioVarBase* vop, size_t bitCount, size_t addOffset) {
    switch (vop->varp()->vltype()) {
    case VLVT_UINT8: return vl_vpi_get_word_gen<CData>(vop, bitCount, addOffset);
    case VLVT_UINT16: return vl_vpi_get_word_gen<SData>(vop, bitCount, addOffset);
    case VLVT_UINT32: return vl_vpi_get_word_gen<IData>(vop, bitCount, addOffset);
    case VLVT_UINT64: return vl_vpi_get_word_gen<QData>(vop, bitCount, addOffset);
    case VLVT_WDATA: return vl_vpi_get_word_gen<EData>(vop, bitCount, addOffset);
    default:
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vltype (%d)", __func__,
                      vop->varp()->vltype());
        return 0;
    }
}

// word: data to be written
// bitCount: maximum number of bits to write, will stop earlier if it reaches the var bounds
// addOffset: additional write bitoffset
void vl_vpi_put_word(const VerilatedVpioVar* vop, QData word, size_t bitCount, size_t addOffset) {
    switch (vop->varp()->vltype()) {
    case VLVT_UINT8: vl_vpi_put_word_gen<CData>(vop, word, bitCount, addOffset); break;
    case VLVT_UINT16: vl_vpi_put_word_gen<SData>(vop, word, bitCount, addOffset); break;
    case VLVT_UINT32: vl_vpi_put_word_gen<IData>(vop, word, bitCount, addOffset); break;
    case VLVT_UINT64: vl_vpi_put_word_gen<QData>(vop, word, bitCount, addOffset); break;
    case VLVT_WDATA: vl_vpi_put_word_gen<EData>(vop, word, bitCount, addOffset); break;
    default:
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vltype (%d)", __func__,
                      vop->varp()->vltype());
    }
}

void vl_vpi_get_value(const VerilatedVpioVarBase* vop, p_vpi_value valuep) {
    const VerilatedVar* const varp = vop->varp();
    void* const varDatap = vop->varDatap();
    const char* fullname = vop->fullname();

    if (!vl_check_format(varp, valuep, fullname, true)) return;
    // string data type is dynamic and may vary in size during simulation
    static thread_local std::string t_outDynamicStr;

    const int varBits = vop->bitSize();

    // 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) {
        // Vector pointer must come from our memory pool
        // It only needs to persist until the next vpi_get_value
        static thread_local t_vpi_vecval t_out[VL_VALUE_STRING_MAX_WORDS * 2];
        valuep->value.vector = t_out;
        if (varp->vltype() == VLVT_WDATA) {
            const int words = VL_WORDS_I(varBits);
            if (VL_UNCOVERABLE(words >= VL_VALUE_STRING_MAX_WORDS)) {
                VL_VPI_ERROR_(
                    __FILE__, __LINE__,
                    "vpi_get_value with more than VL_VALUE_STRING_MAX_WORDS; increase and "
                    "recompile");
                return;
            }
            for (int i = 0; i < words; ++i) {
                t_out[i].aval = vl_vpi_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);
            t_out[1].aval = static_cast<IData>(data >> 32ULL);
            t_out[1].bval = 0;
            t_out[0].aval = static_cast<IData>(data);
            t_out[0].bval = 0;
            return;
        } else {
            t_out[0].aval = vl_vpi_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<CData*>(varDatap);
        for (size_t i = 0; i < varBits; ++i) {
            const size_t pos = i + vop->bitOffset();
            const char val = (datap[pos >> 3] >> (pos & 7)) & 1;
            t_outDynamicStr[varBits - i - 1] = val ? '1' : '0';
        }
        valuep->value.str = const_cast<PLI_BYTE8*>(t_outDynamicStr.c_str());
        return;
    } else if (valuep->format == vpiOctStrVal) {
        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);
            t_outDynamicStr[chars - i - 1] = '0' + val;
        }
        valuep->value.str = const_cast<PLI_BYTE8*>(t_outDynamicStr.c_str());
        return;
    } else if (valuep->format == vpiDecStrVal) {
        if (varp->vltype() == VLVT_UINT8) {
            vl_strprintf(t_outDynamicStr, "%hhu",
                         static_cast<unsigned char>(vl_vpi_get_word(vop, 8, 0)));
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_strprintf(t_outDynamicStr, "%hu",
                         static_cast<unsigned short>(vl_vpi_get_word(vop, 16, 0)));
        } else if (varp->vltype() == VLVT_UINT32) {
            vl_strprintf(t_outDynamicStr, "%u",
                         static_cast<unsigned int>(vl_vpi_get_word(vop, 32, 0)));
        } else if (varp->vltype() == VLVT_UINT64) {
            vl_strprintf(t_outDynamicStr, "%llu",  // lintok-format-ll
                         static_cast<unsigned long long>(vl_vpi_get_word(vop, 64, 0)));
        }
        valuep->value.str = const_cast<PLI_BYTE8*>(t_outDynamicStr.c_str());
        return;
    } else if (valuep->format == vpiHexStrVal) {
        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);
            t_outDynamicStr[chars - i - 1] = "0123456789abcdef"[static_cast<int>(val)];
        }
        valuep->value.str = const_cast<PLI_BYTE8*>(t_outDynamicStr.c_str());
        return;
    } else if (valuep->format == vpiStringVal) {
        if (varp->vltype() == VLVT_STRING) {
            if (varp->isParam()) {
                valuep->value.str = reinterpret_cast<char*>(varDatap);
                return;
            } else {
                t_outDynamicStr = *(vop->varStringDatap());
                valuep->value.str = const_cast<char*>(t_outDynamicStr.c_str());
                return;
            }
        } else {
            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);
                // other simulators replace [leading?] zero chars with spaces, replicate here.
                t_outDynamicStr[chars - i - 1] = val ? val : ' ';
            }
            valuep->value.str = const_cast<PLI_BYTE8*>(t_outDynamicStr.c_str());
            return;
        }
    } else if (valuep->format == vpiIntVal) {
        valuep->value.integer = vl_vpi_get_word(vop, 32, 0);
        return;
    } else if (valuep->format == vpiRealVal) {
        valuep->value.real = *(vop->varRealDatap());
        return;
    } else if (valuep->format == vpiSuppressVal) {
        return;
    }
    VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported format (%s) as requested for %s", __func__,
                  VerilatedVpiError::strFromVpiVal(valuep->format), fullname);
}

void vpi_get_value(vpiHandle object, p_vpi_value valuep) {
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_get_value %p\n", object););
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    if (VL_UNLIKELY(!valuep)) return;

    if (const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object)) {
        vl_vpi_get_value(vop, valuep);
        return;
    } else if (const VerilatedVpioParam* const vop = VerilatedVpioParam::castp(object)) {
        vl_vpi_get_value(vop, valuep);
        return;
    } else if (const VerilatedVpioConst* const vop = VerilatedVpioConst::castp(object)) {
        if (valuep->format == vpiIntVal) {
            valuep->value.integer = vop->num();
            return;
        }
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported format (%s) for %s", __func__,
                      VerilatedVpiError::strFromVpiVal(valuep->format), vop->fullname());
        return;
    }
    VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle (%p)", __func__, object);
}

vpiHandle vpi_put_value(vpiHandle object, p_vpi_value valuep, p_vpi_time /*time_p*/,
                        PLI_INT32 flags) {
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_put_value %p %p\n", object, valuep););
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    if (VL_UNLIKELY(!valuep)) {
        VL_VPI_WARNING_(__FILE__, __LINE__, "Ignoring vpi_put_value with nullptr value pointer");
        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()););

        if (VL_UNLIKELY(!vop->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());
            return nullptr;
        }
        if (!vl_check_format(vop->varp(), valuep, vop->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());
                return nullptr;
            }
            VerilatedVpiImp::inertialDelay(vop, valuep);
            return object;
        }
        VerilatedVpiImp::evalNeeded(true);
        const int varBits = vop->bitSize();
        if (valuep->format == vpiVectorVal) {
            if (VL_UNLIKELY(!valuep->value.vector)) return nullptr;
            if (vop->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);
                return object;
            } else if (vop->varp()->vltype() == VLVT_UINT64 && varBits > 32) {
                const QData val = (static_cast<QData>(valuep->value.vector[1].aval) << 32)
                                  | static_cast<QData>(valuep->value.vector[0].aval);
                vl_vpi_put_word(vop, val, 64, 0);
                return object;
            } else {
                vl_vpi_put_word(vop, 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<CData*>(vop->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;

                if (set)
                    datap[pos >> 3] |= 1 << (pos & 7);
                else
                    datap[pos >> 3] &= ~(1 << (pos & 7));
            }
            return object;
        } else if (valuep->format == vpiOctStrVal) {
            const int len = std::strlen(valuep->value.str);
            for (int i = 0; i < len; ++i) {
                unsigned char digit = valuep->value.str[len - i - 1] - '0';
                if (digit > 7) {  // If str was < '0', then as unsigned, digit > 7
                    VL_VPI_WARNING_(__FILE__, __LINE__,
                                    "%s: Non octal character '%c' in '%s' as value %s for %s",
                                    __func__, digit + '0', valuep->value.str,
                                    VerilatedVpiError::strFromVpiVal(valuep->format),
                                    vop->fullname());
                    digit = 0;
                }
                vl_vpi_put_word(vop, digit, 3, i * 3);
            }
            return object;
        } else if (valuep->format == vpiDecStrVal) {
            char remainder[16];
            unsigned long long val;
            const int success = std::sscanf(valuep->value.str, "%30llu%15s",  // lintok-format-ll
                                            &val, remainder);
            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());
                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_put_word(vop, val, 64, 0);
            return object;
        } else if (valuep->format == vpiHexStrVal) {
            const int chars = (varBits + 3) >> 2;
            const char* val = valuep->value.str;
            // skip hex ident if one is detected at the start of the string
            if (val[0] == '0' && (val[1] == 'x' || val[1] == 'X')) val += 2;
            const int len = std::strlen(val);
            for (int i = 0; i < chars; ++i) {
                char hex;
                // compute hex digit value
                if (i < len) {
                    const char digit = val[len - i - 1];
                    if (digit >= '0' && digit <= '9') {
                        hex = digit - '0';
                    } else if (digit >= 'a' && digit <= 'f') {
                        hex = digit - 'a' + 10;
                    } else if (digit >= 'A' && digit <= 'F') {
                        hex = digit - 'A' + 10;
                    } else {
                        VL_VPI_WARNING_(__FILE__, __LINE__,
                                        "%s: Non hex character '%c' in '%s' as value %s for %s",
                                        __func__, digit, valuep->value.str,
                                        VerilatedVpiError::strFromVpiVal(valuep->format),
                                        vop->fullname());
                        hex = 0;
                    }
                } else {
                    hex = 0;
                }
                // assign hex digit value to destination
                vl_vpi_put_word(vop, hex, 4, i * 4);
            }
            return object;
        } else if (valuep->format == vpiStringVal) {
            if (vop->varp()->vltype() == VLVT_STRING) {
                *(vop->varStringDatap()) = valuep->value.str;
                return object;
            } else {
                const int chars = VL_BYTES_I(varBits);
                const int len = std::strlen(valuep->value.str);
                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);
                }
            }
            return object;
        } else if (valuep->format == vpiIntVal) {
            vl_vpi_put_word(vop, valuep->value.integer, 64, 0);
            return object;
        } else if (valuep->format == vpiRealVal) {
            if (vop->varp()->vltype() == VLVT_REAL) {
                *(vop->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());
        return nullptr;
    } else if (const VerilatedVpioParam* const vop = VerilatedVpioParam::castp(object)) {
        VL_VPI_WARNING_(__FILE__, __LINE__, "%s: Ignoring vpi_put_value to vpiParameter: %s",
                        __func__, vop->fullname());
        return nullptr;
    } else if (const VerilatedVpioConst* const vop = VerilatedVpioConst::castp(object)) {
        VL_VPI_WARNING_(__FILE__, __LINE__, "%s: Ignoring vpi_put_value to vpiConstant: %s",
                        __func__, vop->fullname());
        return nullptr;
    }
    VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle (%p)", __func__, object);
    return nullptr;
}

bool vl_check_array_format(const VerilatedVar* varp, const p_vpi_arrayvalue arrayvalue_p,
                           const char* fullname) {
    if (arrayvalue_p->format == vpiVectorVal) {
        switch (varp->vltype()) {
        case VLVT_UINT8:
        case VLVT_UINT16:
        case VLVT_UINT32:
        case VLVT_UINT64:
        case VLVT_WDATA: return true;
        default:;
        }
    } else if (arrayvalue_p->format == vpiIntVal) {
        switch (varp->vltype()) {
        case VLVT_UINT8:
        case VLVT_UINT16:
        case VLVT_UINT32: return true;
        default:;
        }
    } else if ((arrayvalue_p->format == vpiRawTwoStateVal)
               || (arrayvalue_p->format == vpiRawFourStateVal)) {
        switch (varp->vltype()) {
        case VLVT_UINT8:
        case VLVT_UINT16:
        case VLVT_UINT32:
        case VLVT_UINT64:
        case VLVT_WDATA: return true;
        default:;
        }
    } else if (arrayvalue_p->format == vpiShortIntVal) {
        switch (varp->vltype()) {
        case VLVT_UINT8:
        case VLVT_UINT16: return true;
        default:;
        }
    } else if (arrayvalue_p->format == vpiLongIntVal) {
        switch (varp->vltype()) {
        case VLVT_UINT8:
        case VLVT_UINT16:
        case VLVT_UINT32:
        case VLVT_UINT64: return true;
        default:;
        }
    }

    VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported format (%s) as requested for %s", __func__,
                  VerilatedVpiError::strFromVpiVal(arrayvalue_p->format), fullname);

    return false;
}

template <typename T, typename K>
void vl_get_value_array_integrals(unsigned index, const unsigned num, const unsigned size,
                                  const unsigned packedSize, const bool leftIsLow, const T* src,
                                  K* dst) {
    static_assert(sizeof(K) >= sizeof(T), "size of type K is less than size of type T");
    for (int i = 0; i < num; ++i) {
        dst[i] = src[index];
        index = leftIsLow    ? index == (size - 1) ? 0 : index + 1
                : index == 0 ? size - 1
                             : index - 1;
    }
}

template <typename T, typename K>
void vl_put_value_array_integrals(unsigned index, const unsigned num, const unsigned size,
                                  const unsigned packedSize, const bool leftIsLow, const T* src,
                                  K* dst) {
    static_assert(std::is_integral<T>::value, "type T is not an integral type");
    static_assert(std::is_unsigned<T>::value, "type T is not unsigned");
    static_assert(sizeof(T) >= sizeof(K), "size of type T is less than size of type K");
    const unsigned element_size_bytes = VL_BYTES_I(packedSize);
    const T mask = element_size_bytes == sizeof(T)
                       ? static_cast<T>(-1)
                       : ~(static_cast<T>(-1) << (element_size_bytes * 8));
    for (unsigned i = 0; i < num; ++i) {
        dst[index] = src[i] & static_cast<T>(mask);
        index = leftIsLow    ? index == (size - 1) ? 0 : index + 1
                : index == 0 ? size - 1
                             : index - 1;
    }
}

template <typename T>
void vl_get_value_array_vectors(unsigned index, const unsigned num, const unsigned size,
                                const unsigned packedSize, const bool leftIsLow, const T* src,
                                p_vpi_vecval dst) {
    static_assert(std::is_unsigned<T>::value,
                  "type T is not unsigned");  // ensure logical right shift
    const unsigned element_size_words = VL_WORDS_I(packedSize);
    if (sizeof(T) == sizeof(QData)) {
        for (unsigned i = 0; i < num; ++i) {
            dst[i * 2].aval = static_cast<QData>(src[index]);
            dst[i * 2].bval = 0;
            dst[(i * 2) + 1].aval = static_cast<QData>(src[index]) >> 32;
            dst[(i * 2) + 1].bval = 0;
            index = leftIsLow    ? index == (size - 1) ? 0 : index + 1
                    : index == 0 ? size - 1
                                 : index - 1;
        }
    } else {
        for (unsigned i = 0; i < num; ++i) {
            const size_t dst_index = i * element_size_words;
            const size_t src_index = index * element_size_words;
            for (unsigned j = 0; j < element_size_words; ++j) {
                dst[dst_index + j].aval = src[src_index + j];
                dst[dst_index + j].bval = 0;
            }
            index = leftIsLow    ? index == (size - 1) ? 0 : index + 1
                    : index == 0 ? size - 1
                                 : index - 1;
        }
    }
}

template <typename T>
void vl_put_value_array_vectors(unsigned index, const unsigned num, const unsigned size,
                                const unsigned packedSize, const bool leftIsLow,
                                const bool fourState, const p_vpi_vecval src, T* dst) {
    static_assert(std::is_unsigned<T>::value, "type T is not unsigned");
    static_assert(std::is_integral<T>::value, "type T is not an integral type");
    const unsigned element_size_bytes VL_BYTES_I(packedSize);
    const unsigned element_size_words VL_WORDS_I(packedSize);
    if (sizeof(T) == sizeof(QData)) {  //destination is QDATA
        const QData mask = element_size_bytes == sizeof(T)
                               ? static_cast<QData>(-1)
                               : ~(static_cast<QData>(-1) << (element_size_bytes * 8));
        for (unsigned i = 0; i < num; ++i) {
            dst[index] = src[i * 2].aval;
            dst[index]
                |= (static_cast<QData>(src[(i * 2) + 1].aval) << (sizeof(PLI_UINT32) * 8)) & mask;
            index = leftIsLow    ? index == (size - 1) ? 0 : index + 1
                    : index == 0 ? size - 1
                                 : index - 1;
        }
    } else {
        for (unsigned i = 0; i < num; ++i) {
            unsigned bytes_stored = 0;
            for (unsigned j = 0; j < element_size_words; ++j) {
                if (bytes_stored >= element_size_bytes) break;
                const T mask
                    = (element_size_bytes - bytes_stored) >= sizeof(PLI_UINT32)
                          ? static_cast<T>(-1)
                          : ~(static_cast<T>(-1) << ((element_size_bytes - bytes_stored) * 8));
                dst[(index * element_size_words) + j]
                    = static_cast<T>(src[(i * element_size_words) + j].aval) & mask;
                bytes_stored += sizeof(PLI_UINT32);
            }
            index = leftIsLow    ? index == (size - 1) ? 0 : index + 1
                    : index == 0 ? size - 1
                                 : index - 1;
        }
    }
}

template <typename T>
void vl_get_value_array_rawvals(unsigned index, unsigned num, const unsigned size,
                                const unsigned packedSize, const bool leftIsLow,
                                const bool fourState, const T* src, PLI_BYTE8* dst) {
    static_assert(std::is_unsigned<T>::value,
                  "type T is not unsigned");  //ensure logical right shift
    const unsigned element_size_bytes VL_BYTES_I(packedSize);
    const unsigned element_size_repr = (element_size_bytes + sizeof(T) - 1) / sizeof(T);
    size_t dst_index = 0;
    while (num-- > 0) {
        const size_t src_offset = index * element_size_repr;
        unsigned bytes_copied = 0;
        for (unsigned j = 0; j < element_size_repr; ++j) {
            const T& src_data = src[src_offset + j];
            for (unsigned k = 0; k < sizeof(T); ++k) {
                if (bytes_copied++ == element_size_bytes) break;
                dst[dst_index++] = src_data >> (k * 8);
            }
        }
        if (fourState) {
            std::fill(dst + dst_index, dst + dst_index + element_size_bytes, 0);
            dst_index += element_size_bytes;
        }
        index = leftIsLow    ? index == (size - 1) ? 0 : index + 1
                : index == 0 ? size - 1
                             : index - 1;
    }
}

template <typename T>
void vl_put_value_array_rawvals(unsigned index, const unsigned num, const unsigned size,
                                const unsigned packedSize, const bool leftIsLow,
                                const bool fourState, const PLI_UBYTE8* src, T* dst) {
    const unsigned element_size_bytes VL_BYTES_I(packedSize);
    const unsigned element_size_repr = (element_size_bytes + sizeof(T) - 1) / sizeof(T);
    for (unsigned i = 0; i < num; ++i) {
        unsigned bytes_copied = 0;
        const size_t dst_offset = index * element_size_repr;
        const size_t src_offset = i * element_size_bytes;
        for (unsigned j = 0; j < element_size_repr; ++j) {
            T& dst_data = dst[dst_offset + j];
            for (unsigned k = 0; k < sizeof(T); ++k) {
                if (bytes_copied == element_size_bytes) break;
                const unsigned src_index
                    = fourState ? (src_offset * 2) + bytes_copied : (src_offset) + bytes_copied;
                dst_data &= ~((static_cast<T>(0xFF) & 0xFF) << (k * 8));
                dst_data |= ((static_cast<T>(src[src_index]) & 0xFF) << (k * 8));
                bytes_copied++;
            }
        }
        index = leftIsLow    ? index == (size - 1) ? 0 : index + 1
                : index == 0 ? size - 1
                             : index - 1;
    }
}

void vl_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const PLI_INT32* index_p,
                        PLI_UINT32 num) {
    const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object);
    if (!vl_check_array_format(vop->varp(), arrayvalue_p, vop->fullname())) return;

    const VerilatedVar* const varp = vop->varp();

    static thread_local EData out_data[VL_VALUE_STRING_MAX_WORDS * 2];

    const unsigned size = vop->size();
    if (VL_UNCOVERABLE(num > size)) {
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: requested elements (%u) exceed array size (%u)",
                      __func__, num, size);
        return;
    }

    const bool leftIsLow = vop->rangep()->left() == vop->rangep()->low();
    const int index
        = leftIsLow ? index_p[0] - vop->rangep()->left() : vop->rangep()->left() - index_p[0];

    if (arrayvalue_p->format == vpiShortIntVal) {
        if (VL_UNCOVERABLE((sizeof(PLI_INT16) * num) >= VL_VALUE_STRING_MAX_CHARS)) {
            VL_FATAL_MT(__FILE__, __LINE__, "",
                        "vpi_get_value_array with more than VL_VALUE_STRING_MAX_WORDS; "
                        "increase and recompile");
        }

        PLI_INT16* shortintsp = reinterpret_cast<PLI_INT16*>(out_data);
        arrayvalue_p->value.shortints = shortintsp;

        if (varp->vltype() == VLVT_UINT8) {
            vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
                                         vop->varCDatap(), shortintsp);
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
                                         vop->varSDatap(), shortintsp);
        }

        return;
    } else if (arrayvalue_p->format == vpiIntVal) {
        if (VL_UNCOVERABLE(num >= VL_VALUE_STRING_MAX_WORDS)) {
            VL_FATAL_MT(__FILE__, __LINE__, "",
                        "vpi_get_value_array with more than VL_VALUE_STRING_MAX_WORDS; "
                        "increase and recompile");
        }

        PLI_INT32* integersp = reinterpret_cast<PLI_INT32*>(out_data);
        arrayvalue_p->value.integers = integersp;

        if (varp->vltype() == VLVT_UINT8) {
            vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
                                         vop->varCDatap(), integersp);
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
                                         vop->varSDatap(), integersp);
        } else if (varp->vltype() == VLVT_UINT32) {
            vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
                                         vop->varIDatap(), integersp);
        }

        return;
    } else if (arrayvalue_p->format == vpiLongIntVal) {
        if (VL_UNCOVERABLE((sizeof(PLI_INT64) * num) >= VL_VALUE_STRING_MAX_CHARS)) {
            VL_FATAL_MT(__FILE__, __LINE__, "",
                        "vpi_get_value_array with more than VL_VALUE_STRING_MAX_WORDS; "
                        "increase and recompile");
        }

        PLI_INT64* longintsp = reinterpret_cast<PLI_INT64*>(out_data);
        arrayvalue_p->value.longints = longintsp;

        if (varp->vltype() == VLVT_UINT8) {
            vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
                                         vop->varCDatap(), longintsp);
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
                                         vop->varSDatap(), longintsp);
        } else if (varp->vltype() == VLVT_UINT32) {
            vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
                                         vop->varIDatap(), longintsp);
        } else if (varp->vltype() == VLVT_UINT64) {
            vl_get_value_array_integrals(index, num, size, varp->entBits(), leftIsLow,
                                         vop->varQDatap(), longintsp);
        }

        return;
    } else if (arrayvalue_p->format == vpiVectorVal) {
        if (VL_UNCOVERABLE((VL_WORDS_I(varp->entBits()) * 2 * num) >= VL_VALUE_STRING_MAX_WORDS)) {
            VL_FATAL_MT(__FILE__, __LINE__, "",
                        "vpi_get_value_array with more than VL_VALUE_STRING_MAX_WORDS; "
                        "increase and recompile");
        }

        p_vpi_vecval vectorsp = reinterpret_cast<p_vpi_vecval>(out_data);
        arrayvalue_p->value.vectors = vectorsp;

        if (varp->vltype() == VLVT_UINT8) {
            vl_get_value_array_vectors(index, num, size, varp->entBits(), leftIsLow,
                                       vop->varCDatap(), vectorsp);
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_get_value_array_vectors(index, num, size, varp->entBits(), leftIsLow,
                                       vop->varSDatap(), vectorsp);
        } else if (varp->vltype() == VLVT_UINT32) {
            vl_get_value_array_vectors(index, num, size, varp->entBits(), leftIsLow,
                                       vop->varIDatap(), vectorsp);
        } else if (varp->vltype() == VLVT_UINT64) {
            vl_get_value_array_vectors(index, num, size, varp->entBits(), leftIsLow,
                                       vop->varQDatap(), vectorsp);
        } else if (varp->vltype() == VLVT_WDATA) {
            vl_get_value_array_vectors(index, num, size, varp->entBits(), leftIsLow,
                                       vop->varEDatap(), vectorsp);
        }

        return;
    } else if (arrayvalue_p->format == vpiRawFourStateVal) {
        if (VL_UNCOVERABLE((VL_BYTES_I(varp->entBits()) * 2 * num) >= VL_VALUE_STRING_MAX_CHARS)) {
            VL_FATAL_MT(__FILE__, __LINE__, "",
                        "vpi_get_value_array with more than VL_VALUE_STRING_MAX_WORDS; "
                        "increase and recompile");
        }

        PLI_BYTE8* valuep = reinterpret_cast<PLI_BYTE8*>(out_data);
        arrayvalue_p->value.rawvals = valuep;

        if (varp->vltype() == VLVT_UINT8) {
            vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true,
                                       vop->varCDatap(), valuep);
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true,
                                       vop->varSDatap(), valuep);
        } else if (varp->vltype() == VLVT_UINT32) {
            vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true,
                                       vop->varIDatap(), valuep);
        } else if (varp->vltype() == VLVT_UINT64) {
            vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true,
                                       vop->varQDatap(), valuep);
        } else if (varp->vltype() == VLVT_WDATA) {
            vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true,
                                       vop->varEDatap(), valuep);
        }

        return;
    } else if (arrayvalue_p->format == vpiRawTwoStateVal) {
        if (VL_UNCOVERABLE((VL_BYTES_I(varp->entBits()) * num) >= VL_VALUE_STRING_MAX_CHARS)) {
            VL_FATAL_MT(__FILE__, __LINE__, "",
                        "vpi_get_value_array with more than VL_VALUE_STRING_MAX_WORDS; "
                        "increase and recompile");
        }

        PLI_BYTE8* valuep = reinterpret_cast<PLI_BYTE8*>(out_data);
        arrayvalue_p->value.rawvals = valuep;

        if (varp->vltype() == VLVT_UINT8) {
            vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false,
                                       vop->varCDatap(), valuep);
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false,
                                       vop->varSDatap(), valuep);
        } else if (varp->vltype() == VLVT_UINT32) {
            vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false,
                                       vop->varIDatap(), valuep);
        } else if (varp->vltype() == VLVT_UINT64) {
            vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false,
                                       vop->varQDatap(), valuep);
        } else if (varp->vltype() == VLVT_WDATA) {
            vl_get_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false,
                                       vop->varEDatap(), valuep);
        }

        return;
    }

    VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported format (%s) as requested for %s", __func__,
                  VerilatedVpiError::strFromVpiVal(arrayvalue_p->format), vop->fullname());
}

void vpi_get_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, PLI_INT32* index_p,
                         PLI_UINT32 num) {
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_get_value_array %p\n", object););
    VerilatedVpiImp::assertOneCheck();

    VL_VPI_ERROR_RESET_();
    if (VL_UNLIKELY(!object)) return;

    if (VL_UNLIKELY(!arrayvalue_p)) {
        VL_VPI_WARNING_(__FILE__, __LINE__,
                        "Ignoring vpi_get_value_array with null value pointer");
        return;
    }

    if (VL_UNLIKELY(!index_p)) {
        VL_VPI_WARNING_(__FILE__, __LINE__,
                        "Ignoring vpi_get_value_array with null index pointer");
        return;
    }

    const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object);
    if (VL_UNLIKELY(!vop)) {
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle (%p)", __func__, object);
        return;
    }

    if (vop->type() != vpiRegArray) {
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported type (%p, %s)", __func__, object,
                      VerilatedVpiError::strFromVpiObjType(vop->type()));
        return;
    }

    const int lowRange = vop->rangep()->low();
    const int highRange = vop->rangep()->high();
    if ((index_p[0] > highRange) || (index_p[0] < lowRange)) {
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: index %u for object %s is out of bounds [%u,%u]",
                      __func__, index_p[0], vop->fullname(), lowRange, highRange);
        return;
    }

    if (arrayvalue_p->flags & vpiUserAllocFlag) {
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiUserAllocFlag (%x)", __func__,
                      arrayvalue_p->flags);
        return;
    }

    vl_get_value_array(object, arrayvalue_p, index_p, num);
}

void vl_put_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, const PLI_INT32* index_p,
                        PLI_UINT32 num) {
    const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object);
    if (!vl_check_array_format(vop->varp(), arrayvalue_p, vop->fullname())) return;

    const VerilatedVar* const varp = vop->varp();

    const int size = vop->size();
    if (VL_UNCOVERABLE(num > size)) {
        VL_VPI_ERROR_(__FILE__, __LINE__,
                      "%s: requested elements to set (%u) exceed array size (%u)", __func__, num,
                      size);
        return;
    }

    const bool leftIsLow = vop->rangep()->left() == vop->rangep()->low();
    const int index
        = leftIsLow ? index_p[0] - vop->rangep()->left() : vop->rangep()->left() - index_p[0];

    if (arrayvalue_p->format == vpiShortIntVal) {
        const PLI_UINT16* shortintsp
            = reinterpret_cast<PLI_UINT16*>(arrayvalue_p->value.shortints);

        if (varp->vltype() == VLVT_UINT8) {
            vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, shortintsp,
                                         vop->varCDatap());
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, shortintsp,
                                         vop->varSDatap());
        }

        return;
    } else if (arrayvalue_p->format == vpiIntVal) {
        const PLI_UINT32* integersp = reinterpret_cast<PLI_UINT32*>(arrayvalue_p->value.integers);

        if (varp->vltype() == VLVT_UINT8) {
            vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, integersp,
                                         vop->varCDatap());
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, integersp,
                                         vop->varSDatap());
        } else if (varp->vltype() == VLVT_UINT32) {
            vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, integersp,
                                         vop->varIDatap());
        }

        return;
    } else if (arrayvalue_p->format == vpiLongIntVal) {
        const PLI_UINT64* longintsp = reinterpret_cast<PLI_UINT64*>(arrayvalue_p->value.longints);

        if (varp->vltype() == VLVT_UINT8) {
            vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, longintsp,
                                         vop->varCDatap());
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, longintsp,
                                         vop->varSDatap());
        } else if (varp->vltype() == VLVT_UINT32) {
            vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, longintsp,
                                         vop->varIDatap());
        } else if (varp->vltype() == VLVT_UINT64) {
            vl_put_value_array_integrals(index, num, size, varp->entBits(), leftIsLow, longintsp,
                                         vop->varQDatap());
        }

        return;
    } else if (arrayvalue_p->format == vpiVectorVal) {
        const p_vpi_vecval vectorsp = arrayvalue_p->value.vectors;

        if (varp->vltype() == VLVT_UINT8) {
            vl_put_value_array_vectors(index, num, size, varp->entBits(), leftIsLow, true,
                                       vectorsp, vop->varCDatap());
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_put_value_array_vectors(index, num, size, varp->entBits(), leftIsLow, true,
                                       vectorsp, vop->varSDatap());
        } else if (varp->vltype() == VLVT_UINT32) {
            vl_put_value_array_vectors(index, num, size, varp->entBits(), leftIsLow, true,
                                       vectorsp, vop->varIDatap());
        } else if (varp->vltype() == VLVT_UINT64) {
            vl_put_value_array_vectors(index, num, size, varp->entBits(), leftIsLow, true,
                                       vectorsp, vop->varQDatap());
        } else if (varp->vltype() == VLVT_WDATA) {
            vl_put_value_array_vectors(index, num, size, varp->entBits(), leftIsLow, true,
                                       vectorsp, vop->varEDatap());
        }

        return;
    } else if (arrayvalue_p->format == vpiRawFourStateVal) {
        const PLI_UBYTE8* valuep = reinterpret_cast<PLI_UBYTE8*>(arrayvalue_p->value.rawvals);

        if (varp->vltype() == VLVT_UINT8) {
            vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true, valuep,
                                       vop->varCDatap());
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true, valuep,
                                       vop->varSDatap());
        } else if (varp->vltype() == VLVT_UINT32) {
            vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true, valuep,
                                       vop->varIDatap());
        } else if (varp->vltype() == VLVT_UINT64) {
            vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true, valuep,
                                       vop->varQDatap());
        } else if (varp->vltype() == VLVT_WDATA) {
            vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, true, valuep,
                                       vop->varEDatap());
        }

        return;
    } else if (arrayvalue_p->format == vpiRawTwoStateVal) {
        const PLI_UBYTE8* valuep = reinterpret_cast<PLI_UBYTE8*>(arrayvalue_p->value.rawvals);

        if (varp->vltype() == VLVT_UINT8) {
            vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false, valuep,
                                       vop->varCDatap());
        } else if (varp->vltype() == VLVT_UINT16) {
            vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false, valuep,
                                       vop->varSDatap());
        } else if (varp->vltype() == VLVT_UINT32) {
            vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false, valuep,
                                       vop->varIDatap());
        } else if (varp->vltype() == VLVT_UINT64) {
            vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false, valuep,
                                       vop->varQDatap());
        } else if (varp->vltype() == VLVT_WDATA) {
            vl_put_value_array_rawvals(index, num, size, varp->entBits(), leftIsLow, false, valuep,
                                       vop->varEDatap());
        }

        return;
    }

    VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported format (%s) as requested for %s", __func__,
                  VerilatedVpiError::strFromVpiVal(arrayvalue_p->format), vop->fullname());
}

void vpi_put_value_array(vpiHandle object, p_vpi_arrayvalue arrayvalue_p, PLI_INT32* index_p,
                         PLI_UINT32 num) {
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_put_value_array %p\n", object););
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();

    if (VL_UNLIKELY(!arrayvalue_p)) {
        VL_VPI_WARNING_(__FILE__, __LINE__,
                        "Ignoring vpi_put_value_array with null value pointer");
        return;
    }

    if (VL_UNLIKELY(!index_p)) {
        VL_VPI_WARNING_(__FILE__, __LINE__,
                        "Ignoring vpi_put_value_array with null index pointer");
        return;
    }

    const VerilatedVpioVar* const vop = VerilatedVpioVar::castp(object);
    if (VL_UNLIKELY(!vop)) {
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported vpiHandle (%p)", __func__, object);
        return;
    }

    if (vop->type() != vpiRegArray) {
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported type (%p, %s)", __func__, object,
                      VerilatedVpiError::strFromVpiObjType(vop->type()));
        return;
    }

    const int lowRange = vop->rangep()->low();
    const int highRange = vop->rangep()->high();
    if ((index_p[0] > highRange) || (index_p[0] < lowRange)) {
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: index %u for object %s is out of bounds [%u,%u]",
                      __func__, index_p[0], vop->fullname(), lowRange, highRange);
        return;
    }

    if (VL_UNLIKELY(!vop->varp()->isPublicRW())) {
        VL_VPI_ERROR_(__FILE__, __LINE__,
                      "Ignoring vpi_put_value_array to signal marked read-only,"
                      " use public_flat_rw instead: %s",
                      vop->fullname());
        return;
    }

    if (arrayvalue_p->flags & (vpiPropagateOff | vpiOneValue)) {
        VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported flags (%x)", __func__,
                      arrayvalue_p->flags);
        return;
    }

    vl_put_value_array(object, arrayvalue_p, index_p, num);
}

// time processing

void vpi_get_time(vpiHandle object, p_vpi_time time_p) {
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    // cppcheck-suppress nullPointer
    if (VL_UNLIKELY(!time_p)) {
        VL_VPI_WARNING_(__FILE__, __LINE__, "Ignoring vpi_get_time with nullptr value pointer");
        return;
    }
    if (time_p->type == vpiSimTime) {
        const QData qtime = VL_TIME_Q();
        VlWide<2> itime;
        VL_SET_WQ(itime, qtime);
        time_p->low = itime[0];
        time_p->high = itime[1];
        return;
    } else if (time_p->type == vpiScaledRealTime) {
        double dtime = VL_TIME_D();
        if (const VerilatedVpioScope* const vop = VerilatedVpioScope::castp(object)) {
            const int scalePow10
                = Verilated::threadContextp()->timeprecision() - vop->scopep()->timeunit();
            const double scale = vl_time_multiplier(scalePow10);  // e.g. 0.0001
            dtime *= scale;
        }
        time_p->real = dtime;
        return;
    }
    VL_VPI_ERROR_(__FILE__, __LINE__, "%s: Unsupported type (%d)", __func__, time_p->type);
}

// I/O routines

PLI_UINT32 vpi_mcd_open(PLI_BYTE8* filenamep) {
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    return VL_FOPEN_NN(filenamep, "wb");
}

PLI_UINT32 vpi_mcd_close(PLI_UINT32 mcd) {
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    VL_FCLOSE_I(mcd);
    return 0;
}

PLI_BYTE8* vpi_mcd_name(PLI_UINT32 /*mcd*/) {
    VL_VPI_UNIMP_();
    return nullptr;
}

PLI_INT32 vpi_mcd_printf(PLI_UINT32 mcd, PLI_BYTE8* formatp, ...) {
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    va_list ap;
    va_start(ap, formatp);
    const int chars = vpi_mcd_vprintf(mcd, formatp, ap);
    va_end(ap);
    return chars;
}

PLI_INT32 vpi_printf(PLI_BYTE8* formatp, ...) {
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    va_list ap;
    va_start(ap, formatp);
    const int chars = vpi_vprintf(formatp, ap);
    va_end(ap);
    return chars;
}

// cppcheck-suppress constParameterPointer
PLI_INT32 vpi_vprintf(PLI_BYTE8* formatp, va_list ap) {
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    return VL_VPRINTF(formatp, ap);
}

// cppcheck-suppress constParameterPointer
PLI_INT32 vpi_mcd_vprintf(PLI_UINT32 mcd, PLI_BYTE8* format, va_list ap) {
    VerilatedVpiImp::assertOneCheck();
    FILE* const fp = VL_CVT_I_FP(mcd);
    VL_VPI_ERROR_RESET_();
    // cppcheck-suppress nullPointer
    if (VL_UNLIKELY(!fp)) return 0;
    const int chars = vfprintf(fp, format, ap);
    return chars;
}

PLI_INT32 vpi_flush(void) {
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    Verilated::runFlushCallbacks();
    return 0;  // Gcc coverage bug // LCOV_EXCL_LINE
}

PLI_INT32 vpi_mcd_flush(PLI_UINT32 mcd) {
    VerilatedVpiImp::assertOneCheck();
    FILE* const fp = VL_CVT_I_FP(mcd);
    VL_VPI_ERROR_RESET_();
    if (VL_UNLIKELY(!fp)) return 1;
    std::fflush(fp);
    return 0;
}

// utility routines

PLI_INT32 vpi_compare_objects(vpiHandle /*object1*/, vpiHandle /*object2*/) {
    VL_VPI_UNIMP_();
    return 0;
}
PLI_INT32 vpi_chk_error(p_vpi_error_info error_info_p) {
    // executing vpi_chk_error does not reset error
    // error_info_p can be nullptr, so only return level in that case
    VerilatedVpiImp::assertOneCheck();
    const p_vpi_error_info imp_info_p = VerilatedVpiImp::error_info()->getError();
    if (error_info_p && imp_info_p) *error_info_p = *imp_info_p;
    if (!imp_info_p) return 0;  // no error occurred
    return imp_info_p->level;  // return error severity level
}

#ifndef VL_NO_LEGACY
PLI_INT32 vpi_free_object(vpiHandle object) {
    // vpi_free_object is IEEE deprecated, use vpi_release_handle
    return vpi_release_handle(object);
}
#endif

PLI_INT32 vpi_release_handle(vpiHandle object) {
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_release_handle %p\n", object););
    VerilatedVpiImp::assertOneCheck();
    VerilatedVpio* const vop = VerilatedVpio::castp(object);
    VL_VPI_ERROR_RESET_();
    if (VL_UNLIKELY(!vop)) return 0;
    VL_DO_DANGLING(delete vop, vop);
    return 1;
}

PLI_INT32 vpi_get_vlog_info(p_vpi_vlog_info vlog_info_p) {
    // This is VL_MT_SAFE, but not marked as can't indicate it in the standardized header file
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    const auto argc_argv = Verilated::threadContextp()->impp()->argc_argv();
    vlog_info_p->argc = argc_argv.first;
    vlog_info_p->argv = argc_argv.second;
    vlog_info_p->product = const_cast<PLI_BYTE8*>(Verilated::productName());
    vlog_info_p->version = const_cast<PLI_BYTE8*>(Verilated::productVersion());
    return 1;
}

// routines added with 1364-2001

PLI_INT32 vpi_get_data(PLI_INT32 /*id*/, PLI_BYTE8* /*dataLoc*/, PLI_INT32 /*numOfBytes*/) {
    VL_VPI_UNIMP_();
    return 0;
}
PLI_INT32 vpi_put_data(PLI_INT32 /*id*/, PLI_BYTE8* /*dataLoc*/, PLI_INT32 /*numOfBytes*/) {
    VL_VPI_UNIMP_();
    return 0;
}
void* vpi_get_userdata(vpiHandle /*obj*/) {
    VL_VPI_UNIMP_();
    return nullptr;
}
PLI_INT32 vpi_put_userdata(vpiHandle /*obj*/, void* /*userdata*/) {
    VL_VPI_UNIMP_();
    return 0;
}

PLI_INT32 vpi_control(PLI_INT32 operation, ...) {
    VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_control %d\n", operation););
    VerilatedVpiImp::assertOneCheck();
    VL_VPI_ERROR_RESET_();
    switch (operation) {
    case vpiFinish: {
        VL_FINISH_MT("", 0, "*VPI*");
        return 1;
    }
    case vpiStop: {
        VL_STOP_MT("", 0, "*VPI*");
        return 1;  // LCOV_EXCL_LINE
    }
    default: {
        VL_VPI_WARNING_(__FILE__, __LINE__, "%s: Unsupported type %s, ignoring", __func__,
                        VerilatedVpiError::strFromVpiProp(operation));
        return 0;
    }
    }
}

vpiHandle vpi_handle_by_multi_index(vpiHandle /*obj*/, PLI_INT32 /*num_index*/,
                                    PLI_INT32* /*index_array*/) {
    VL_VPI_UNIMP_();
    return nullptr;
}
