diff --git a/include/verilated.cpp b/include/verilated.cpp index d5821b8f7..7b9200363 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -72,6 +72,8 @@ #endif // clang-format on +#include "verilated_trace.h" + // Max characters in static char string for VL_VALUE_STRING constexpr unsigned VL_VALUE_STRING_MAX_WIDTH = 8192; @@ -2162,29 +2164,6 @@ void VL_WRITEMEM_N(bool hex, // Hex format, else binary //=========================================================================== // Timescale conversion -// Helper function for conversion of timescale strings -// Converts (1|10|100)(s|ms|us|ns|ps|fs) to power of then -int VL_TIME_STR_CONVERT(const char* strp) VL_PURE { - int scale = 0; - if (!strp) return 0; - if (*strp++ != '1') return 0; - while (*strp == '0') { - ++scale; - ++strp; - } - switch (*strp++) { - case 's': break; - case 'm': scale -= 3; break; - case 'u': scale -= 6; break; - case 'n': scale -= 9; break; - case 'p': scale -= 12; break; - case 'f': scale -= 15; break; - default: return 0; - } - if ((scale < 0) && (*strp++ != 's')) return 0; - if (*strp) return 0; - return scale; -} static const char* vl_time_str(int scale) VL_PURE { static const char* const names[] = {"100s", "10s", "1s", "100ms", "10ms", "1ms", "100us", "10us", "1us", @@ -2306,8 +2285,9 @@ void VerilatedContext::checkMagic(const VerilatedContext* contextp) { } VerilatedContext::Serialized::Serialized() { - m_timeunit = VL_TIME_UNIT; // Initial value until overriden by _Vconfigure - m_timeprecision = VL_TIME_PRECISION; // Initial value until overriden by _Vconfigure + constexpr int8_t picosecond = -12; + m_timeunit = picosecond; // Initial value until overriden by _Vconfigure + m_timeprecision = picosecond; // Initial value until overriden by _Vconfigure } void VerilatedContext::assertOn(bool flag) VL_MT_SAFE { @@ -2914,6 +2894,8 @@ void VerilatedImp::versionDump() VL_MT_SAFE { VerilatedModel::VerilatedModel(VerilatedContext& context) : m_context{context} {} +std::unique_ptr VerilatedModel::traceConfig() const { return nullptr; } + //=========================================================================== // VerilatedModule:: Methods diff --git a/include/verilated.h b/include/verilated.h index ebb1990e8..8943fc523 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -91,6 +91,8 @@ class VerilatedFstC; class VerilatedFstSc; class VerilatedScope; class VerilatedScopeNameMap; +template class VerilatedTrace; +class VerilatedTraceConfig; class VerilatedVar; class VerilatedVarNameMap; class VerilatedVcd; @@ -278,6 +280,12 @@ public: virtual const char* modelName() const = 0; /// Returns the thread level parallelism, this model was Verilated with. Always 1 or higher. virtual unsigned threads() const = 0; + +private: + // The following are for use by Verilator internals only + template friend class VerilatedTrace; + // Run-time trace configuration requested by this model + virtual std::unique_ptr traceConfig() const; }; //========================================================================= diff --git a/include/verilated.mk.in b/include/verilated.mk.in index a5dc4bdb3..c86e93676 100644 --- a/include/verilated.mk.in +++ b/include/verilated.mk.in @@ -142,26 +142,6 @@ ifneq ($(VM_THREADS),0) endif endif -ifneq ($(VM_TRACE_THREADS),0) - ifneq ($(VM_TRACE_THREADS),) - ifeq ($(findstring -DVL_THREADED,$(CPPFLAGS)),) - $(error VM_TRACE_THREADS requires VM_THREADS) - endif - CPPFLAGS += -DVL_TRACE_THREADED - VK_C11=1 - VK_LIBS_THREADED=1 - endif -endif - - -ifneq ($(VM_TRACE_FST_WRITER_THREAD),0) - ifneq ($(VM_TRACE_FST_WRITER_THREAD),) - CPPFLAGS += -DVL_TRACE_FST_WRITER_THREAD - VK_C11=1 - VK_LIBS_THREADED=1 - endif -endif - ifneq ($(VK_C11),0) ifneq ($(VK_C11),) # Need C++11 at least, so always default to newest diff --git a/include/verilated_fst_c.cpp b/include/verilated_fst_c.cpp index 0bc1048cf..69a71c5d9 100644 --- a/include/verilated_fst_c.cpp +++ b/include/verilated_fst_c.cpp @@ -28,7 +28,7 @@ #include "verilated_fst_c.h" // GTKWave configuration -#ifdef VL_TRACE_FST_WRITER_THREAD +#ifdef VL_THREADED # define HAVE_LIBPTHREAD # define FST_WRITER_PARALLEL #endif @@ -106,9 +106,7 @@ void VerilatedFst::open(const char* filename) VL_MT_SAFE_EXCLUDES(m_mutex) { m_fst = fstWriterCreate(filename, 1); fstWriterSetPackType(m_fst, FST_WR_PT_LZ4); fstWriterSetTimescaleFromString(m_fst, timeResStr().c_str()); // lintok-begin-on-ref -#ifdef VL_TRACE_FST_WRITER_THREAD - fstWriterSetParallelMode(m_fst, 1); -#endif + if (m_useFstWriterThread) fstWriterSetParallelMode(m_fst, 1); fullDump(true); // First dump must be full for fst m_curScope.clear(); @@ -250,23 +248,36 @@ void VerilatedFst::declDouble(uint32_t code, const char* name, int dtypenum, fst //============================================================================= // Get/commit trace buffer -VerilatedFstBuffer* VerilatedFst::getTraceBuffer() { return new VerilatedFstBuffer{*this}; } +VerilatedFst::Buffer* VerilatedFst::getTraceBuffer() { +#ifdef VL_THREADED + if (offload()) return new OffloadBuffer{*this}; +#endif + return new Buffer{*this}; +} -void VerilatedFst::commitTraceBuffer(VerilatedFstBuffer* bufp) { -#ifdef VL_TRACE_OFFLOAD - if (bufp->m_offloadBufferWritep) { - m_offloadBufferWritep = bufp->m_offloadBufferWritep; - return; // Buffer will be deleted by the offload thread +void VerilatedFst::commitTraceBuffer(VerilatedFst::Buffer* bufp) { +#ifdef VL_THREADED + if (offload()) { + OffloadBuffer* const offloadBufferp = static_cast(bufp); + if (offloadBufferp->m_offloadBufferWritep) { + m_offloadBufferWritep = offloadBufferp->m_offloadBufferWritep; + return; // Buffer will be deleted by the offload thread + } } #endif delete bufp; } //============================================================================= -// VerilatedFstBuffer implementation +// Configure -VerilatedFstBuffer::VerilatedFstBuffer(VerilatedFst& owner) - : VerilatedTraceBuffer{owner} {} +void VerilatedFst::configure(const VerilatedTraceConfig& config) { + // If at least one model requests the FST writer thread, then use it + m_useFstWriterThread |= config.m_useFstWriterThread; +} + +//============================================================================= +// VerilatedFstBuffer implementation //============================================================================= // Trace rendering primitives diff --git a/include/verilated_fst_c.h b/include/verilated_fst_c.h index 5131cc8cc..c87b9763b 100644 --- a/include/verilated_fst_c.h +++ b/include/verilated_fst_c.h @@ -43,7 +43,7 @@ public: using Super = VerilatedTrace; private: - friend Buffer; // Give the buffer access to the private bits + friend VerilatedFstBuffer; // Give the buffer access to the private bits //========================================================================= // FST specific internals @@ -55,6 +55,8 @@ private: fstHandle* m_symbolp = nullptr; // same as m_code2symbol, but as an array char* m_strbuf = nullptr; // String buffer long enough to hold maxBits() chars + bool m_useFstWriterThread = false; // Whether to use the separate FST writer thread + // CONSTRUCTORS VL_UNCOPYABLE(VerilatedFst); void declare(uint32_t code, const char* name, int dtypenum, fstVarDir vardir, @@ -72,8 +74,11 @@ protected: virtual bool preChangeDump() override { return isOpen(); } // Trace buffer management - virtual VerilatedFstBuffer* getTraceBuffer() override; - virtual void commitTraceBuffer(VerilatedFstBuffer*) override; + virtual Buffer* getTraceBuffer() override; + virtual void commitTraceBuffer(Buffer*) override; + + // Configure sub-class + virtual void configure(const VerilatedTraceConfig&) override; public: //========================================================================= @@ -124,10 +129,14 @@ template <> void VerilatedFst::Super::dumpvars(int level, const std::string& hie //============================================================================= // VerilatedFstBuffer -class VerilatedFstBuffer final : public VerilatedTraceBuffer { +class VerilatedFstBuffer VL_NOT_FINAL { // Give the trace file access to the private bits friend VerilatedFst; friend VerilatedFst::Super; + friend VerilatedFst::Buffer; + friend VerilatedFst::OffloadBuffer; + + VerilatedFst& m_owner; // Trace file owning this buffer. Required by subclasses. // The FST file handle void* const m_fst = m_owner.m_fst; @@ -136,10 +145,10 @@ class VerilatedFstBuffer final : public VerilatedTraceBuffer #include #include +#include #include -#ifdef VL_TRACE_OFFLOAD +#ifdef VL_THREADED # include # include #endif @@ -57,9 +43,10 @@ // clang-format on class VlThreadPool; -template class VerilatedTraceBuffer; +template class VerilatedTraceBuffer; +template class VerilatedTraceOffloadBuffer; -#ifdef VL_TRACE_OFFLOAD +#ifdef VL_THREADED //============================================================================= // Offloaded tracing @@ -129,27 +116,46 @@ public: }; #endif +//============================================================================= +// VerilatedTraceConfig + +// Simple data representing trace configuration required by generated models. +class VerilatedTraceConfig final { +public: + const bool m_useParallel; // Use parallel tracing + const bool m_useOffloading; // Offloading trace rendering + const bool m_useFstWriterThread; // Use the separate FST writer thread + + VerilatedTraceConfig(bool useParallel, bool useOffloading, bool useFstWriterThread) + : m_useParallel{useParallel} + , m_useOffloading{useOffloading} + , m_useFstWriterThread{useFstWriterThread} {} +}; + //============================================================================= // VerilatedTrace // T_Trace is the format specific subclass of VerilatedTrace. -// T_Buffer is the format specific subclass of VerilatedTraceBuffer. +// T_Buffer is the format specific base class of VerilatedTraceBuffer. template class VerilatedTrace VL_NOT_FINAL { - // Give the buffer (both base and derived) access to the private bits - friend VerilatedTraceBuffer; - friend T_Buffer; - public: - using Buffer = T_Buffer; + using Buffer = VerilatedTraceBuffer; + using OffloadBuffer = VerilatedTraceOffloadBuffer; //========================================================================= // Generic tracing internals using initCb_t = void (*)(void*, T_Trace*, uint32_t); // Type of init callbacks using dumpCb_t = void (*)(void*, Buffer*); // Type of dump callbacks + using dumpOffloadCb_t = void (*)(void*, OffloadBuffer*); // Type of offload dump callbacks using cleanupCb_t = void (*)(void*, T_Trace*); // Type of cleanup callbacks private: + // Give the buffer (both base and derived) access to the private bits + friend T_Buffer; + friend Buffer; + friend OffloadBuffer; + struct CallbackRecord { // Note: would make these fields const, but some old STL implementations // (the one in Ubuntu 14.04 with GCC 4.8.4 in particular) use the @@ -158,6 +164,7 @@ private: union { // The callback initCb_t m_initCb; dumpCb_t m_dumpCb; + dumpOffloadCb_t m_dumpOffloadCb; cleanupCb_t m_cleanupCb; }; void* m_userp; // The user pointer to pass to the callback (the symbol table) @@ -167,12 +174,18 @@ private: CallbackRecord(dumpCb_t cb, void* userp) : m_dumpCb{cb} , m_userp{userp} {} + CallbackRecord(dumpOffloadCb_t cb, void* userp) + : m_dumpOffloadCb{cb} + , m_userp{userp} {} CallbackRecord(cleanupCb_t cb, void* userp) : m_cleanupCb{cb} , m_userp{userp} {} }; -#ifdef VL_TRACE_PARALLEL + bool m_offload = false; // Use the offload thread (ignored if !VL_THREADED) + bool m_parallel = false; // Use parallel tracing (ignored if !VL_THREADED) + +#ifdef VL_THREADED struct ParallelWorkerData { const dumpCb_t m_cb; // The callback void* const m_userp; // The use pointer to pass to the callback @@ -198,13 +211,13 @@ protected: uint32_t* m_sigs_oldvalp = nullptr; // Previous value store EData* m_sigs_enabledp = nullptr; // Bit vector of enabled codes (nullptr = all on) private: - uint64_t m_timeLastDump = 0; // Last time we did a dump std::vector m_sigs_enabledVec; // Staging for m_sigs_enabledp std::vector m_initCbs; // Routines to initialize tracing std::vector m_fullCbs; // Routines to perform full dump + std::vector m_fullOffloadCbs; // Routines to perform offloaded full dump std::vector m_chgCbs; // Routines to perform incremental dump + std::vector m_chgOffloadCbs; // Routines to perform offloaded incremental dump std::vector m_cleanupCbs; // Routines to call at the end of dump - VerilatedContext* m_contextp = nullptr; // The context used by the traced models bool m_fullDump = true; // Whether a full dump is required on the next call to 'dump' uint32_t m_nextCode = 0; // Next code number to assign uint32_t m_numSignals = 0; // Number of distinct signals @@ -214,8 +227,10 @@ private: char m_scopeEscape = '.'; double m_timeRes = 1e-9; // Time resolution (ns/ms etc) double m_timeUnit = 1e-0; // Time units (ns/ms etc) - - void addContext(VerilatedContext*) VL_MT_SAFE_EXCLUDES(m_mutex); + uint64_t m_timeLastDump = 0; // Last time we did a dump + bool m_didSomeDump = false; // Did at least one dump (i.e.: m_timeLastDump is valid) + VerilatedContext* m_contextp = nullptr; // The context used by the traced models + std::unordered_set m_models; // The collection of models being traced void addCallbackRecord(std::vector& cbVec, CallbackRecord&& cbRec) VL_MT_SAFE_EXCLUDES(m_mutex); @@ -225,13 +240,14 @@ private: T_Trace* self() { return static_cast(this); } void runCallbacks(const std::vector& cbVec); + void runOffloadedCallbacks(const std::vector& cbVec); // Flush any remaining data for this file static void onFlush(void* selfp) VL_MT_UNSAFE_ONE; // Close the file on termination static void onExit(void* selfp) VL_MT_UNSAFE_ONE; -#ifdef VL_TRACE_OFFLOAD +#ifdef VL_THREADED // Number of total offload buffers that have been allocated uint32_t m_numOffloadBuffers = 0; // Size of offload buffers @@ -277,7 +293,6 @@ protected: uint32_t numSignals() const { return m_numSignals; } uint32_t maxBits() const { return m_maxBits; } void fullDump(bool value) { m_fullDump = value; } - uint64_t timeLastDump() { return m_timeLastDump; } double timeRes() const { return m_timeRes; } double timeUnit() const { return m_timeUnit; } @@ -298,6 +313,14 @@ protected: void closeBase(); void flushBase(); +#ifdef VL_THREADED + inline bool offload() const { return m_offload; } + inline bool parallel() const { return m_parallel; } +#else + static constexpr bool offload() { return false; } + static constexpr bool parallel() { return false; } +#endif + //========================================================================= // Virtual functions to be provided by the format specific implementation @@ -313,6 +336,9 @@ protected: virtual Buffer* getTraceBuffer() = 0; virtual void commitTraceBuffer(Buffer*) = 0; + // Configure sub-class + virtual void configure(const VerilatedTraceConfig&) = 0; + public: //========================================================================= // External interface to client code @@ -339,10 +365,13 @@ public: //========================================================================= // Non-hot path internal interface to Verilator generated code - void addInitCb(initCb_t cb, void* userp, VerilatedContext*) VL_MT_SAFE; - void addFullCb(dumpCb_t cb, void* userp, VerilatedContext*) VL_MT_SAFE; - void addChgCb(dumpCb_t cb, void* userp, VerilatedContext*) VL_MT_SAFE; - void addCleanupCb(cleanupCb_t cb, void* userp, VerilatedContext*) VL_MT_SAFE; + void addModel(VerilatedModel*) VL_MT_SAFE_EXCLUDES(m_mutex); + void addInitCb(initCb_t cb, void* userp) VL_MT_SAFE; + void addFullCb(dumpCb_t cb, void* userp) VL_MT_SAFE; + void addFullCb(dumpOffloadCb_t cb, void* userp) VL_MT_SAFE; + void addChgCb(dumpCb_t cb, void* userp) VL_MT_SAFE; + void addChgCb(dumpOffloadCb_t cb, void* userp) VL_MT_SAFE; + void addCleanupCb(cleanupCb_t cb, void* userp) VL_MT_SAFE; void scopeEscape(char flag) { m_scopeEscape = flag; } @@ -353,32 +382,25 @@ public: //============================================================================= // VerilatedTraceBuffer -// T_Trace is the format specific subclass of VerilatedTrace. -// T_Buffer is the format specific subclass of VerilatedTraceBuffer. +// T_Buffer is the format specific base class of VerilatedTraceBuffer. // The format-specific hot-path methods use duck-typing via T_Buffer for performance. -template class VerilatedTraceBuffer VL_NOT_FINAL { - friend T_Trace; // Give the trace file access to the private bits - +template // +class VerilatedTraceBuffer VL_NOT_FINAL : public T_Buffer { protected: - T_Trace& m_owner; // The VerilatedTrace subclass that owns this buffer + // Type of the owner trace file + using Trace = typename std::remove_cv< + typename std::remove_reference::type>::type; - // Previous value store - uint32_t* const m_sigs_oldvalp = m_owner.m_sigs_oldvalp; - // Bit vector of enabled codes (nullptr = all on) - EData* const m_sigs_enabledp = m_owner.m_sigs_enabledp; + static_assert(std::has_virtual_destructor::value, ""); + static_assert(std::is_base_of, Trace>::value, ""); -#ifdef VL_TRACE_OFFLOAD - // Write pointer into current buffer - uint32_t* m_offloadBufferWritep = m_owner.m_offloadBufferWritep; - // End of offload buffer - uint32_t* const m_offloadBufferEndp = m_owner.m_offloadBufferEndp; -#endif + friend Trace; // Give the trace file access to the private bits + friend std::default_delete>; - // Equivalent to 'this' but is of the sub-type 'T_Derived*'. Use 'self()->' - // to access duck-typed functions to avoid a virtual function call. - inline T_Buffer* self() { return static_cast(this); } + uint32_t* const m_sigs_oldvalp; // Previous value store + EData* const m_sigs_enabledp; // Bit vector of enabled codes (nullptr = all on) - explicit VerilatedTraceBuffer(T_Trace& owner); + explicit VerilatedTraceBuffer(Trace& owner); virtual ~VerilatedTraceBuffer() = default; public: @@ -410,7 +432,67 @@ public: void fullWData(uint32_t* oldp, const WData* newvalp, int bits); void fullDouble(uint32_t* oldp, double newval); -#ifdef VL_TRACE_OFFLOAD + // In non-offload mode, these are called directly by the trace callbacks, + // and are called chg*. In offload mode, they are called by the worker + // thread and are called chg*Impl + + // Check previous dumped value of signal. If changed, then emit trace entry + VL_ATTR_ALWINLINE inline void chgBit(uint32_t* oldp, CData newval) { + const uint32_t diff = *oldp ^ newval; + if (VL_UNLIKELY(diff)) fullBit(oldp, newval); + } + VL_ATTR_ALWINLINE inline void chgCData(uint32_t* oldp, CData newval, int bits) { + const uint32_t diff = *oldp ^ newval; + if (VL_UNLIKELY(diff)) fullCData(oldp, newval, bits); + } + VL_ATTR_ALWINLINE inline void chgSData(uint32_t* oldp, SData newval, int bits) { + const uint32_t diff = *oldp ^ newval; + if (VL_UNLIKELY(diff)) fullSData(oldp, newval, bits); + } + VL_ATTR_ALWINLINE inline void chgIData(uint32_t* oldp, IData newval, int bits) { + const uint32_t diff = *oldp ^ newval; + if (VL_UNLIKELY(diff)) fullIData(oldp, newval, bits); + } + VL_ATTR_ALWINLINE inline void chgQData(uint32_t* oldp, QData newval, int bits) { + const uint64_t diff = *reinterpret_cast(oldp) ^ newval; + if (VL_UNLIKELY(diff)) fullQData(oldp, newval, bits); + } + VL_ATTR_ALWINLINE inline void chgWData(uint32_t* oldp, const WData* newvalp, int bits) { + for (int i = 0; i < (bits + 31) / 32; ++i) { + if (VL_UNLIKELY(oldp[i] ^ newvalp[i])) { + fullWData(oldp, newvalp, bits); + return; + } + } + } + VL_ATTR_ALWINLINE inline void chgDouble(uint32_t* oldp, double newval) { + // cppcheck-suppress invalidPointerCast + if (VL_UNLIKELY(*reinterpret_cast(oldp) != newval)) fullDouble(oldp, newval); + } +}; + +#ifdef VL_THREADED +//============================================================================= +// VerilatedTraceOffloadBuffer + +// T_Buffer is the format specific base class of VerilatedTraceBuffer. +// The format-specific hot-path methods use duck-typing via T_Buffer for performance. +template // +class VerilatedTraceOffloadBuffer final : public VerilatedTraceBuffer { + using typename VerilatedTraceBuffer::Trace; + + friend Trace; // Give the trace file access to the private bits + + uint32_t* m_offloadBufferWritep; // Write pointer into current buffer + uint32_t* const m_offloadBufferEndp; // End of offload buffer + + explicit VerilatedTraceOffloadBuffer(Trace& owner); + virtual ~VerilatedTraceOffloadBuffer() = default; + +public: + //========================================================================= + // Hot path internal interface to Verilator generated code + // Offloaded tracing. Just dump everything in the offload buffer inline void chgBit(uint32_t code, CData newval) { m_offloadBufferWritep[0] = VerilatedTraceOffloadCommand::CHG_BIT_0 | newval; @@ -461,63 +543,7 @@ public: m_offloadBufferWritep += 4; VL_DEBUG_IF(assert(m_offloadBufferWritep <= m_offloadBufferEndp);); } - -#define chgBit chgBitImpl -#define chgCData chgCDataImpl -#define chgSData chgSDataImpl -#define chgIData chgIDataImpl -#define chgQData chgQDataImpl -#define chgWData chgWDataImpl -#define chgDouble chgDoubleImpl -#endif - - // In non-offload mode, these are called directly by the trace callbacks, - // and are called chg*. In offload mode, they are called by the worker - // thread and are called chg*Impl - - // Check previous dumped value of signal. If changed, then emit trace entry - VL_ATTR_ALWINLINE inline void chgBit(uint32_t* oldp, CData newval) { - const uint32_t diff = *oldp ^ newval; - if (VL_UNLIKELY(diff)) fullBit(oldp, newval); - } - VL_ATTR_ALWINLINE inline void chgCData(uint32_t* oldp, CData newval, int bits) { - const uint32_t diff = *oldp ^ newval; - if (VL_UNLIKELY(diff)) fullCData(oldp, newval, bits); - } - VL_ATTR_ALWINLINE inline void chgSData(uint32_t* oldp, SData newval, int bits) { - const uint32_t diff = *oldp ^ newval; - if (VL_UNLIKELY(diff)) fullSData(oldp, newval, bits); - } - VL_ATTR_ALWINLINE inline void chgIData(uint32_t* oldp, IData newval, int bits) { - const uint32_t diff = *oldp ^ newval; - if (VL_UNLIKELY(diff)) fullIData(oldp, newval, bits); - } - VL_ATTR_ALWINLINE inline void chgQData(uint32_t* oldp, QData newval, int bits) { - const uint64_t diff = *reinterpret_cast(oldp) ^ newval; - if (VL_UNLIKELY(diff)) fullQData(oldp, newval, bits); - } - VL_ATTR_ALWINLINE inline void chgWData(uint32_t* oldp, const WData* newvalp, int bits) { - for (int i = 0; i < (bits + 31) / 32; ++i) { - if (VL_UNLIKELY(oldp[i] ^ newvalp[i])) { - fullWData(oldp, newvalp, bits); - return; - } - } - } - VL_ATTR_ALWINLINE inline void chgDouble(uint32_t* oldp, double newval) { - // cppcheck-suppress invalidPointerCast - if (VL_UNLIKELY(*reinterpret_cast(oldp) != newval)) fullDouble(oldp, newval); - } - -#ifdef VL_TRACE_OFFLOAD -#undef chgBit -#undef chgCData -#undef chgSData -#undef chgIData -#undef chgQData -#undef chgWData -#undef chgDouble -#endif }; +#endif #endif // guard diff --git a/include/verilated_trace_imp.h b/include/verilated_trace_imp.h index a09ac0f43..1401f67eb 100644 --- a/include/verilated_trace_imp.h +++ b/include/verilated_trace_imp.h @@ -26,7 +26,7 @@ #include "verilated_intrinsics.h" #include "verilated_trace.h" -#ifdef VL_TRACE_PARALLEL +#ifdef VL_THREADED # include "verilated_threads.h" # include #endif @@ -78,7 +78,7 @@ static std::string doubleToTimescale(double value) { return valuestr; // Gets converted to string, so no ref to stack } -#ifdef VL_TRACE_OFFLOAD +#ifdef VL_THREADED //========================================================================= // Buffer management @@ -127,7 +127,7 @@ template <> void VerilatedTrace::offloadWorkerThreadMain() { const uint32_t* readp = bufferp; - std::unique_ptr traceBufp; // We own the passed tracebuffer + std::unique_ptr traceBufp; // We own the passed tracebuffer while (true) { const uint32_t cmd = readp[0]; @@ -143,44 +143,44 @@ template <> void VerilatedTrace::offloadWorkerThreadMain() { // CHG_* commands case VerilatedTraceOffloadCommand::CHG_BIT_0: VL_TRACE_OFFLOAD_DEBUG("Command CHG_BIT_0 " << top); - traceBufp->chgBitImpl(oldp, 0); + traceBufp->chgBit(oldp, 0); continue; case VerilatedTraceOffloadCommand::CHG_BIT_1: VL_TRACE_OFFLOAD_DEBUG("Command CHG_BIT_1 " << top); - traceBufp->chgBitImpl(oldp, 1); + traceBufp->chgBit(oldp, 1); continue; case VerilatedTraceOffloadCommand::CHG_CDATA: VL_TRACE_OFFLOAD_DEBUG("Command CHG_CDATA " << top); // Bits stored in bottom byte of command - traceBufp->chgCDataImpl(oldp, *readp, top); + traceBufp->chgCData(oldp, *readp, top); readp += 1; continue; case VerilatedTraceOffloadCommand::CHG_SDATA: VL_TRACE_OFFLOAD_DEBUG("Command CHG_SDATA " << top); // Bits stored in bottom byte of command - traceBufp->chgSDataImpl(oldp, *readp, top); + traceBufp->chgSData(oldp, *readp, top); readp += 1; continue; case VerilatedTraceOffloadCommand::CHG_IDATA: VL_TRACE_OFFLOAD_DEBUG("Command CHG_IDATA " << top); // Bits stored in bottom byte of command - traceBufp->chgIDataImpl(oldp, *readp, top); + traceBufp->chgIData(oldp, *readp, top); readp += 1; continue; case VerilatedTraceOffloadCommand::CHG_QDATA: VL_TRACE_OFFLOAD_DEBUG("Command CHG_QDATA " << top); // Bits stored in bottom byte of command - traceBufp->chgQDataImpl(oldp, *reinterpret_cast(readp), top); + traceBufp->chgQData(oldp, *reinterpret_cast(readp), top); readp += 2; continue; case VerilatedTraceOffloadCommand::CHG_WDATA: VL_TRACE_OFFLOAD_DEBUG("Command CHG_WDATA " << top); - traceBufp->chgWDataImpl(oldp, readp, top); + traceBufp->chgWData(oldp, readp, top); readp += VL_WORDS_I(top); continue; case VerilatedTraceOffloadCommand::CHG_DOUBLE: VL_TRACE_OFFLOAD_DEBUG("Command CHG_DOUBLE " << top); - traceBufp->chgDoubleImpl(oldp, *reinterpret_cast(readp)); + traceBufp->chgDouble(oldp, *reinterpret_cast(readp)); readp += 2; continue; @@ -196,7 +196,7 @@ template <> void VerilatedTrace::offloadWorkerThreadMain() { case VerilatedTraceOffloadCommand::TRACE_BUFFER: VL_TRACE_OFFLOAD_DEBUG("Command TRACE_BUFFER " << top); readp -= 1; // No code in this command, undo increment - traceBufp.reset(*reinterpret_cast(readp)); + traceBufp.reset(*reinterpret_cast(readp)); readp += 2; continue; @@ -252,24 +252,28 @@ template <> void VerilatedTrace::shutdownOffloadWorker() { // Life cycle template <> void VerilatedTrace::closeBase() { -#ifdef VL_TRACE_OFFLOAD - shutdownOffloadWorker(); - while (m_numOffloadBuffers) { - delete[] m_offloadBuffersFromWorker.get(); - --m_numOffloadBuffers; +#ifdef VL_THREADED + if (offload()) { + shutdownOffloadWorker(); + while (m_numOffloadBuffers) { + delete[] m_offloadBuffersFromWorker.get(); + --m_numOffloadBuffers; + } } #endif } template <> void VerilatedTrace::flushBase() { -#ifdef VL_TRACE_OFFLOAD - // Hand an empty buffer to the worker thread - uint32_t* const bufferp = getOffloadBuffer(); - *bufferp = VerilatedTraceOffloadCommand::END; - m_offloadBuffersToWorker.put(bufferp); - // Wait for it to be returned. As the processing is in-order, - // this ensures all previous buffers have been processed. - waitForOffloadBuffer(bufferp); +#ifdef VL_THREADED + if (offload()) { + // Hand an empty buffer to the worker thread + uint32_t* const bufferp = getOffloadBuffer(); + *bufferp = VerilatedTraceOffloadCommand::END; + m_offloadBuffersToWorker.put(bufferp); + // Wait for it to be returned. As the processing is in-order, + // this ensures all previous buffers have been processed. + waitForOffloadBuffer(bufferp); + } #endif } @@ -299,9 +303,7 @@ template <> VerilatedTrace::~VerilatedTrace() { if (m_sigs_enabledp) VL_DO_CLEAR(delete[] m_sigs_enabledp, m_sigs_enabledp = nullptr); Verilated::removeFlushCb(VerilatedTrace::onFlush, this); Verilated::removeExitCb(VerilatedTrace::onExit, this); -#ifdef VL_TRACE_OFFLOAD - closeBase(); -#endif + if (offload()) closeBase(); } //========================================================================= @@ -355,17 +357,19 @@ template <> void VerilatedTrace::traceInit() VL_MT_UNSAFE { Verilated::addFlushCb(VerilatedTrace::onFlush, this); Verilated::addExitCb(VerilatedTrace::onExit, this); -#ifdef VL_TRACE_OFFLOAD - // Compute offload buffer size. we need to be able to store a new value for - // each signal, which is 'nextCode()' entries after the init callbacks - // above have been run, plus up to 2 more words of metadata per signal, - // plus fixed overhead of 1 for a termination flag and 3 for a time stamp - // update. - m_offloadBufferSize = nextCode() + numSignals() * 2 + 4; +#ifdef VL_THREADED + if (offload()) { + // Compute offload buffer size. we need to be able to store a new value for + // each signal, which is 'nextCode()' entries after the init callbacks + // above have been run, plus up to 2 more words of metadata per signal, + // plus fixed overhead of 1 for a termination flag and 3 for a time stamp + // update. + m_offloadBufferSize = nextCode() + numSignals() * 2 + 4; - // Start the worker thread - m_workerThread.reset( - new std::thread{&VerilatedTrace::offloadWorkerThreadMain, this}); + // Start the worker thread + m_workerThread.reset( + new std::thread{&VerilatedTrace::offloadWorkerThreadMain, this}); + } #endif } @@ -451,7 +455,7 @@ void VerilatedTrace::dumpvars(int level, const std::string& } } -#ifdef VL_TRACE_PARALLEL +#ifdef VL_THREADED template <> // void VerilatedTrace::parallelWorkerTask(void* datap, bool) { ParallelWorkerData* const wdp = reinterpret_cast(datap); @@ -479,45 +483,47 @@ template <> VL_ATTR_NOINLINE void VerilatedTrace::ParallelWo template <> void VerilatedTrace::runCallbacks(const std::vector& cbVec) { -#ifdef VL_TRACE_PARALLEL - // If tracing in parallel, dispatch to the thread pool - VlThreadPool* threadPoolp = static_cast(m_contextp->threadPoolp()); - // List of work items for thread (std::list, as ParallelWorkerData is not movable) - std::list workerData; - // We use the whole pool + the main thread - const unsigned threads = threadPoolp->numThreads() + 1; - // Main thread executes all jobs with index % threads == 0 - std::vector mainThreadWorkerData; - // Enuque all the jobs - for (unsigned i = 0; i < cbVec.size(); ++i) { - const CallbackRecord& cbr = cbVec[i]; - // Always get the trace buffer on the main thread - Buffer* const bufp = getTraceBuffer(); - // Create new work item - workerData.emplace_back(cbr.m_dumpCb, cbr.m_userp, bufp); - // Grab the new work item - ParallelWorkerData* const itemp = &workerData.back(); - // Enqueue task to thread pool, or main thread - if (unsigned rem = i % threads) { - threadPoolp->workerp(rem - 1)->addTask(parallelWorkerTask, itemp); - } else { - mainThreadWorkerData.push_back(itemp); +#ifdef VL_THREADED + if (parallel()) { + // If tracing in parallel, dispatch to the thread pool + VlThreadPool* threadPoolp = static_cast(m_contextp->threadPoolp()); + // List of work items for thread (std::list, as ParallelWorkerData is not movable) + std::list workerData; + // We use the whole pool + the main thread + const unsigned threads = threadPoolp->numThreads() + 1; + // Main thread executes all jobs with index % threads == 0 + std::vector mainThreadWorkerData; + // Enuque all the jobs + for (unsigned i = 0; i < cbVec.size(); ++i) { + const CallbackRecord& cbr = cbVec[i]; + // Always get the trace buffer on the main thread + Buffer* const bufp = getTraceBuffer(); + // Create new work item + workerData.emplace_back(cbr.m_dumpCb, cbr.m_userp, bufp); + // Grab the new work item + ParallelWorkerData* const itemp = &workerData.back(); + // Enqueue task to thread pool, or main thread + if (unsigned rem = i % threads) { + threadPoolp->workerp(rem - 1)->addTask(parallelWorkerTask, itemp); + } else { + mainThreadWorkerData.push_back(itemp); + } + } + // Execute main thead jobs + for (ParallelWorkerData* const itemp : mainThreadWorkerData) { + parallelWorkerTask(itemp, false); + } + // Commit all trace buffers in order + for (ParallelWorkerData& item : workerData) { + // Wait until ready + item.wait(); + // Commit the buffer + commitTraceBuffer(item.m_bufp); } - } - // Execute main thead jobs - for (ParallelWorkerData* const itemp : mainThreadWorkerData) { - parallelWorkerTask(itemp, false); - } - // Commit all trace buffers in order - for (ParallelWorkerData& item : workerData) { - // Wait until ready - item.wait(); - // Commit the buffer - commitTraceBuffer(item.m_bufp); - } - // Done - return; + // Done + return; + } #endif // Fall back on sequential execution for (const CallbackRecord& cbr : cbVec) { @@ -527,19 +533,35 @@ void VerilatedTrace::runCallbacks(const std::vector +void VerilatedTrace::runOffloadedCallbacks( + const std::vector& cbVec) { + // Fall back on sequential execution + for (const CallbackRecord& cbr : cbVec) { +#ifdef VL_THREADED + Buffer* traceBufferp = getTraceBuffer(); + cbr.m_dumpOffloadCb(cbr.m_userp, static_cast(traceBufferp)); + commitTraceBuffer(traceBufferp); +#else + VL_FATAL_MT(__FILE__, __LINE__, "", "Unreachable"); +#endif + } +} + template <> void VerilatedTrace::dump(uint64_t timeui) VL_MT_SAFE_EXCLUDES(m_mutex) { // Not really VL_MT_SAFE but more VL_MT_UNSAFE_ONE. // This does get the mutex, but if multiple threads are trying to dump // chances are the data being dumped will have other problems const VerilatedLockGuard lock{m_mutex}; - if (VL_UNCOVERABLE(m_timeLastDump && timeui <= m_timeLastDump)) { // LCOV_EXCL_START + if (VL_UNCOVERABLE(m_didSomeDump && timeui <= m_timeLastDump)) { // LCOV_EXCL_START VL_PRINTF_MT("%%Warning: previous dump at t=%" PRIu64 ", requesting t=%" PRIu64 ", dump call ignored\n", m_timeLastDump, timeui); return; } // LCOV_EXCL_STOP m_timeLastDump = timeui; + m_didSomeDump = true; Verilated::quiesce(); @@ -550,35 +572,47 @@ void VerilatedTrace::dump(uint64_t timeui) VL_MT_SAFE_EXCLUD if (!preChangeDump()) return; } -#ifdef VL_TRACE_OFFLOAD - // Currently only incremental dumps run on the worker thread uint32_t* bufferp = nullptr; - if (VL_LIKELY(!m_fullDump)) { - // Get the offload buffer we are about to fill - bufferp = getOffloadBuffer(); - m_offloadBufferWritep = bufferp; - m_offloadBufferEndp = bufferp + m_offloadBufferSize; + if (offload()) { +#ifdef VL_THREADED + // Currently only incremental dumps run on the worker thread + if (VL_LIKELY(!m_fullDump)) { + // Get the offload buffer we are about to fill + bufferp = getOffloadBuffer(); + m_offloadBufferWritep = bufferp; + m_offloadBufferEndp = bufferp + m_offloadBufferSize; - // Tell worker to update time point - m_offloadBufferWritep[0] = VerilatedTraceOffloadCommand::TIME_CHANGE; - *reinterpret_cast(m_offloadBufferWritep + 1) = timeui; - m_offloadBufferWritep += 3; + // Tell worker to update time point + m_offloadBufferWritep[0] = VerilatedTraceOffloadCommand::TIME_CHANGE; + *reinterpret_cast(m_offloadBufferWritep + 1) = timeui; + m_offloadBufferWritep += 3; + } else { + // Update time point + flushBase(); + emitTimeChange(timeui); + } +#else + VL_FATAL_MT(__FILE__, __LINE__, "", "Unreachable"); +#endif } else { // Update time point - flushBase(); emitTimeChange(timeui); } -#else - // Update time point - emitTimeChange(timeui); -#endif // Run the callbacks if (VL_UNLIKELY(m_fullDump)) { m_fullDump = false; // No more need for next dump to be full - runCallbacks(m_fullCbs); + if (offload()) { + runOffloadedCallbacks(m_fullOffloadCbs); + } else { + runCallbacks(m_fullCbs); + } } else { - runCallbacks(m_chgCbs); + if (offload()) { + runOffloadedCallbacks(m_chgOffloadCbs); + } else { + runCallbacks(m_chgCbs); + } } for (uint32_t i = 0; i < m_cleanupCbs.size(); ++i) { @@ -586,8 +620,8 @@ void VerilatedTrace::dump(uint64_t timeui) VL_MT_SAFE_EXCLUD cbr.m_cleanupCb(cbr.m_userp, self()); } -#ifdef VL_TRACE_OFFLOAD - if (VL_LIKELY(bufferp)) { +#ifdef VL_THREADED + if (offload() && VL_LIKELY(bufferp)) { // Mark end of the offload buffer we just filled *m_offloadBufferWritep++ = VerilatedTraceOffloadCommand::END; @@ -604,15 +638,56 @@ void VerilatedTrace::dump(uint64_t timeui) VL_MT_SAFE_EXCLUD // Non-hot path internal interface to Verilator generated code template <> -void VerilatedTrace::addContext(VerilatedContext* contextp) +void VerilatedTrace::addModel(VerilatedModel* modelp) VL_MT_SAFE_EXCLUDES(m_mutex) { const VerilatedLockGuard lock{m_mutex}; - if (m_contextp && contextp != m_contextp) { - VL_FATAL_MT( - __FILE__, __LINE__, "", - "A trace file instance can only handle models from the same simulation context"); + + const bool firstModel = m_models.empty(); + const bool newModel = m_models.insert(modelp).second; + VerilatedContext* const contextp = modelp->contextp(); + + // Validate + if (!newModel) { // LCOV_EXCL_START + VL_FATAL_MT(__FILE__, __LINE__, "", + "The same model has already been added to this trace file"); } + if (VL_UNCOVERABLE(m_contextp && contextp != m_contextp)) { + VL_FATAL_MT(__FILE__, __LINE__, "", + "A trace file instance can only handle models from the same context"); + } + if (VL_UNCOVERABLE(m_didSomeDump)) { + VL_FATAL_MT(__FILE__, __LINE__, "", + "Cannot add models to a trace file if 'dump' has already been called"); + } // LCOV_EXCL_STOP + + // Keep hold of the context m_contextp = contextp; + + // Get the desired trace config from the model + const std::unique_ptr configp = modelp->traceConfig(); +#ifndef VL_THREADED + if (configp->m_useOffloading) { + VL_FATAL_MT(__FILE__, __LINE__, "", "Cannot use trace offloading without VL_THREADED"); + } +#endif + + // Configure trace base class + if (!firstModel) { + if (m_offload != configp->m_useOffloading) { + VL_FATAL_MT(__FILE__, __LINE__, "", + "Either all or no models using the same trace file must use offloading"); + } + } + m_offload = configp->m_useOffloading; + // If at least one model requests parallel tracing, then use it + m_parallel |= configp->m_useParallel; + + if (VL_UNCOVERABLE(m_parallel && m_offload)) { // LCOV_EXCL_START + VL_FATAL_MT(__FILE__, __LINE__, "", "Cannot use parallel tracing with offloading"); + } // LCOV_EXCL_STOP + + // Configure format specific sub class + configure(*(configp.get())); } template <> @@ -620,36 +695,31 @@ void VerilatedTrace::addCallbackRecord(std::vector -void VerilatedTrace::addInitCb(initCb_t cb, void* userp, - VerilatedContext* contextp) VL_MT_SAFE { - addContext(contextp); +void VerilatedTrace::addInitCb(initCb_t cb, void* userp) VL_MT_SAFE { addCallbackRecord(m_initCbs, CallbackRecord{cb, userp}); } template <> -void VerilatedTrace::addFullCb(dumpCb_t cb, void* userp, - VerilatedContext* contextp) VL_MT_SAFE { - addContext(contextp); +void VerilatedTrace::addFullCb(dumpCb_t cb, void* userp) VL_MT_SAFE { addCallbackRecord(m_fullCbs, CallbackRecord{cb, userp}); } template <> -void VerilatedTrace::addChgCb(dumpCb_t cb, void* userp, - VerilatedContext* contextp) VL_MT_SAFE { - addContext(contextp); +void VerilatedTrace::addFullCb(dumpOffloadCb_t cb, void* userp) VL_MT_SAFE { + addCallbackRecord(m_fullOffloadCbs, CallbackRecord{cb, userp}); +} +template <> +void VerilatedTrace::addChgCb(dumpCb_t cb, void* userp) VL_MT_SAFE { addCallbackRecord(m_chgCbs, CallbackRecord{cb, userp}); } template <> -void VerilatedTrace::addCleanupCb(cleanupCb_t cb, void* userp, - VerilatedContext* contextp) VL_MT_SAFE { - addContext(contextp); +void VerilatedTrace::addChgCb(dumpOffloadCb_t cb, void* userp) VL_MT_SAFE { + addCallbackRecord(m_chgOffloadCbs, CallbackRecord{cb, userp}); +} +template <> +void VerilatedTrace::addCleanupCb(cleanupCb_t cb, void* userp) VL_MT_SAFE { addCallbackRecord(m_cleanupCbs, CallbackRecord{cb, userp}); } @@ -756,20 +826,10 @@ static inline void cvtQDataToStr(char* dstp, QData value) { // VerilatedTraceBuffer template <> // -VerilatedTraceBuffer::VerilatedTraceBuffer(VL_SUB_T& owner) - : m_owner{owner} { -#ifdef VL_TRACE_OFFLOAD - if (m_offloadBufferWritep) { - using This = VerilatedTraceBuffer*; - // Tack on the buffer address - static_assert(2 * sizeof(uint32_t) >= sizeof(This), - "This should be enough on all plafrorms"); - *m_offloadBufferWritep++ = VerilatedTraceOffloadCommand::TRACE_BUFFER; - *reinterpret_cast(m_offloadBufferWritep) = this; - m_offloadBufferWritep += 2; - } -#endif -} +VerilatedTraceBuffer::VerilatedTraceBuffer(Trace& owner) + : VL_BUF_T{owner} + , m_sigs_oldvalp{owner.m_sigs_oldvalp} + , m_sigs_enabledp{owner.m_sigs_enabledp} {} // These functions must write the new value back into the old value store, // and subsequently call the format specific emit* implementations. Note @@ -777,61 +837,81 @@ VerilatedTraceBuffer::VerilatedTraceBuffer(VL_SUB_T& owner) // the emit* functions can be inlined for performance. template <> // -void VerilatedTraceBuffer::fullBit(uint32_t* oldp, CData newval) { +void VerilatedTraceBuffer::fullBit(uint32_t* oldp, CData newval) { const uint32_t code = oldp - m_sigs_oldvalp; *oldp = newval; // Still copy even if not tracing so chg doesn't call full if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return; - self()->emitBit(code, newval); + emitBit(code, newval); } template <> -void VerilatedTraceBuffer::fullCData(uint32_t* oldp, CData newval, int bits) { +void VerilatedTraceBuffer::fullCData(uint32_t* oldp, CData newval, int bits) { const uint32_t code = oldp - m_sigs_oldvalp; *oldp = newval; // Still copy even if not tracing so chg doesn't call full if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return; - self()->emitCData(code, newval, bits); + emitCData(code, newval, bits); } template <> -void VerilatedTraceBuffer::fullSData(uint32_t* oldp, SData newval, int bits) { +void VerilatedTraceBuffer::fullSData(uint32_t* oldp, SData newval, int bits) { const uint32_t code = oldp - m_sigs_oldvalp; *oldp = newval; // Still copy even if not tracing so chg doesn't call full if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return; - self()->emitSData(code, newval, bits); + emitSData(code, newval, bits); } template <> -void VerilatedTraceBuffer::fullIData(uint32_t* oldp, IData newval, int bits) { +void VerilatedTraceBuffer::fullIData(uint32_t* oldp, IData newval, int bits) { const uint32_t code = oldp - m_sigs_oldvalp; *oldp = newval; // Still copy even if not tracing so chg doesn't call full if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return; - self()->emitIData(code, newval, bits); + emitIData(code, newval, bits); } template <> -void VerilatedTraceBuffer::fullQData(uint32_t* oldp, QData newval, int bits) { +void VerilatedTraceBuffer::fullQData(uint32_t* oldp, QData newval, int bits) { const uint32_t code = oldp - m_sigs_oldvalp; *reinterpret_cast(oldp) = newval; if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return; - self()->emitQData(code, newval, bits); + emitQData(code, newval, bits); } template <> -void VerilatedTraceBuffer::fullWData(uint32_t* oldp, const WData* newvalp, - int bits) { +void VerilatedTraceBuffer::fullWData(uint32_t* oldp, const WData* newvalp, int bits) { const uint32_t code = oldp - m_sigs_oldvalp; for (int i = 0; i < VL_WORDS_I(bits); ++i) oldp[i] = newvalp[i]; if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return; - self()->emitWData(code, newvalp, bits); + emitWData(code, newvalp, bits); } -template <> -void VerilatedTraceBuffer::fullDouble(uint32_t* oldp, double newval) { +template <> // +void VerilatedTraceBuffer::fullDouble(uint32_t* oldp, double newval) { const uint32_t code = oldp - m_sigs_oldvalp; *reinterpret_cast(oldp) = newval; if (VL_UNLIKELY(m_sigs_enabledp && !(VL_BITISSET_W(m_sigs_enabledp, code)))) return; // cppcheck-suppress invalidPointerCast - self()->emitDouble(code, newval); + emitDouble(code, newval); } +#ifdef VL_THREADED +//========================================================================= +// VerilatedTraceOffloadBuffer + +template <> // +VerilatedTraceOffloadBuffer::VerilatedTraceOffloadBuffer(VL_SUB_T& owner) + : VerilatedTraceBuffer{owner} + , m_offloadBufferWritep{owner.m_offloadBufferWritep} + , m_offloadBufferEndp{owner.m_offloadBufferEndp} { + if (m_offloadBufferWritep) { + using This = VerilatedTraceBuffer*; + // Tack on the buffer address + static_assert(2 * sizeof(uint32_t) >= sizeof(This), + "This should be enough on all plafrorms"); + *m_offloadBufferWritep++ = VerilatedTraceOffloadCommand::TRACE_BUFFER; + *reinterpret_cast(m_offloadBufferWritep) = static_cast(this); + m_offloadBufferWritep += 2; + } +} +#endif + #endif // VL_CPPCHECK diff --git a/include/verilated_vcd_c.cpp b/include/verilated_vcd_c.cpp index 9b01ea4ce..342ba7b2c 100644 --- a/include/verilated_vcd_c.cpp +++ b/include/verilated_vcd_c.cpp @@ -229,9 +229,11 @@ VerilatedVcd::~VerilatedVcd() { if (m_wrBufp) VL_DO_CLEAR(delete[] m_wrBufp, m_wrBufp = nullptr); deleteNameMap(); if (m_filep && m_fileNewed) VL_DO_CLEAR(delete m_filep, m_filep = nullptr); -#ifdef VL_TRACE_PARALLEL - assert(m_numBuffers == m_freeBuffers.size()); - for (auto& pair : m_freeBuffers) VL_DO_CLEAR(delete[] pair.first, pair.first = nullptr); +#ifdef VL_THREADED + if (parallel()) { + assert(m_numBuffers == m_freeBuffers.size()); + for (auto& pair : m_freeBuffers) VL_DO_CLEAR(delete[] pair.first, pair.first = nullptr); + } #endif } @@ -260,11 +262,6 @@ void VerilatedVcd::close() VL_MT_SAFE_EXCLUDES(m_mutex) { // This function is on the flush() call path const VerilatedLockGuard lock{m_mutex}; if (!isOpen()) return; - if (m_evcd) { - printStr("$vcdclose "); - printQuad(timeLastDump()); - printStr(" $end\n"); - } closePrev(); // closePrev() called Super::flush(), so we just // need to shut down the tracing thread here. @@ -514,38 +511,29 @@ void VerilatedVcd::declare(uint32_t code, const char* name, const char* wirep, b // Print reference std::string decl = "$var "; - if (m_evcd) { - decl += "port"; - } else { - decl += wirep; // usually "wire" - } + decl += wirep; // usually "wire" constexpr size_t bufsize = 1000; char buf[bufsize]; VL_SNPRINTF(buf, bufsize, " %2d ", bits); decl += buf; - if (m_evcd) { - VL_SNPRINTF(buf, bufsize, "<%u", code); - decl += buf; - } else { - // Add string code to decl - char* const endp = writeCode(buf, code); - *endp = '\0'; - decl += buf; - // Build suffix array entry - char* const entryp = &m_suffixes[code * VL_TRACE_SUFFIX_ENTRY_SIZE]; - const size_t length = endp - buf; - assert(length <= VL_TRACE_MAX_VCD_CODE_SIZE); - // 1 bit values don't have a ' ' separator between value and string code - const bool isBit = bits == 1; - entryp[0] = ' '; // Separator - // Use memcpy as we checked size above, and strcpy is flagged unsafe - std::memcpy(entryp + !isBit, buf, - std::strlen(buf)); // Code (overwrite separator if isBit) - entryp[length + !isBit] = '\n'; // Replace '\0' with line termination '\n' - // Set length of suffix (used to increment write pointer) - entryp[VL_TRACE_SUFFIX_ENTRY_SIZE - 1] = !isBit + length + 1; - } + // Add string code to decl + char* const endp = writeCode(buf, code); + *endp = '\0'; + decl += buf; + // Build suffix array entry + char* const entryp = &m_suffixes[code * VL_TRACE_SUFFIX_ENTRY_SIZE]; + const size_t length = endp - buf; + assert(length <= VL_TRACE_MAX_VCD_CODE_SIZE); + // 1 bit values don't have a ' ' separator between value and string code + const bool isBit = bits == 1; + entryp[0] = ' '; // Separator + // Use memcpy as we checked size above, and strcpy is flagged unsafe + std::memcpy(entryp + !isBit, buf, + std::strlen(buf)); // Code (overwrite separator if isBit) + entryp[length + !isBit] = '\n'; // Replace '\0' with line termination '\n' + // Set length of suffix (used to increment write pointer) + entryp[VL_TRACE_SUFFIX_ENTRY_SIZE - 1] = !isBit + length + 1; decl += " "; decl += basename; if (array) { @@ -583,67 +571,63 @@ void VerilatedVcd::declDouble(uint32_t code, const char* name, bool array, int a //============================================================================= // Get/commit trace buffer -VerilatedVcdBuffer* VerilatedVcd::getTraceBuffer() { -#ifdef VL_TRACE_PARALLEL - // Note: This is called from VeriltedVcd::dump, which already holds the lock - // If no buffer available, allocate a new one - if (m_freeBuffers.empty()) { - constexpr size_t pageSize = 4096; - // 4 * m_maxSignalBytes, so we can reserve 2 * m_maxSignalBytes at the end for safety - size_t startingSize = roundUpToMultipleOf(4 * m_maxSignalBytes); - m_freeBuffers.emplace_back(new char[startingSize], startingSize); - ++m_numBuffers; +VerilatedVcd::Buffer* VerilatedVcd::getTraceBuffer() { + VerilatedVcd::Buffer* const bufp = new Buffer{*this}; +#ifdef VL_THREADED + if (parallel()) { + // Note: This is called from VeriltedVcd::dump, which already holds the lock + // If no buffer available, allocate a new one + if (m_freeBuffers.empty()) { + constexpr size_t pageSize = 4096; + // 4 * m_maxSignalBytes, so we can reserve 2 * m_maxSignalBytes at the end for safety + size_t startingSize = roundUpToMultipleOf(4 * m_maxSignalBytes); + m_freeBuffers.emplace_back(new char[startingSize], startingSize); + ++m_numBuffers; + } + // Grab a buffer + const auto pair = m_freeBuffers.back(); + m_freeBuffers.pop_back(); + // Initialize + bufp->m_writep = bufp->m_bufp = pair.first; + bufp->m_size = pair.second; + bufp->adjustGrowp(); } - // Grab a buffer - const auto pair = m_freeBuffers.back(); - m_freeBuffers.pop_back(); - // Return the buffer - return new VerilatedVcdBuffer{*this, pair.first, pair.second}; -#else - return new VerilatedVcdBuffer{*this}; #endif + // Return the buffer + return bufp; } -void VerilatedVcd::commitTraceBuffer(VerilatedVcdBuffer* bufp) { -#ifdef VL_TRACE_PARALLEL - // Note: This is called from VeriltedVcd::dump, which already holds the lock - // Resize output buffer. Note, we use the full size of the trace buffer, as - // this is a lot more stable than the actual occupancy of the trace buffer. - // This helps us to avoid re-allocations due to small size changes. - bufferResize(bufp->m_size); - // Compute occupancy of buffer - const size_t usedSize = bufp->m_writep - bufp->m_bufp; - // Copy to output buffer - std::memcpy(m_writep, bufp->m_bufp, usedSize); - // Adjust write pointer - m_writep += usedSize; - // Flush if necessary - bufferCheck(); - // Put buffer back on free list - m_freeBuffers.emplace_back(bufp->m_bufp, bufp->m_size); +void VerilatedVcd::commitTraceBuffer(VerilatedVcd::Buffer* bufp) { + if (parallel()) { +#if VL_THREADED + // Note: This is called from VeriltedVcd::dump, which already holds the lock + // Resize output buffer. Note, we use the full size of the trace buffer, as + // this is a lot more stable than the actual occupancy of the trace buffer. + // This helps us to avoid re-allocations due to small size changes. + bufferResize(bufp->m_size); + // Compute occupancy of buffer + const size_t usedSize = bufp->m_writep - bufp->m_bufp; + // Copy to output buffer + std::memcpy(m_writep, bufp->m_bufp, usedSize); + // Adjust write pointer + m_writep += usedSize; + // Flush if necessary + bufferCheck(); + // Put buffer back on free list + m_freeBuffers.emplace_back(bufp->m_bufp, bufp->m_size); #else - // Needs adjusting for emitTimeChange - m_writep = bufp->m_writep; + VL_FATAL_MT(__FILE__, __LINE__, "", "Unreachable"); #endif + } else { + // Needs adjusting for emitTimeChange + m_writep = bufp->m_writep; + } delete bufp; } //============================================================================= // VerilatedVcdBuffer implementation -#ifdef VL_TRACE_PARALLEL -VerilatedVcdBuffer::VerilatedVcdBuffer(VerilatedVcd& owner, char* bufp, size_t size) - : VerilatedTraceBuffer{owner} - , m_writep{bufp} - , m_bufp{bufp} - , m_size{size} { - adjustGrowp(); -} -#else -VerilatedVcdBuffer::VerilatedVcdBuffer(VerilatedVcd& owner) - : VerilatedTraceBuffer{owner} {} -#endif - //============================================================================= // Trace rendering primitives @@ -679,35 +663,39 @@ void VerilatedVcdBuffer::finishLine(uint32_t code, char* writep) { // suffix, which was stored in the last byte of the suffix buffer entry. m_writep = writep + suffixp[VL_TRACE_SUFFIX_ENTRY_SIZE - 1]; -#ifdef VL_TRACE_PARALLEL - // Double the size of the buffer if necessary - if (VL_UNLIKELY(m_writep >= m_growp)) { - // Compute occupied size of current buffer - const size_t usedSize = m_writep - m_bufp; - // We are always doubling the size - m_size *= 2; - // Allocate the new buffer - char* const newBufp = new char[m_size]; - // Copy from current buffer to new buffer - std::memcpy(newBufp, m_bufp, usedSize); - // Delete current buffer - delete[] m_bufp; - // Make new buffer the current buffer - m_bufp = newBufp; - // Adjust write pointer - m_writep = m_bufp + usedSize; - // Adjust resize limit - adjustGrowp(); - } + if (m_owner.parallel()) { +#ifdef VL_THREADED + // Double the size of the buffer if necessary + if (VL_UNLIKELY(m_writep >= m_growp)) { + // Compute occupied size of current buffer + const size_t usedSize = m_writep - m_bufp; + // We are always doubling the size + m_size *= 2; + // Allocate the new buffer + char* const newBufp = new char[m_size]; + // Copy from current buffer to new buffer + std::memcpy(newBufp, m_bufp, usedSize); + // Delete current buffer + delete[] m_bufp; + // Make new buffer the current buffer + m_bufp = newBufp; + // Adjust write pointer + m_writep = m_bufp + usedSize; + // Adjust resize limit + adjustGrowp(); + } #else - // Flush the write buffer if there's not enough space left for new information - // We only call this once per vector, so we need enough slop for a very wide "b###" line - if (VL_UNLIKELY(m_writep > m_wrFlushp)) { - m_owner.m_writep = m_writep; - m_owner.bufferFlush(); - m_writep = m_owner.m_writep; - } + VL_FATAL_MT(__FILE__, __LINE__, "", "Unreachable"); #endif + } else { + // Flush the write buffer if there's not enough space left for new information + // We only call this once per vector, so we need enough slop for a very wide "b###" line + if (VL_UNLIKELY(m_writep > m_wrFlushp)) { + m_owner.m_writep = m_writep; + m_owner.bufferFlush(); + m_writep = m_owner.m_writep; + } + } } //============================================================================= diff --git a/include/verilated_vcd_c.h b/include/verilated_vcd_c.h index 0d83eb25d..20c5440f5 100644 --- a/include/verilated_vcd_c.h +++ b/include/verilated_vcd_c.h @@ -41,7 +41,7 @@ public: using Super = VerilatedTrace; private: - friend Buffer; // Give the buffer access to the private bits + friend VerilatedVcdBuffer; // Give the buffer access to the private bits //========================================================================= // VCD specific internals @@ -49,7 +49,6 @@ private: VerilatedVcdFile* m_filep; // File we're writing to bool m_fileNewed; // m_filep needs destruction bool m_isOpen = false; // True indicates open file - bool m_evcd = false; // True for evcd format std::string m_filename; // Filename we're writing to (if open) uint64_t m_rolloverMB = 0; // MB of file size to rollover at int m_modDepth = 0; // Depth of module hierarchy @@ -66,7 +65,7 @@ private: using NameMap = std::map; NameMap* m_namemapp = nullptr; // List of names for the header -#ifdef VL_TRACE_PARALLEL +#ifdef VL_THREADED // Vector of free trace buffers as (pointer, size) pairs. std::vector> m_freeBuffers; size_t m_numBuffers = 0; // Number of trace buffers allocated @@ -110,8 +109,11 @@ protected: virtual bool preChangeDump() override; // Trace buffer management - virtual VerilatedVcdBuffer* getTraceBuffer() override; - virtual void commitTraceBuffer(VerilatedVcdBuffer*) override; + virtual Buffer* getTraceBuffer() override; + virtual void commitTraceBuffer(Buffer*) override; + + // Configure sub-class + virtual void configure(const VerilatedTraceConfig&) override { return; }; public: //========================================================================= @@ -160,47 +162,46 @@ template <> void VerilatedVcd::Super::dumpvars(int level, const std::string& hie //============================================================================= // VerilatedVcdBuffer -class VerilatedVcdBuffer final : public VerilatedTraceBuffer { - // Give the trace file access to the private bits +class VerilatedVcdBuffer VL_NOT_FINAL { + // Give the trace file ans sub-classes access to the private bits friend VerilatedVcd; friend VerilatedVcd::Super; + friend VerilatedVcd::Buffer; + friend VerilatedVcd::OffloadBuffer; -#ifdef VL_TRACE_PARALLEL - char* m_writep; // Write pointer into m_bufp - char* m_bufp; // The beginning of the trace buffer - size_t m_size; // The size of the buffer at m_bufp - char* m_growp; // Resize limit pointer -#else - char* m_writep = m_owner.m_writep; // Write pointer into output buffer - char* const m_wrFlushp = m_owner.m_wrFlushp; // Output buffer flush trigger location -#endif + VerilatedVcd& m_owner; // Trace file owning this buffer. Required by subclasses. + + // Write pointer into output buffer (in parallel mode, this is set up in 'getTraceBuffer') + char* m_writep = m_owner.parallel() ? nullptr : m_owner.m_writep; + // Output buffer flush trigger location (only used when not parallel) + char* const m_wrFlushp = m_owner.parallel() ? nullptr : m_owner.m_wrFlushp; // VCD line end string codes + metadata const char* const m_suffixes = m_owner.m_suffixes.data(); // The maximum number of bytes a single signal can emit const size_t m_maxSignalBytes = m_owner.m_maxSignalBytes; - void finishLine(uint32_t code, char* writep); +#ifdef VL_THREADED + // Additional data for parallel tracing only + char* m_bufp = nullptr; // The beginning of the trace buffer + size_t m_size = 0; // The size of the buffer at m_bufp + char* m_growp = nullptr; // Resize limit pointer -#ifdef VL_TRACE_PARALLEL void adjustGrowp() { m_growp = (m_bufp + m_size) - (2 * m_maxSignalBytes); assert(m_growp >= m_bufp + m_maxSignalBytes); } #endif -public: + void finishLine(uint32_t code, char* writep); + // CONSTRUCTOR -#ifdef VL_TRACE_PARALLEL - explicit VerilatedVcdBuffer(VerilatedVcd& owner, char* bufp, size_t size); -#else - explicit VerilatedVcdBuffer(VerilatedVcd& owner); -#endif - ~VerilatedVcdBuffer() = default; + explicit VerilatedVcdBuffer(VerilatedVcd& owner) + : m_owner{owner} {} + virtual ~VerilatedVcdBuffer() = default; //========================================================================= // Implementation of VerilatedTraceBuffer interface - // Implementations of duck-typed methods for VerilatedTraceBuffer. These are // called from only one place (the full* methods), so always inline them. VL_ATTR_ALWINLINE inline void emitBit(uint32_t code, CData newval); diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 5518a98a8..9a6e7fca8 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -674,10 +674,10 @@ void AstNode::addHereThisAsNext(AstNode* newp) { tailp->m_headtailp = newp; } // Iterator fixup - if (newLastp->m_iterpp) { - *(newLastp->m_iterpp) = this; - } else if (this->m_iterpp) { + if (newLastp->m_iterpp) *(newLastp->m_iterpp) = this; + if (this->m_iterpp) { *(this->m_iterpp) = newp; + this->m_iterpp = nullptr; } // debugTreeChange(this, "-addHereThisAsNext: ", __LINE__, true); diff --git a/src/V3Ast.h b/src/V3Ast.h index 3b98ec47b..739857574 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -1728,13 +1728,13 @@ public: void dtypeSetVoid() { dtypep(findVoidDType()); } // Data type locators - AstNodeDType* findBitDType() { return findBasicDType(VBasicDTypeKwd::LOGIC); } - AstNodeDType* findDoubleDType() { return findBasicDType(VBasicDTypeKwd::DOUBLE); } - AstNodeDType* findStringDType() { return findBasicDType(VBasicDTypeKwd::STRING); } - AstNodeDType* findSigned32DType() { return findBasicDType(VBasicDTypeKwd::INTEGER); } - AstNodeDType* findUInt32DType() { return findBasicDType(VBasicDTypeKwd::UINT32); } - AstNodeDType* findUInt64DType() { return findBasicDType(VBasicDTypeKwd::UINT64); } - AstNodeDType* findCHandleDType() { return findBasicDType(VBasicDTypeKwd::CHANDLE); } + AstNodeDType* findBitDType() const { return findBasicDType(VBasicDTypeKwd::LOGIC); } + AstNodeDType* findDoubleDType() const { return findBasicDType(VBasicDTypeKwd::DOUBLE); } + AstNodeDType* findStringDType() const { return findBasicDType(VBasicDTypeKwd::STRING); } + AstNodeDType* findSigned32DType() const { return findBasicDType(VBasicDTypeKwd::INTEGER); } + AstNodeDType* findUInt32DType() const { return findBasicDType(VBasicDTypeKwd::UINT32); } + AstNodeDType* findUInt64DType() const { return findBasicDType(VBasicDTypeKwd::UINT64); } + AstNodeDType* findCHandleDType() const { return findBasicDType(VBasicDTypeKwd::CHANDLE); } AstNodeDType* findEmptyQueueDType() const; AstNodeDType* findVoidDType() const; AstNodeDType* findQueueIndexDType() const; diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 470b53500..f68c95fde 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -673,6 +673,9 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const { const CTypeRecursed key = adtypep->keyDTypep()->cTypeRecurse(true); const CTypeRecursed val = adtypep->subDTypep()->cTypeRecurse(true); info.m_type = "VlAssocArray<" + key.m_type + ", " + val.m_type + ">"; + } else if (const auto* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) { + const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true); + info.m_type = "VlAssocArray"; } else if (const auto* const adtypep = VN_CAST(dtypep, DynArrayDType)) { const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true); info.m_type = "VlQueue<" + sub.m_type + ">"; @@ -1724,6 +1727,10 @@ string AstQueueDType::prettyDTypeName() const { if (boundConst()) str += ":" + cvtToStr(boundConst()); return str + "]"; } +void AstWildcardArrayDType::dumpSmall(std::ostream& str) const { + this->AstNodeDType::dumpSmall(str); + str << "[*]"; +} void AstUnsizedArrayDType::dumpSmall(std::ostream& str) const { this->AstNodeDType::dumpSmall(str); str << "[]"; diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index ec215b54c..a9dd99dcf 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -278,6 +278,17 @@ public: virtual bool same(const AstNode* samep) const override { return true; } }; +class AstWildcardRange final : public AstNodeRange { + // Wildcard range specification, for wildcard index type associative arrays +public: + explicit AstWildcardRange(FileLine* fl) + : ASTGEN_SUPER_WildcardRange(fl) {} + ASTNODE_NODE_FUNCS(WildcardRange) + virtual string emitC() { V3ERROR_NA_RETURN(""); } + virtual string emitVerilog() { return "[*]"; } + virtual bool same(const AstNode* samep) const override { return true; } +}; + class AstGatePin final : public AstNodeMath { // Possibly expand a gate primitive input pin value to match the range of the gate primitive public: @@ -819,6 +830,62 @@ public: virtual bool isCompound() const override { return true; } }; +class AstWildcardArrayDType final : public AstNodeDType { + // Wildcard index type associative array data type, ie "some_dtype var_name [*]" + // Children: DTYPE (moved to refDTypep() in V3Width) +private: + AstNodeDType* m_refDTypep; // Elements of this type (after widthing) +public: + AstWildcardArrayDType(FileLine* fl, VFlagChildDType, AstNodeDType* dtp) + : ASTGEN_SUPER_WildcardArrayDType(fl) { + childDTypep(dtp); // Only for parser + refDTypep(nullptr); + dtypep(nullptr); // V3Width will resolve + } + ASTNODE_NODE_FUNCS(WildcardArrayDType) + virtual const char* broken() const override { + BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists()) + || (!m_refDTypep && childDTypep()))); + return nullptr; + } + virtual void cloneRelink() override { + if (m_refDTypep && m_refDTypep->clonep()) m_refDTypep = m_refDTypep->clonep(); + } + virtual bool same(const AstNode* samep) const override { + const AstNodeArrayDType* const asamep = static_cast(samep); + if (!asamep->subDTypep()) return false; + return (subDTypep() == asamep->subDTypep()); + } + virtual bool similarDType(AstNodeDType* samep) const override { + const AstNodeArrayDType* const asamep = static_cast(samep); + return type() == samep->type() && asamep->subDTypep() + && subDTypep()->skipRefp()->similarDType(asamep->subDTypep()->skipRefp()); + } + virtual void dumpSmall(std::ostream& str) const override; + virtual AstNodeDType* getChildDTypep() const override { return childDTypep(); } + // op1 = Range of variable + AstNodeDType* childDTypep() const { return VN_AS(op1p(), NodeDType); } + void childDTypep(AstNodeDType* nodep) { setOp1p(nodep); } + virtual AstNodeDType* subDTypep() const override { + return m_refDTypep ? m_refDTypep : childDTypep(); + } + void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; } + virtual AstNodeDType* virtRefDTypep() const override { return m_refDTypep; } + virtual void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); } + // METHODS + virtual AstBasicDType* basicp() const override { return subDTypep()->basicp(); } + virtual AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; } + virtual AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; } + virtual AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; } + virtual int widthAlignBytes() const override { + return sizeof(std::map); + } + virtual int widthTotalBytes() const override { + return sizeof(std::map); + } + virtual bool isCompound() const override { return true; } +}; + class AstBasicDType final : public AstNodeDType { // Builtin atomic/vectored data type // Children: RANGE (converted to constant in V3Width) @@ -1687,6 +1754,44 @@ public: virtual int instrCount() const override { return widthInstrs(); } }; +class AstWildcardSel final : public AstNodeSel { + // Parents: math|stmt + // Children: varref|arraysel, math +private: + void init(AstNode* fromp) { + if (fromp && VN_IS(fromp->dtypep()->skipRefp(), WildcardArrayDType)) { + // Strip off array to find what array references + dtypeFrom(VN_AS(fromp->dtypep()->skipRefp(), WildcardArrayDType)->subDTypep()); + } + } + +public: + AstWildcardSel(FileLine* fl, AstNode* fromp, AstNode* bitp) + : ASTGEN_SUPER_WildcardSel(fl, fromp, bitp) { + init(fromp); + } + ASTNODE_NODE_FUNCS(WildcardSel) + virtual AstNode* cloneType(AstNode* lhsp, AstNode* rhsp) override { + return new AstWildcardSel{this->fileline(), lhsp, rhsp}; + } + virtual void numberOperate(V3Number& out, const V3Number& lhs, const V3Number& rhs) override { + V3ERROR_NA; + } + virtual string emitVerilog() override { return "%k(%l%f[%r])"; } + virtual string emitC() override { return "%li%k[%ri]"; } + virtual bool cleanOut() const override { return true; } + virtual bool cleanLhs() const override { return false; } + virtual bool cleanRhs() const override { return true; } + virtual bool sizeMattersLhs() const override { return false; } + virtual bool sizeMattersRhs() const override { return false; } + virtual bool isGateOptimizable() const override { + return true; + } // esp for V3Const::ifSameAssign + virtual bool isPredictOptimizable() const override { return false; } + virtual bool same(const AstNode* samep) const override { return true; } + virtual int instrCount() const override { return widthInstrs(); } +}; + class AstWordSel final : public AstNodeSel { // Select a single word from a multi-word wide value public: @@ -4879,6 +4984,47 @@ public: virtual bool same(const AstNode* samep) const override { return true; } }; +class AstConsWildcard final : public AstNodeMath { + // Construct a wildcard assoc array and return object, '{} + // Parents: math + // Children: expression (elements or other queues) +public: + AstConsWildcard(FileLine* fl, AstNode* defaultp) + : ASTGEN_SUPER_ConsWildcard(fl) { + setNOp1p(defaultp); + } + ASTNODE_NODE_FUNCS(ConsWildcard) + virtual string emitVerilog() override { return "'{}"; } + virtual string emitC() override { V3ERROR_NA_RETURN(""); } + virtual string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } + virtual bool cleanOut() const override { return true; } + virtual int instrCount() const override { return widthInstrs(); } + AstNode* defaultp() const { return op1p(); } + virtual bool same(const AstNode* samep) const override { return true; } +}; +class AstSetWildcard final : public AstNodeMath { + // Set a wildcard assoc array element and return object, '{} + // Parents: math + // Children: expression (elements or other queues) +public: + AstSetWildcard(FileLine* fl, AstNode* lhsp, AstNode* keyp, AstNode* valuep) + : ASTGEN_SUPER_SetWildcard(fl) { + setOp1p(lhsp); + setNOp2p(keyp); + setOp3p(valuep); + } + ASTNODE_NODE_FUNCS(SetWildcard) + virtual string emitVerilog() override { return "'{}"; } + virtual string emitC() override { V3ERROR_NA_RETURN(""); } + virtual string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } + virtual bool cleanOut() const override { return true; } + virtual int instrCount() const override { return widthInstrs(); } + AstNode* lhsp() const { return op1p(); } + AstNode* keyp() const { return op2p(); } + AstNode* valuep() const { return op3p(); } + virtual bool same(const AstNode* samep) const override { return true; } +}; + class AstConsDynArray final : public AstNodeMath { // Construct a queue and return object, '{}. '{lhs}, '{lhs. rhs} // Parents: math diff --git a/src/V3Clean.cpp b/src/V3Clean.cpp index 613b681d7..eaf7c1f39 100644 --- a/src/V3Clean.cpp +++ b/src/V3Clean.cpp @@ -88,6 +88,7 @@ private: if (VN_IS(nodep, Var) || VN_IS(nodep, NodeDType) // Don't want to change variable widths! || VN_IS(nodep->dtypep()->skipRefp(), AssocArrayDType) // Or arrays + || VN_IS(nodep->dtypep()->skipRefp(), WildcardArrayDType) || VN_IS(nodep->dtypep()->skipRefp(), DynArrayDType) || VN_IS(nodep->dtypep()->skipRefp(), ClassRefDType) || VN_IS(nodep->dtypep()->skipRefp(), QueueDType) diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 03a62d491..8112135c8 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -611,6 +611,17 @@ void EmitCFunc::emitVarReset(AstVar* varp) { emitSetVarConstant(varNameProtected + ".at(" + cvtToStr(itr.first) + ")", VN_AS(valuep, Const)); } + } else if (AstWildcardArrayDType* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) { + if (initarp->defaultp()) { + emitSetVarConstant(varNameProtected + ".atDefault()", + VN_AS(initarp->defaultp(), Const)); + } + const auto& mapr = initarp->map(); + for (const auto& itr : mapr) { + AstNode* const valuep = itr.second->valuep(); + emitSetVarConstant(varNameProtected + ".at(" + cvtToStr(itr.first) + ")", + VN_AS(valuep, Const)); + } } else if (AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { if (initarp->defaultp()) { puts("for (int __Vi=0; __Vi<" + cvtToStr(adtypep->elementsConst())); @@ -642,6 +653,11 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); return emitVarResetRecurse(varp, varNameProtected, adtypep->subDTypep(), depth + 1, suffix + ".atDefault()" + cvtarray); + } else if (AstWildcardArrayDType* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) { + // Access std::array as C array + const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); + return emitVarResetRecurse(varp, varNameProtected, adtypep->subDTypep(), depth + 1, + suffix + ".atDefault()" + cvtarray); } else if (VN_IS(dtypep, ClassRefDType)) { return ""; // Constructor does it } else if (const AstDynArrayDType* const adtypep = VN_CAST(dtypep, DynArrayDType)) { diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index da8a127f4..9e528807d 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -371,6 +371,14 @@ public: } puts(")"); } + virtual void visit(AstWildcardSel* nodep) override { + iterateAndNextNull(nodep->fromp()); + putbs(".at("); + AstWildcardArrayDType* const adtypep = VN_AS(nodep->fromp()->dtypep(), WildcardArrayDType); + UASSERT_OBJ(adtypep, nodep, "Wildcard select on non-wildcard-associative type"); + iterateAndNextNull(nodep->bitp()); + puts(")"); + } virtual void visit(AstCCall* nodep) override { const AstCFunc* const funcp = nodep->funcp(); const AstNodeModule* const funcModp = EmitCParentModule::get(funcp); @@ -1181,6 +1189,24 @@ public: iterateAndNextNull(nodep->valuep()); puts(")"); } + virtual void visit(AstConsWildcard* nodep) override { + putbs(nodep->dtypep()->cType("", false, false)); + puts("()"); + if (nodep->defaultp()) { + putbs(".setDefault("); + iterateAndNextNull(nodep->defaultp()); + puts(")"); + } + } + virtual void visit(AstSetWildcard* nodep) override { + iterateAndNextNull(nodep->lhsp()); + putbs(".set("); + iterateAndNextNull(nodep->keyp()); + puts(", "); + putbs(""); + iterateAndNextNull(nodep->valuep()); + puts(")"); + } virtual void visit(AstConsDynArray* nodep) override { putbs(nodep->dtypep()->cType("", false, false)); if (!nodep->lhsp()) { diff --git a/src/V3EmitCMake.cpp b/src/V3EmitCMake.cpp index 710829eaf..1e8f1700e 100644 --- a/src/V3EmitCMake.cpp +++ b/src/V3EmitCMake.cpp @@ -113,12 +113,6 @@ class CMakeEmitter final { cmake_set_raw(*of, name + "_COVERAGE", v3Global.opt.coverage() ? "1" : "0"); *of << "# Threaded output mode? 0/1/N threads (from --threads)\n"; cmake_set_raw(*of, name + "_THREADS", cvtToStr(v3Global.opt.threads())); - *of << "# Threaded tracing output mode? 0/1/N threads (from --threads/--trace-threads)\n"; - cmake_set_raw(*of, name + "_TRACE_THREADS", cvtToStr(v3Global.opt.vmTraceThreads())); - cmake_set_raw(*of, name + "_TRACE_FST_WRITER_THREAD", - v3Global.opt.traceThreads() && v3Global.opt.traceFormat().fst() ? "1" : "0"); - *of << "# Struct output mode? 0/1 (from --trace-structs)\n"; - cmake_set_raw(*of, name + "_TRACE_STRUCTS", cvtToStr(v3Global.opt.traceStructs())); *of << "# VCD Tracing output mode? 0/1 (from --trace)\n"; cmake_set_raw(*of, name + "_TRACE_VCD", (v3Global.opt.trace() && v3Global.opt.traceFormat().vcd()) ? "1" : "0"); diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 92e3b8999..b8524e3cb 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -223,6 +223,9 @@ class EmitCModel final : public EmitCFunc { puts("const char* hierName() const override final;\n"); puts("const char* modelName() const override final;\n"); puts("unsigned threads() const override final;\n"); + if (v3Global.opt.trace()) { + puts("std::unique_ptr traceConfig() const override final;\n"); + } puts("} VL_ATTR_ALIGNED(VL_CACHE_LINE_BYTES);\n"); @@ -429,6 +432,17 @@ class EmitCModel final : public EmitCFunc { + "\"; }\n"); puts("unsigned " + topClassName() + "::threads() const { return " + cvtToStr(std::max(1, v3Global.opt.threads())) + "; }\n"); + + if (v3Global.opt.trace()) { + puts("std::unique_ptr " + topClassName() + + "::traceConfig() const {\n"); + puts("return std::unique_ptr{new VerilatedTraceConfig{"); + puts(v3Global.opt.useTraceParallel() ? "true" : "false"); + puts(v3Global.opt.useTraceOffload() ? ", true" : ", false"); + puts(v3Global.opt.useFstWriterThread() ? ", true" : ", false"); + puts("}};\n"); + puts("};\n"); + } } void emitTraceMethods(AstNodeModule* modp) { @@ -481,8 +495,8 @@ class EmitCModel final : public EmitCFunc { puts(/**/ "}"); } puts(/**/ "if (false && levels && options) {} // Prevent unused\n"); - puts(/**/ "tfp->spTrace()->addInitCb(&" + protect("trace_init") - + ", &(vlSymsp->TOP), contextp());\n"); + puts(/**/ "tfp->spTrace()->addModel(this);\n"); + puts(/**/ "tfp->spTrace()->addInitCb(&" + protect("trace_init") + ", &(vlSymsp->TOP));\n"); puts(/**/ topModNameProtected + "__" + protect("trace_register") + "(&(vlSymsp->TOP), tfp->spTrace());\n"); diff --git a/src/V3EmitMk.cpp b/src/V3EmitMk.cpp index 800cf589c..75dabd3ff 100644 --- a/src/V3EmitMk.cpp +++ b/src/V3EmitMk.cpp @@ -73,15 +73,6 @@ public: of.puts("VM_TRACE_FST = "); of.puts(v3Global.opt.trace() && v3Global.opt.traceFormat().fst() ? "1" : "0"); of.puts("\n"); - of.puts( - "# Tracing threaded output mode? 0/1/N threads (from --threads/--trace-thread)\n"); - of.puts("VM_TRACE_THREADS = "); - of.puts(cvtToStr(v3Global.opt.vmTraceThreads())); - of.puts("\n"); - of.puts("# Separate FST writer thread? 0/1 (from --trace-fst with --trace-thread > 0)\n"); - of.puts("VM_TRACE_FST_WRITER_THREAD = "); - of.puts(v3Global.opt.traceThreads() && v3Global.opt.traceFormat().fst() ? "1" : "0"); - of.puts("\n"); of.puts("\n### Object file lists...\n"); for (int support = 0; support < 3; ++support) { @@ -369,13 +360,13 @@ class EmitMkHierVerilation final { // Rules to process hierarchical blocks of.puts("\n# Verilate hierarchical blocks\n"); - for (V3HierBlockPlan::const_iterator it = m_planp->begin(); it != m_planp->end(); ++it) { - const string prefix = it->second->hierPrefix(); - const string argsFile = it->second->commandArgsFileName(false); - of.puts(it->second->hierGenerated(true)); + for (const V3HierBlock* const blockp : m_planp->hierBlocksSorted()) { + const string prefix = blockp->hierPrefix(); + const string argsFile = blockp->commandArgsFileName(false); + of.puts(blockp->hierGenerated(true)); of.puts(": $(VM_HIER_INPUT_FILES) $(VM_HIER_VERILOG_LIBS) "); of.puts(V3Os::filenameNonDir(argsFile) + " "); - const V3HierBlock::HierBlockSet& children = it->second->children(); + const V3HierBlock::HierBlockSet& children = blockp->children(); for (V3HierBlock::HierBlockSet::const_iterator child = children.begin(); child != children.end(); ++child) { of.puts((*child)->hierWrapper(true) + " "); @@ -384,16 +375,16 @@ class EmitMkHierVerilation final { emitLaunchVerilator(of, argsFile); // Rule to build lib*.a - of.puts(it->second->hierLib(true)); + of.puts(blockp->hierLib(true)); of.puts(": "); - of.puts(it->second->hierMk(true)); + of.puts(blockp->hierMk(true)); of.puts(" "); for (V3HierBlock::HierBlockSet::const_iterator child = children.begin(); child != children.end(); ++child) { of.puts((*child)->hierLib(true)); of.puts(" "); } - of.puts("\n\t$(MAKE) -f " + it->second->hierMk(false) + " -C " + prefix); + of.puts("\n\t$(MAKE) -f " + blockp->hierMk(false) + " -C " + prefix); of.puts(" VM_PREFIX=" + prefix); of.puts("\n\n"); } diff --git a/src/V3Hasher.cpp b/src/V3Hasher.cpp index 4b0cbbbba..ba59ea462 100644 --- a/src/V3Hasher.cpp +++ b/src/V3Hasher.cpp @@ -133,6 +133,11 @@ private: iterateNull(nodep->virtRefDTypep()); }); } + virtual void visit(AstWildcardArrayDType* nodep) override { + m_hash += hashNodeAndIterate(nodep, false, HASH_CHILDREN, [=]() { // + iterateNull(nodep->virtRefDTypep()); + }); + } virtual void visit(AstBasicDType* nodep) override { m_hash += hashNodeAndIterate(nodep, false, HASH_CHILDREN, [=]() { m_hash += nodep->keyword(); diff --git a/src/V3HierBlock.cpp b/src/V3HierBlock.cpp index 79701ba80..22006fa23 100644 --- a/src/V3HierBlock.cpp +++ b/src/V3HierBlock.cpp @@ -156,7 +156,7 @@ V3StringList V3HierBlock::commandArgs(bool forCMake) const { opts.push_back(" --lib-create " + modp()->name()); // possibly mangled name if (v3Global.opt.protectKeyProvided()) opts.push_back(" --protect-key " + v3Global.opt.protectKeyDefaulted()); - opts.push_back(" --hierarchical-child"); + opts.push_back(" --hierarchical-child " + cvtToStr(std::max(1, v3Global.opt.threads()))); const StrGParams gparamsStr = stringifyParams(gparams(), true); for (StrGParams::const_iterator paramIt = gparamsStr.begin(); paramIt != gparamsStr.end(); @@ -306,10 +306,6 @@ public: //###################################################################### -bool V3HierBlockPlan::isHierBlock(const AstNodeModule* modp) const { - return m_blocks.find(modp) != m_blocks.end(); -} - void V3HierBlockPlan::add(const AstNodeModule* modp, const std::vector& gparams) { const iterator it = m_blocks.find(modp); if (it == m_blocks.end()) { diff --git a/src/V3HierBlock.h b/src/V3HierBlock.h index 7a01dd1a2..c247dce52 100644 --- a/src/V3HierBlock.h +++ b/src/V3HierBlock.h @@ -109,7 +109,6 @@ public: using HierVector = std::vector; VL_DEBUG_FUNC; // Declare debug() - bool isHierBlock(const AstNodeModule* modp) const; void add(const AstNodeModule* modp, const std::vector& gparams); void registerUsage(const AstNodeModule* parentp, const AstNodeModule* childp); diff --git a/src/V3LinkCells.cpp b/src/V3LinkCells.cpp index 2645b521e..da4dabaa4 100644 --- a/src/V3LinkCells.cpp +++ b/src/V3LinkCells.cpp @@ -480,7 +480,7 @@ private: // mangled_name, BlockOptions const V3HierBlockOptSet& hierBlocks = v3Global.opt.hierBlocks(); const auto hierIt = vlstd::as_const(hierBlocks).find(v3Global.opt.topModule()); - UASSERT((hierIt != hierBlocks.end()) == v3Global.opt.hierChild(), + UASSERT((hierIt != hierBlocks.end()) == !!v3Global.opt.hierChild(), "information of the top module must exist if --hierarchical-child is set"); // Look at all modules, and store pointers to all module names for (AstNodeModule *nextp, *nodep = v3Global.rootp()->modulesp(); nodep; nodep = nextp) { diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 1e7da33b8..55011bf09 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1124,7 +1124,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, char const V3HierarchicalBlockOption opt(valp); m_hierBlocks.emplace(opt.mangledName(), opt); }); - DECL_OPTION("-hierarchical-child", OnOff, &m_hierChild); + DECL_OPTION("-hierarchical-child", Set, &m_hierChild); DECL_OPTION("-I", CbPartialMatch, [this, &optdir](const char* optp) { addIncDirUser(parseFileArg(optdir, optp)); }); diff --git a/src/V3Options.h b/src/V3Options.h index 6788c3f06..e9e6bef1b 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -241,7 +241,6 @@ private: bool m_exe = false; // main switch: --exe bool m_flatten = false; // main switch: --flatten bool m_hierarchical = false; // main switch: --hierarchical - bool m_hierChild = false; // main switch: --hierarchical-child bool m_ignc = false; // main switch: --ignc bool m_lintOnly = false; // main switch: --lint-only bool m_gmake = false; // main switch: --make gmake @@ -287,6 +286,7 @@ private: int m_dumpTree = 0; // main switch: --dump-tree int m_expandLimit = 64; // main switch: --expand-limit int m_gateStmts = 100; // main switch: --gate-stmts + int m_hierChild = 0; // main switch: --hierarchical-child int m_ifDepth = 0; // main switch: --if-depth int m_inlineMult = 2000; // main switch: --inline-mult int m_instrCountDpi = 200; // main switch: --instr-count-dpi @@ -516,7 +516,10 @@ public: int traceMaxWidth() const { return m_traceMaxWidth; } int traceThreads() const { return m_traceThreads; } bool useTraceOffload() const { return trace() && traceFormat().fst() && traceThreads() > 1; } - bool useTraceParallel() const { return trace() && traceFormat().vcd() && threads() > 1; } + bool useTraceParallel() const { + return trace() && traceFormat().vcd() && threads() && (threads() > 1 || hierChild() > 1); + } + bool useFstWriterThread() const { return traceThreads() && traceFormat().fst(); } unsigned vmTraceThreads() const { return useTraceParallel() ? threads() : useTraceOffload() ? 1 : 0; } @@ -603,7 +606,7 @@ public: } bool hierarchical() const { return m_hierarchical; } - bool hierChild() const { return m_hierChild; } + int hierChild() const { return m_hierChild; } bool hierTop() const { return !m_hierChild && !m_hierBlocks.empty(); } const V3HierBlockOptSet& hierBlocks() const { return m_hierBlocks; } // Directory to save .tree, .dot, .dat, .vpp for hierarchical block top diff --git a/src/V3ParseGrammar.cpp b/src/V3ParseGrammar.cpp index 2cc65af6a..f2d57f289 100644 --- a/src/V3ParseGrammar.cpp +++ b/src/V3ParseGrammar.cpp @@ -138,6 +138,8 @@ AstNodeDType* V3ParseGrammar::createArray(AstNodeDType* basep, AstNodeRange* nra AstNode* const keyp = arangep->elementsp()->unlinkFrBack(); arrayp = new AstBracketArrayDType(nrangep->fileline(), VFlagChildDType(), arrayp, keyp); + } else if (VN_IS(nrangep, WildcardRange)) { + arrayp = new AstWildcardArrayDType{nrangep->fileline(), VFlagChildDType{}, arrayp}; } else { UASSERT_OBJ(0, nrangep, "Expected range or unsized range"); } diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index 7113c5e0e..c8a665f3e 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -498,7 +498,9 @@ private: }; if (isTopFunc) { // Top functions - funcp->argTypes("void* voidSelf, " + v3Global.opt.traceClassBase() + "::Buffer* bufp"); + funcp->argTypes("void* voidSelf, " + v3Global.opt.traceClassBase() + + "::" + (v3Global.opt.useTraceOffload() ? "OffloadBuffer" : "Buffer") + + "* bufp"); addInitStr(voidSelfAssign(m_topModp)); addInitStr(symClassAssign()); // Add global activity check to change dump functions @@ -513,12 +515,12 @@ private: } m_regFuncp->addStmtsp(new AstAddrOfCFunc(flp, funcp)); m_regFuncp->addStmtsp(new AstText(flp, ", vlSelf", true)); - m_regFuncp->addStmtsp( - new AstText(flp, ", vlSelf->vlSymsp->__Vm_modelp->contextp()", true)); m_regFuncp->addStmtsp(new AstText(flp, ");\n", true)); } else { // Sub functions - funcp->argTypes(v3Global.opt.traceClassBase() + "::Buffer* bufp"); + funcp->argTypes(v3Global.opt.traceClassBase() + + "::" + +(v3Global.opt.useTraceOffload() ? "OffloadBuffer" : "Buffer") + + "* bufp"); // Setup base references. Note in rare occasions we can end up with an empty trace // sub function, hence the VL_ATTR_UNUSED attributes. if (full) { @@ -702,8 +704,7 @@ private: // Register it m_regFuncp->addStmtsp(new AstText(fl, "tracep->addCleanupCb(", true)); m_regFuncp->addStmtsp(new AstAddrOfCFunc(fl, cleanupFuncp)); - m_regFuncp->addStmtsp( - new AstText(fl, ", vlSelf, vlSelf->vlSymsp->__Vm_modelp->contextp());\n", true)); + m_regFuncp->addStmtsp(new AstText(fl, ", vlSelf);\n", true)); // Clear global activity flag cleanupFuncp->addStmtsp( diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 63468c3e6..b75b9f855 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -981,6 +981,27 @@ private: } } + virtual void visit(AstWildcardSel* nodep) override { + // Signed/Real: Output type based on array-declared type; binary operator + if (m_vup->prelim()) { + const AstNodeDType* const fromDtp = nodep->fromp()->dtypep()->skipRefp(); + const AstWildcardArrayDType* const adtypep = VN_CAST(fromDtp, WildcardArrayDType); + if (!adtypep) { + UINFO(1, " Related dtype: " << fromDtp << endl); + nodep->v3fatalSrc("Wildcard array reference is not to wildcard array"); + } + const AstBasicDType* const basicp = nodep->bitp()->dtypep()->skipRefp()->basicp(); + if (!basicp + || (basicp->keyword() != VBasicDTypeKwd::STRING + && !basicp->keyword().isIntNumeric())) { + nodep->v3error("Wildcard index must be integral (IEEE 1800-2017 7.8.1)"); + } + iterateCheckTyped(nodep, "Wildcard associative select", nodep->bitp(), + adtypep->findStringDType(), BOTH); + nodep->dtypeFrom(adtypep->subDTypep()); + } + } + virtual void visit(AstSliceSel* nodep) override { // Always creates as output an unpacked array if (m_vup->prelim()) { @@ -1582,6 +1603,14 @@ private: nodep->dtypep(nodep); // The array itself, not subDtype UINFO(4, "dtWidthed " << nodep << endl); } + virtual void visit(AstWildcardArrayDType* nodep) override { + if (nodep->didWidthAndSet()) return; // This node is a dtype & not both PRELIMed+FINALed + // Iterate into subDTypep() to resolve that type and update pointer. + nodep->refDTypep(iterateEditMoveDTypep(nodep, nodep->subDTypep())); + // Cleanup array size + nodep->dtypep(nodep); // The array itself, not subDtype + UINFO(4, "dtWidthed " << nodep << endl); + } virtual void visit(AstBasicDType* nodep) override { if (nodep->didWidthAndSet()) return; // This node is a dtype & not both PRELIMed+FINALed if (nodep->generic()) return; // Already perfect @@ -2178,6 +2207,31 @@ private: EXTEND_EXP); } } + virtual void visit(AstConsWildcard* nodep) override { + // Type computed when constructed here + auto* const vdtypep = VN_AS(m_vup->dtypep()->skipRefp(), WildcardArrayDType); + UASSERT_OBJ(vdtypep, nodep, "ConsWildcard requires wildcard upper parent data type"); + if (m_vup->prelim()) { + nodep->dtypeFrom(vdtypep); + if (nodep->defaultp()) { + iterateCheck(nodep, "default", nodep->defaultp(), CONTEXT, FINAL, + vdtypep->subDTypep(), EXTEND_EXP); + } + } + } + virtual void visit(AstSetWildcard* nodep) override { + // Type computed when constructed here + auto* const vdtypep = VN_AS(m_vup->dtypep()->skipRefp(), WildcardArrayDType); + UASSERT_OBJ(vdtypep, nodep, "SetWildcard requires wildcard upper parent data type"); + if (m_vup->prelim()) { + nodep->dtypeFrom(vdtypep); + userIterateAndNext(nodep->lhsp(), WidthVP{vdtypep, BOTH}.p()); + iterateCheck(nodep, "key", nodep->keyp(), CONTEXT, FINAL, vdtypep->findStringDType(), + EXTEND_EXP); + iterateCheck(nodep, "value", nodep->valuep(), CONTEXT, FINAL, vdtypep->subDTypep(), + EXTEND_EXP); + } + } virtual void visit(AstConsDynArray* nodep) override { // Type computed when constructed here AstDynArrayDType* const vdtypep = VN_AS(m_vup->dtypep()->skipRefp(), DynArrayDType); @@ -2426,6 +2480,7 @@ private: } } else if (VN_IS(fromDtp, EnumDType) // || VN_IS(fromDtp, AssocArrayDType) // + || VN_IS(fromDtp, WildcardArrayDType) // || VN_IS(fromDtp, UnpackArrayDType) // || VN_IS(fromDtp, DynArrayDType) // || VN_IS(fromDtp, QueueDType) // @@ -2523,6 +2578,8 @@ private: methodCallEnum(nodep, adtypep); } else if (AstAssocArrayDType* const adtypep = VN_CAST(fromDtp, AssocArrayDType)) { methodCallAssoc(nodep, adtypep); + } else if (AstWildcardArrayDType* const adtypep = VN_CAST(fromDtp, WildcardArrayDType)) { + methodCallWildcard(nodep, adtypep); } else if (AstDynArrayDType* const adtypep = VN_CAST(fromDtp, DynArrayDType)) { methodCallDyn(nodep, adtypep); } else if (AstQueueDType* const adtypep = VN_CAST(fromDtp, QueueDType)) { @@ -2684,6 +2741,89 @@ private: nodep->v3error("Unknown built-in enum method " << nodep->prettyNameQ()); } } + void methodCallWildcard(AstMethodCall* nodep, AstWildcardArrayDType* adtypep) { + AstCMethodHard* newp = nullptr; + if (nodep->name() == "num" // function int num() + || nodep->name() == "size") { + methodOkArguments(nodep, 0, 0); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + "size"}; // So don't need num() + newp->dtypeSetSigned32(); + } else if (nodep->name() == "first" // function int first(ref index) + || nodep->name() == "last" // + || nodep->name() == "next" // + || nodep->name() == "prev" // + || nodep->name() == "unique_index" // + || nodep->name() == "find_index" || nodep->name() == "find_first_index" + || nodep->name() == "find_last_index") { + nodep->v3error("Array method " << nodep->prettyNameQ() + << " not legal on wildcard associative arrays"); + } else if (nodep->name() == "exists") { // function int exists(input index) + // IEEE really should have made this a "bit" return + methodOkArguments(nodep, 1, 1); + AstNode* const index_exprp = methodCallWildcardIndexExpr(nodep, adtypep); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), "exists", + index_exprp->unlinkFrBack()}; + newp->dtypeSetSigned32(); + newp->pure(true); + } else if (nodep->name() == "delete") { // function void delete([input integer index]) + methodOkArguments(nodep, 0, 1); + methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::WRITE); + if (!nodep->pinsp()) { + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + "clear"}; + newp->makeStatement(); + } else { + AstNode* const index_exprp = methodCallWildcardIndexExpr(nodep, adtypep); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + "erase", index_exprp->unlinkFrBack()}; + newp->makeStatement(); + } + } else if (nodep->name() == "sort" || nodep->name() == "rsort" + || nodep->name() == "reverse" || nodep->name() == "shuffle") { + nodep->v3error("Array method " << nodep->prettyNameQ() + << " not legal on associative arrays"); + } else if (nodep->name() == "and" || nodep->name() == "or" || nodep->name() == "xor" + || nodep->name() == "sum" || nodep->name() == "product") { + // All value return + AstWith* const withp + = methodWithArgument(nodep, false, false, adtypep->subDTypep(), + adtypep->findStringDType(), adtypep->subDTypep()); + methodOkArguments(nodep, 0, 0); + methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::READ); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + "r_" + nodep->name(), withp}; + newp->dtypeFrom(withp ? withp->dtypep() : adtypep->subDTypep()); + if (!nodep->firstAbovep()) newp->makeStatement(); + } else if (nodep->name() == "min" || nodep->name() == "max" || nodep->name() == "unique") { + methodOkArguments(nodep, 0, 0); + methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::READ); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + nodep->name()}; + newp->dtypeFrom(adtypep); + if (!nodep->firstAbovep()) newp->makeStatement(); + } else if (nodep->name() == "find" || nodep->name() == "find_first" + || nodep->name() == "find_last") { + AstWith* const withp + = methodWithArgument(nodep, true, false, nodep->findBitDType(), + adtypep->findStringDType(), adtypep->subDTypep()); + methodOkArguments(nodep, 0, 0); + methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::READ); + newp = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + nodep->name(), withp}; + newp->dtypeFrom(adtypep); + if (!nodep->firstAbovep()) newp->makeStatement(); + } else { + nodep->v3error("Unknown wildcard associative array method " << nodep->prettyNameQ()); + nodep->dtypeFrom(adtypep->subDTypep()); // Best guess + } + if (newp) { + newp->protect(false); + newp->didWidth(true); + nodep->replaceWith(newp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } + } void methodCallAssoc(AstMethodCall* nodep, AstAssocArrayDType* adtypep) { AstCMethodHard* newp = nullptr; if (nodep->name() == "num" // function int num() @@ -2789,6 +2929,13 @@ private: VL_DANGLING(index_exprp); // May have been edited return VN_AS(nodep->pinsp(), Arg)->exprp(); } + AstNode* methodCallWildcardIndexExpr(AstMethodCall* nodep, AstWildcardArrayDType* adtypep) { + AstNode* const index_exprp = VN_CAST(nodep->pinsp(), Arg)->exprp(); + iterateCheck(nodep, "index", index_exprp, CONTEXT, FINAL, adtypep->findStringDType(), + EXTEND_EXP); + VL_DANGLING(index_exprp); // May have been edited + return VN_AS(nodep->pinsp(), Arg)->exprp(); + } void methodCallLValueRecurse(AstMethodCall* nodep, AstNode* childp, const VAccess& access) { if (AstNodeVarRef* const varrefp = VN_CAST(childp, NodeVarRef)) { varrefp->access(access); @@ -3395,6 +3542,8 @@ private: VL_DO_DANGLING(patternArray(nodep, vdtypep, defaultp), nodep); } else if (auto* const vdtypep = VN_CAST(dtypep, AssocArrayDType)) { VL_DO_DANGLING(patternAssoc(nodep, vdtypep, defaultp), nodep); + } else if (auto* const vdtypep = VN_CAST(dtypep, WildcardArrayDType)) { + VL_DO_DANGLING(patternWildcard(nodep, vdtypep, defaultp), nodep); } else if (auto* const vdtypep = VN_CAST(dtypep, DynArrayDType)) { VL_DO_DANGLING(patternDynArray(nodep, vdtypep, defaultp), nodep); } else if (auto* const vdtypep = VN_CAST(dtypep, QueueDType)) { @@ -3578,6 +3727,26 @@ private: // if (debug() >= 9) newp->dumpTree("-apat-out: "); VL_DO_DANGLING(pushDeletep(nodep), nodep); // Deletes defaultp also, if present } + void patternWildcard(AstPattern* nodep, AstWildcardArrayDType* arrayDtp, + AstPatMember* defaultp) { + AstNode* defaultValuep = nullptr; + if (defaultp) defaultValuep = defaultp->lhssp()->unlinkFrBack(); + AstNode* newp = new AstConsWildcard{nodep->fileline(), defaultValuep}; + newp->dtypeFrom(arrayDtp); + for (AstPatMember* patp = VN_AS(nodep->itemsp(), PatMember); patp; + patp = VN_AS(patp->nextp(), PatMember)) { + patp->dtypep(arrayDtp->subDTypep()); + AstNode* const valuep = patternMemberValueIterate(patp); + AstNode* const keyp = patp->keyp(); + auto* const newap + = new AstSetWildcard{nodep->fileline(), newp, keyp->unlinkFrBack(), valuep}; + newap->dtypeFrom(arrayDtp); + newp = newap; + } + nodep->replaceWith(newp); + // if (debug() >= 9) newp->dumpTree("-apat-out: "); + VL_DO_DANGLING(pushDeletep(nodep), nodep); // Deletes defaultp also, if present + } void patternDynArray(AstPattern* nodep, AstDynArrayDType* arrayp, AstPatMember*) { AstNode* newp = new AstConsDynArray(nodep->fileline()); newp->dtypeFrom(arrayp); @@ -4094,6 +4263,7 @@ private: added = true; newFormat += "%g"; } else if (VN_IS(dtypep, AssocArrayDType) // + || VN_IS(dtypep, WildcardArrayDType) // || VN_IS(dtypep, ClassRefDType) // || VN_IS(dtypep, DynArrayDType) // || VN_IS(dtypep, QueueDType)) { diff --git a/src/V3WidthSel.cpp b/src/V3WidthSel.cpp index 793b98aec..4d6b7de01 100644 --- a/src/V3WidthSel.cpp +++ b/src/V3WidthSel.cpp @@ -88,6 +88,7 @@ private: if (const AstNodeArrayDType* const adtypep = VN_CAST(ddtypep, NodeArrayDType)) { fromRange = adtypep->declRange(); } else if (VN_IS(ddtypep, AssocArrayDType)) { + } else if (VN_IS(ddtypep, WildcardArrayDType)) { } else if (VN_IS(ddtypep, DynArrayDType)) { } else if (VN_IS(ddtypep, QueueDType)) { } else if (const AstNodeUOrStructDType* const adtypep @@ -257,6 +258,15 @@ private: if (debug() >= 9) newp->dumpTree(cout, "--SELBTn: "); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); + } else if (const AstWildcardArrayDType* const adtypep + = VN_CAST(ddtypep, WildcardArrayDType)) { + // SELBIT(array, index) -> WILDCARDSEL(array, index) + AstNode* const subp = rhsp; + AstWildcardSel* const newp = new AstWildcardSel{nodep->fileline(), fromp, subp}; + newp->dtypeFrom(adtypep->subDTypep()); // Need to strip off array reference + if (debug() >= 9) newp->dumpTree(cout, "--SELBTn: "); + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); } else if (const AstDynArrayDType* const adtypep = VN_CAST(ddtypep, DynArrayDType)) { // SELBIT(array, index) -> CMETHODCALL(queue, "at", index) AstNode* const subp = rhsp; diff --git a/src/verilog.y b/src/verilog.y index 532dd852b..1494d49fa 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -2077,10 +2077,8 @@ variable_dimension: // ==IEEE: variable_dimension // // IEEE: associative_dimension (if data_type) // // Can't tell which until see if expr is data type or not | '[' exprOrDataType ']' { $$ = new AstBracketRange($1, $2); } - | yP_BRASTAR ']' - { $$ = nullptr; BBUNSUP($1, "Unsupported: [*] wildcard associative arrays"); } - | '[' '*' ']' - { $$ = nullptr; BBUNSUP($2, "Unsupported: [*] wildcard associative arrays"); } + | yP_BRASTAR ']' { $$ = new AstWildcardRange{$1}; } + | '[' '*' ']' { $$ = new AstWildcardRange{$1}; } // // IEEE: queue_dimension // // '[' '$' ']' -- $ is part of expr, see '[' constExpr ']' // // '[' '$' ':' expr ']' -- anyrange:expr:$ diff --git a/test_regress/t/t_timescale.pl b/test_regress/t/t_assoc_wildcard.pl similarity index 69% rename from test_regress/t/t_timescale.pl rename to test_regress/t/t_assoc_wildcard.pl index 1bab927e5..9a15dd2cc 100755 --- a/test_regress/t/t_timescale.pl +++ b/test_regress/t/t_assoc_wildcard.pl @@ -2,18 +2,15 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2003-2019 by Wilson Snyder. This program is free software; you +# Copyright 2019 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 -scenarios(vlt_all => 1); +scenarios(simulator => 1); compile( - make_top_shell => 0, - make_main => 0, - v_flags2 => ["--exe $Self->{t_dir}/t_timescale.cpp"], ); execute( diff --git a/test_regress/t/t_assoc_wildcard_unsup.v b/test_regress/t/t_assoc_wildcard.v similarity index 71% rename from test_regress/t/t_assoc_wildcard_unsup.v rename to test_regress/t/t_assoc_wildcard.v index d17eaf3c1..2d4e5c782 100644 --- a/test_regress/t/t_assoc_wildcard_unsup.v +++ b/test_regress/t/t_assoc_wildcard.v @@ -22,26 +22,25 @@ module t (/*AUTOARG*/ cyc <= cyc + 1; begin // Wildcard - string a [*]; + string a [*] = '{default: "nope", "BBBBB": "fooing", 23'h434343: "baring"}; int k; string v; + v = a["CCC"]; `checks(v, "baring"); + v = a["BBBBB"]; `checks(v, "fooing"); + a[32'd1234] = "fooed"; a[4'd3] = "bared"; - i = a.num(); `checkh(i, 2); - i = a.size(); `checkh(i, 2); - v = a[32'd1234]; `checks(v, "fooed"); + a[79'h4141] = "bazed"; + i = a.num(); `checkh(i, 5); + i = a.size(); `checkh(i, 5); + v = a[39'd1234]; `checks(v, "fooed"); + v = a["AA"]; `checks(v, "bazed"); v = a[4'd3]; `checks(v, "bared"); i = a.exists("baz"); `checkh(i, 0); i = a.exists(4'd3); `checkh(i, 1); - i = a.first(k); `checkh(i, 1); `checks(k, 4'd3); - i = a.next(k); `checkh(i, 1); `checks(k, 32'd1234); - i = a.next(k); `checkh(i, 0); - i = a.last(k); `checkh(i, 1); `checks(k, 32'd1234); - i = a.prev(k); `checkh(i, 1); `checks(k, 4'd3); - i = a.prev(k); `checkh(i, 0); a.delete(4'd3); - i = a.size(); `checkh(i, 1); + i = a.size(); `checkh(i, 4); end $write("*-* All Finished *-*\n"); diff --git a/test_regress/t/t_assoc_wildcard_bad.out b/test_regress/t/t_assoc_wildcard_bad.out new file mode 100644 index 000000000..2bd459ff6 --- /dev/null +++ b/test_regress/t/t_assoc_wildcard_bad.out @@ -0,0 +1,73 @@ +%Error: t/t_assoc_wildcard_bad.v:23:13: The 1 arguments passed to .num method does not match its requiring 0 arguments + : ... In instance t + 23 | v = a.num("badarg"); + | ^~~ +%Error: t/t_assoc_wildcard_bad.v:24:13: The 1 arguments passed to .size method does not match its requiring 0 arguments + : ... In instance t + 24 | v = a.size("badarg"); + | ^~~~ +%Error: t/t_assoc_wildcard_bad.v:25:13: The 0 arguments passed to .exists method does not match its requiring 1 arguments + : ... In instance t + 25 | v = a.exists(); + | ^~~~~~ +%Error: t/t_assoc_wildcard_bad.v:26:13: The 2 arguments passed to .exists method does not match its requiring 1 arguments + : ... In instance t + 26 | v = a.exists(k, "bad2"); + | ^~~~~~ +%Error: t/t_assoc_wildcard_bad.v:27:9: The 2 arguments passed to .delete method does not match its requiring 0 to 1 arguments + : ... In instance t + 27 | a.delete(k, "bad2"); + | ^~~~~~ +%Error: t/t_assoc_wildcard_bad.v:29:9: Array method 'sort' not legal on associative arrays + : ... In instance t + 29 | a.sort; + | ^~~~ +%Error: t/t_assoc_wildcard_bad.v:30:9: Array method 'rsort' not legal on associative arrays + : ... In instance t + 30 | a.rsort; + | ^~~~~ +%Error: t/t_assoc_wildcard_bad.v:31:9: Array method 'reverse' not legal on associative arrays + : ... In instance t + 31 | a.reverse; + | ^~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:32:9: Array method 'shuffle' not legal on associative arrays + : ... In instance t + 32 | a.shuffle; + | ^~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:34:9: Array method 'first' not legal on wildcard associative arrays + : ... In instance t + 34 | a.first; + | ^~~~~ +%Error: t/t_assoc_wildcard_bad.v:35:9: Array method 'last' not legal on wildcard associative arrays + : ... In instance t + 35 | a.last; + | ^~~~ +%Error: t/t_assoc_wildcard_bad.v:36:9: Array method 'next' not legal on wildcard associative arrays + : ... In instance t + 36 | a.next; + | ^~~~ +%Error: t/t_assoc_wildcard_bad.v:37:9: Array method 'prev' not legal on wildcard associative arrays + : ... In instance t + 37 | a.prev; + | ^~~~ +%Error: t/t_assoc_wildcard_bad.v:38:9: Array method 'unique_index' not legal on wildcard associative arrays + : ... In instance t + 38 | a.unique_index; + | ^~~~~~~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:39:9: Array method 'find_index' not legal on wildcard associative arrays + : ... In instance t + 39 | a.find_index; + | ^~~~~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:40:9: Array method 'find_first_index' not legal on wildcard associative arrays + : ... In instance t + 40 | a.find_first_index; + | ^~~~~~~~~~~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:41:9: Array method 'find_last_index' not legal on wildcard associative arrays + : ... In instance t + 41 | a.find_last_index; + | ^~~~~~~~~~~~~~~ +%Error: t/t_assoc_wildcard_bad.v:43:8: Wildcard index must be integral (IEEE 1800-2017 7.8.1) + : ... In instance t + 43 | a[x] = "bad"; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_assoc_wildcard_unsup.pl b/test_regress/t/t_assoc_wildcard_bad.pl similarity index 100% rename from test_regress/t/t_assoc_wildcard_unsup.pl rename to test_regress/t/t_assoc_wildcard_bad.pl diff --git a/test_regress/t/t_assoc_wildcard_bad.v b/test_regress/t/t_assoc_wildcard_bad.v new file mode 100644 index 000000000..85ebf83ba --- /dev/null +++ b/test_regress/t/t_assoc_wildcard_bad.v @@ -0,0 +1,45 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2019 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +typedef class Cls; + +class Cls; + integer imembera; + integer imemberb; +endclass : Cls + +module t (/*AUTOARG*/); + + initial begin + string a [*]; + string k; + string v; + + Cls x; + + v = a.num("badarg"); + v = a.size("badarg"); + v = a.exists(); // Bad + v = a.exists(k, "bad2"); + a.delete(k, "bad2"); + + a.sort; // Not legal on assoc + a.rsort; // Not legal on assoc + a.reverse; // Not legal on assoc + a.shuffle; // Not legal on assoc + + a.first; // Not legal on wildcard + a.last; // Not legal on wildcard + a.next; // Not legal on wildcard + a.prev; // Not legal on wildcard + a.unique_index; // Not legal on wildcard + a.find_index; // Not legal on wildcard + a.find_first_index; // Not legal on wildcard + a.find_last_index; // Not legal on wildcard + + a[x] = "bad"; + end +endmodule diff --git a/test_regress/t/t_assoc_wildcard_method.pl b/test_regress/t/t_assoc_wildcard_method.pl new file mode 100755 index 000000000..b46d46042 --- /dev/null +++ b/test_regress/t/t_assoc_wildcard_method.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_assoc_wildcard_method.v b/test_regress/t/t_assoc_wildcard_method.v new file mode 100644 index 000000000..c61086e7b --- /dev/null +++ b/test_regress/t/t_assoc_wildcard_method.v @@ -0,0 +1,127 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2019 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define checks(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); + +module t (/*AUTOARG*/); + initial begin + int q[*]; + int qe[*]; // Empty + int qv[$]; // Value returns + int qi[$]; // Index returns + int i; + string v; + + q = '{"a":1, "b":2, "c":2, "d":4, "e":3}; + v = $sformatf("%p", q); `checks(v, "'{\"a\":'h1, \"b\":'h2, \"c\":'h2, \"d\":'h4, \"e\":'h3} "); + + // NOT tested: with ... selectors + + //q.sort; // Not legal on assoc - see t_assoc_meth_bad + //q.rsort; // Not legal on assoc - see t_assoc_meth_bad + //q.reverse; // Not legal on assoc - see t_assoc_meth_bad + //q.shuffle; // Not legal on assoc - see t_assoc_meth_bad + + v = $sformatf("%p", qe); `checks(v, "'{}"); + qv = q.unique; + v = $sformatf("%p", qv); `checks(v, "'{'h1, 'h2, 'h4, 'h3} "); + qv = qe.unique; + v = $sformatf("%p", qv); `checks(v, "'{}"); + + //q.unique_index; // Not legal on wildcard assoc - see t_assoc_wildcard_bad + + // These require an with clause or are illegal + qv = q.find with (item == 2); + v = $sformatf("%p", qv); `checks(v, "'{'h2, 'h2} "); + qv = q.find_first with (item == 2); + v = $sformatf("%p", qv); `checks(v, "'{'h2} "); + qv = q.find_last with (item == 2); + v = $sformatf("%p", qv); `checks(v, "'{'h2} "); + + qv = q.find with (item == 20); + v = $sformatf("%p", qv); `checks(v, "'{}"); + qv = q.find_first with (item == 20); + v = $sformatf("%p", qv); `checks(v, "'{}"); + qv = q.find_last with (item == 20); + v = $sformatf("%p", qv); `checks(v, "'{}"); + + //q.find_index; // Not legal on wildcard assoc - see t_assoc_wildcard_bad + //q.find_first_index; // Not legal on wildcard assoc - see t_assoc_wildcard_bad + //q.find_last_index; // Not legal on wildcard assoc - see t_assoc_wildcard_bad + + qv = q.min; + v = $sformatf("%p", qv); `checks(v, "'{'h1} "); + qv = q.max; + v = $sformatf("%p", qv); `checks(v, "'{'h4} "); + + qv = qe.min; + v = $sformatf("%p", qv); `checks(v, "'{}"); + qv = qe.max; + v = $sformatf("%p", qv); `checks(v, "'{}"); + + // Reduction methods + + i = q.sum; + `checkh(i, 32'hc); + i = q.sum with (item + 1); + `checkh(i, 32'h11); + i = q.product; + `checkh(i, 32'h30); + i = q.product with (item + 1); + `checkh(i, 32'h168); + + i = qe.sum; + `checkh(i, 32'h0); + i = qe.product; + `checkh(i, 32'h0); + + q = '{10:32'b1100, 11:32'b1010}; + i = q.and; + `checkh(i, 32'b1000); + i = q.and with (item + 1); + `checkh(i, 32'b1001); + i = q.or; + `checkh(i, 32'b1110); + i = q.or with (item + 1); + `checkh(i, 32'b1111); + i = q.xor; + `checkh(i, 32'b0110); + i = q.xor with (item + 1); + `checkh(i, 32'b0110); + + i = qe.and; + `checkh(i, 32'b0); + i = qe.or; + `checkh(i, 32'b0); + i = qe.xor; + `checkh(i, 32'b0); + + i = q.and(); + `checkh(i, 32'b1000); + i = q.and() with (item + 1); + `checkh(i, 32'b1001); + i = q.or(); + `checkh(i, 32'b1110); + i = q.or() with (item + 1); + `checkh(i, 32'b1111); + i = q.xor(); + `checkh(i, 32'b0110); + i = q.xor() with (item + 1); + `checkh(i, 32'b0110); + + i = qe.and(); + `checkh(i, 32'b0); + i = qe.or(); + `checkh(i, 32'b0); + i = qe.xor(); + `checkh(i, 32'b0); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_assoc_wildcard_unsup.out b/test_regress/t/t_assoc_wildcard_unsup.out deleted file mode 100644 index 209295f7f..000000000 --- a/test_regress/t/t_assoc_wildcard_unsup.out +++ /dev/null @@ -1,5 +0,0 @@ -%Error-UNSUPPORTED: t/t_assoc_wildcard_unsup.v:25:19: Unsupported: [*] wildcard associative arrays - 25 | string a [*]; - | ^~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to diff --git a/test_regress/t/t_flag_hier1_bad.pl b/test_regress/t/t_flag_hier1_bad.pl index f41278e68..8a3475533 100755 --- a/test_regress/t/t_flag_hier1_bad.pl +++ b/test_regress/t/t_flag_hier1_bad.pl @@ -14,7 +14,7 @@ top_filename("t/t_hier_block.v"); lint( fails => 1, verilator_flags2 => ['--hierarchical', - '--hierarchical-child', + '--hierarchical-child 1', 'modName', ], expect_filename => $Self->{golden_filename}, diff --git a/test_regress/t/t_timescale.cpp b/test_regress/t/t_timescale.cpp deleted file mode 100644 index 84c53c583..000000000 --- a/test_regress/t/t_timescale.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -// This file ONLY is placed under the Creative Commons Public Domain, for -// any use, without warranty, 2020 by Wilson Snyder. -// SPDX-License-Identifier: CC0-1.0 - -#include - -#include "TestCheck.h" - -#include VM_PREFIX_INCLUDE - -unsigned long long main_time = 0; -double sc_time_stamp() { return (double)main_time; } - -#include - -#define FILENM "t_timescale.cpp" - -int errors = 0; - -int main(int argc, char** argv, char** env) { - VM_PREFIX* top = new VM_PREFIX("top"); - - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("100s"), 2); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("10s"), 1); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("1s"), 0); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("100ms"), -1); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("10ms"), -2); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("1ms"), -3); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("100us"), -4); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("10us"), -5); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("1us"), -6); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("100ns"), -7); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("10ns"), -8); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("1ns"), -9); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("100ps"), -10); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("10ps"), -11); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("1ps"), -12); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("100fs"), -13); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("10fs"), -14); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("1fs"), -15); - - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("1.5s"), 0); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("1s "), 0); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("1ss"), 0); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT("s"), 0); - TEST_CHECK_EQ(VL_TIME_STR_CONVERT(0), 0); - - top->final(); - VL_DO_DANGLING(delete top, top); - printf("*-* All Finished *-*\n"); - - return errors ? 10 : 0; -} diff --git a/test_regress/t/t_timescale.v b/test_regress/t/t_timescale.v deleted file mode 100644 index 396bc9b35..000000000 --- a/test_regress/t/t_timescale.v +++ /dev/null @@ -1,3 +0,0 @@ -module t; - -endmodule diff --git a/verilator-config.cmake.in b/verilator-config.cmake.in index 7f9ece972..2125341b8 100644 --- a/verilator-config.cmake.in +++ b/verilator-config.cmake.in @@ -261,15 +261,6 @@ function(verilate TARGET) set_property(TARGET ${TARGET} PROPERTY VERILATOR_THREADED ON) endif() - if (${VERILATE_PREFIX}_TRACE_THREADS) - # If any verilate() call specifies TRACE_THREADS, define VL_TRACE_THREADED in the final build - set_property(TARGET ${TARGET} PROPERTY VERILATOR_TRACE_THREADED ON) - endif() - - if (${VERILATE_PREFIX}_TRACE_FST_WRITER_THREAD) - set_property(TARGET ${TARGET} PROPERTY VERILATOR_TRACE_FST_WRITER_TRHEAD ON) - endif() - if (${VERILATE_PREFIX}_COVERAGE) # If any verilate() call specifies COVERAGE, define VM_COVERAGE in the final build set_property(TARGET ${TARGET} PROPERTY VERILATOR_COVERAGE ON) @@ -330,8 +321,6 @@ function(verilate TARGET) VM_COVERAGE=$> VM_SC=$> $<$>:VL_THREADED> - $<$>:VL_TRACE_THREADED> - $<$>:VL_TRACE_FST_WRITER_THREAD> VM_TRACE=$> VM_TRACE_VCD=$> VM_TRACE_FST=$>