From 0dfa7d3af5a56c24e67f6590d640344da3ec4d87 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kozdra Date: Mon, 18 Jul 2022 18:58:55 +0200 Subject: [PATCH 01/14] Internals: const-qualify findDType function. No functional change. (#3502) --- src/V3Ast.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/V3Ast.h b/src/V3Ast.h index 3b9c88a39..58e940480 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -1717,13 +1717,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; From 9085e34d7096afa6c9354c9701322cbad946b6de Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 18 Jul 2022 13:14:24 +0100 Subject: [PATCH 02/14] Pass VerilatedModel at trace registration time --- include/verilated_trace.h | 10 +++++----- include/verilated_trace_imp.h | 19 ++++++++++--------- src/V3EmitCModel.cpp | 2 +- src/V3Trace.cpp | 6 ++---- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/include/verilated_trace.h b/include/verilated_trace.h index 0d0f7c0f6..5e7fc85c2 100644 --- a/include/verilated_trace.h +++ b/include/verilated_trace.h @@ -215,7 +215,7 @@ private: 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); + void addModel(VerilatedModel*) VL_MT_SAFE_EXCLUDES(m_mutex); void addCallbackRecord(std::vector& cbVec, CallbackRecord&& cbRec) VL_MT_SAFE_EXCLUDES(m_mutex); @@ -339,10 +339,10 @@ 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 addInitCb(initCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; + void addFullCb(dumpCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; + void addChgCb(dumpCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; + void addCleanupCb(cleanupCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; void scopeEscape(char flag) { m_scopeEscape = flag; } diff --git a/include/verilated_trace_imp.h b/include/verilated_trace_imp.h index a09ac0f43..d20c09166 100644 --- a/include/verilated_trace_imp.h +++ b/include/verilated_trace_imp.h @@ -604,9 +604,10 @@ 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}; + VerilatedContext* const contextp = modelp->contextp(); if (m_contextp && contextp != m_contextp) { VL_FATAL_MT( __FILE__, __LINE__, "", @@ -630,26 +631,26 @@ void VerilatedTrace::addCallbackRecord(std::vector void VerilatedTrace::addInitCb(initCb_t cb, void* userp, - VerilatedContext* contextp) VL_MT_SAFE { - addContext(contextp); + VerilatedModel* modelp) VL_MT_SAFE { + addModel(modelp); addCallbackRecord(m_initCbs, CallbackRecord{cb, userp}); } template <> void VerilatedTrace::addFullCb(dumpCb_t cb, void* userp, - VerilatedContext* contextp) VL_MT_SAFE { - addContext(contextp); + VerilatedModel* modelp) VL_MT_SAFE { + addModel(modelp); addCallbackRecord(m_fullCbs, CallbackRecord{cb, userp}); } template <> void VerilatedTrace::addChgCb(dumpCb_t cb, void* userp, - VerilatedContext* contextp) VL_MT_SAFE { - addContext(contextp); + VerilatedModel* modelp) VL_MT_SAFE { + addModel(modelp); addCallbackRecord(m_chgCbs, CallbackRecord{cb, userp}); } template <> void VerilatedTrace::addCleanupCb(cleanupCb_t cb, void* userp, - VerilatedContext* contextp) VL_MT_SAFE { - addContext(contextp); + VerilatedModel* modelp) VL_MT_SAFE { + addModel(modelp); addCallbackRecord(m_cleanupCbs, CallbackRecord{cb, userp}); } diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 04b422a97..67c02e332 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -540,7 +540,7 @@ class EmitCModel final : public EmitCFunc { } puts(/**/ "if (false && levels && options) {} // Prevent unused\n"); puts(/**/ "tfp->spTrace()->addInitCb(&" + protect("trace_init") - + ", &(vlSymsp->TOP), contextp());\n"); + + ", &(vlSymsp->TOP), this);\n"); puts(/**/ topModNameProtected + "__" + protect("trace_register") + "(&(vlSymsp->TOP), tfp->spTrace());\n"); diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index 7113c5e0e..e7a445c1a 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -513,8 +513,7 @@ 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, ", vlSelf->vlSymsp->__Vm_modelp", true)); m_regFuncp->addStmtsp(new AstText(flp, ");\n", true)); } else { // Sub functions @@ -702,8 +701,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, vlSelf->vlSymsp->__Vm_modelp);\n", true)); // Clear global activity flag cleanupFuncp->addStmtsp( From db59c07f27da450153787f5abfaa3fce476545a3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 18 Jul 2022 15:32:14 +0100 Subject: [PATCH 03/14] Implement trace offloading with fewer ifdefs Step towards a proper run-time library. Reduce the amount of ifdefs in the implementation of offloaded tracing. There are still a very small number of ifdefs left, which will need more careful changes in order to keep user API compatibility. --- include/verilated_fst_c.cpp | 35 +++-- include/verilated_fst_c.h | 18 ++- include/verilated_trace.h | 196 ++++++++++++----------- include/verilated_trace_imp.h | 240 ++++++++++++++++++----------- include/verilated_vcd_c.cpp | 31 ++-- include/verilated_vcd_c.h | 33 ++-- src/V3HierBlock.cpp | 2 +- src/V3LinkCells.cpp | 2 +- src/V3Options.cpp | 2 +- src/V3Options.h | 8 +- src/V3Trace.cpp | 8 +- test_regress/t/t_flag_hier1_bad.pl | 2 +- 12 files changed, 337 insertions(+), 240 deletions(-) diff --git a/include/verilated_fst_c.cpp b/include/verilated_fst_c.cpp index 0bc1048cf..e6ea9f99f 100644 --- a/include/verilated_fst_c.cpp +++ b/include/verilated_fst_c.cpp @@ -93,7 +93,17 @@ static_assert(static_cast(FST_ST_VCD_PROGRAM) == static_cast(VLT_TRACE // VerilatedFst VerilatedFst::VerilatedFst(void* fst) - : m_fst{fst} {} + : +#ifdef VL_TRACE_OFFLOAD + VerilatedTrace { + true +} +#else + VerilatedTrace { + false +} +#endif +, m_fst{fst} {} VerilatedFst::~VerilatedFst() { if (m_fst) fstWriterClose(m_fst); @@ -250,13 +260,21 @@ 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; @@ -265,9 +283,6 @@ void VerilatedFst::commitTraceBuffer(VerilatedFstBuffer* bufp) { //============================================================================= // VerilatedFstBuffer implementation -VerilatedFstBuffer::VerilatedFstBuffer(VerilatedFst& owner) - : VerilatedTraceBuffer{owner} {} - //============================================================================= // Trace rendering primitives diff --git a/include/verilated_fst_c.h b/include/verilated_fst_c.h index 5131cc8cc..0587527f6 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 @@ -72,8 +72,8 @@ 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; public: //========================================================================= @@ -124,10 +124,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 +140,10 @@ class VerilatedFstBuffer final : public VerilatedTraceBuffer #include -#ifdef VL_TRACE_OFFLOAD +#ifdef VL_THREADED # include # include #endif @@ -57,9 +57,10 @@ // clang-format on class VlThreadPool; -template class VerilatedTraceBuffer; +template class VerilatedTraceBuffer; +template class VerilatedTraceOffloadBuffer; -#ifdef VL_TRACE_OFFLOAD +#ifdef VL_THREADED //============================================================================= // Offloaded tracing @@ -133,23 +134,26 @@ public: // 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 +162,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,11 +172,16 @@ 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} {} }; + const bool m_offload; // Whether to use the offload thread (ignored if !VL_THREADED) + #ifdef VL_TRACE_PARALLEL struct ParallelWorkerData { const dumpCb_t m_cb; // The callback @@ -202,7 +212,9 @@ private: 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' @@ -225,13 +237,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 @@ -298,6 +311,12 @@ protected: void closeBase(); void flushBase(); +#ifdef VL_THREADED + inline bool offload() const { return m_offload; } +#else + static constexpr bool offload() { return false; } +#endif + //========================================================================= // Virtual functions to be provided by the format specific implementation @@ -317,7 +336,7 @@ public: //========================================================================= // External interface to client code - explicit VerilatedTrace(); + explicit VerilatedTrace(bool offload); ~VerilatedTrace(); // Set time units (s/ms, defaults to ns) @@ -341,7 +360,9 @@ public: void addInitCb(initCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; void addFullCb(dumpCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; + void addFullCb(dumpOffloadCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; void addChgCb(dumpCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; + void addChgCb(dumpOffloadCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; void addCleanupCb(cleanupCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; void scopeEscape(char flag) { m_scopeEscape = flag; } @@ -353,32 +374,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 +424,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 +535,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 d20c09166..9fd3f2421 100644 --- a/include/verilated_trace_imp.h +++ b/include/verilated_trace_imp.h @@ -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_THREDED + 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 } @@ -289,7 +293,14 @@ template <> void VerilatedTrace::onExit(void* selfp) { //============================================================================= // VerilatedTrace -template <> VerilatedTrace::VerilatedTrace() { +template <> +VerilatedTrace::VerilatedTrace(bool offload) + : m_offload{offload} { +#ifndef VL_THREADED + if (m_offload) { + VL_FATAL_MT(__FILE__, __LINE__, "", "Cannot use trace offloading without VL_THREADED"); + } +#endif set_time_unit(Verilated::threadContextp()->timeunitString()); set_time_resolution(Verilated::threadContextp()->timeprecisionString()); } @@ -299,9 +310,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 +364,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 } @@ -527,6 +538,21 @@ 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. @@ -550,35 +576,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 +624,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; @@ -638,16 +676,32 @@ void VerilatedTrace::addInitCb(initCb_t cb, void* userp, template <> void VerilatedTrace::addFullCb(dumpCb_t cb, void* userp, VerilatedModel* modelp) VL_MT_SAFE { + assert(!offload()); addModel(modelp); addCallbackRecord(m_fullCbs, CallbackRecord{cb, userp}); } template <> +void VerilatedTrace::addFullCb(dumpOffloadCb_t cb, void* userp, + VerilatedModel* modelp) VL_MT_SAFE { + assert(offload()); + addModel(modelp); + addCallbackRecord(m_fullOffloadCbs, CallbackRecord{cb, userp}); +} +template <> void VerilatedTrace::addChgCb(dumpCb_t cb, void* userp, VerilatedModel* modelp) VL_MT_SAFE { + assert(!offload()); addModel(modelp); addCallbackRecord(m_chgCbs, CallbackRecord{cb, userp}); } template <> +void VerilatedTrace::addChgCb(dumpOffloadCb_t cb, void* userp, + VerilatedModel* modelp) VL_MT_SAFE { + assert(offload()); + addModel(modelp); + addCallbackRecord(m_chgOffloadCbs, CallbackRecord{cb, userp}); +} +template <> void VerilatedTrace::addCleanupCb(cleanupCb_t cb, void* userp, VerilatedModel* modelp) VL_MT_SAFE { addModel(modelp); @@ -757,20 +811,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 @@ -778,61 +822,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..1f5595ed2 100644 --- a/include/verilated_vcd_c.cpp +++ b/include/verilated_vcd_c.cpp @@ -102,7 +102,8 @@ ssize_t VerilatedVcdFile::write(const char* bufp, ssize_t len) VL_MT_UNSAFE { //============================================================================= // Opening/Closing -VerilatedVcd::VerilatedVcd(VerilatedVcdFile* filep) { +VerilatedVcd::VerilatedVcd(VerilatedVcdFile* filep) + : VerilatedTrace{false} { // Not in header to avoid link issue if header is included without this .cpp file m_fileNewed = (filep == nullptr); m_filep = m_fileNewed ? new VerilatedVcdFile : filep; @@ -583,7 +584,8 @@ void VerilatedVcd::declDouble(uint32_t code, const char* name, bool array, int a //============================================================================= // Get/commit trace buffer -VerilatedVcdBuffer* VerilatedVcd::getTraceBuffer() { +VerilatedVcd::Buffer* VerilatedVcd::getTraceBuffer() { + VerilatedVcd::Buffer* const bufp = new Buffer{*this}; #ifdef VL_TRACE_PARALLEL // Note: This is called from VeriltedVcd::dump, which already holds the lock // If no buffer available, allocate a new one @@ -597,14 +599,16 @@ VerilatedVcdBuffer* VerilatedVcd::getTraceBuffer() { // 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}; + // Initialize + bufp->m_writep = bufp->m_bufp = pair.first; + bufp->m_size = pair.second; + bufp->adjustGrowp(); #endif + // Return the buffer + return bufp; } -void VerilatedVcd::commitTraceBuffer(VerilatedVcdBuffer* bufp) { +void VerilatedVcd::commitTraceBuffer(VerilatedVcd::Buffer* 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 @@ -631,19 +635,6 @@ void VerilatedVcd::commitTraceBuffer(VerilatedVcdBuffer* 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 diff --git a/include/verilated_vcd_c.h b/include/verilated_vcd_c.h index 0d83eb25d..2a6316d10 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 @@ -110,8 +110,8 @@ 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; public: //========================================================================= @@ -160,16 +160,20 @@ 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; + + VerilatedVcd& m_owner; // Trace file owning this buffer. Required by subclasses. #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 + char* m_writep = nullptr; // Write pointer into m_bufp + 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 #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 @@ -189,18 +193,13 @@ class VerilatedVcdBuffer final : public VerilatedTraceBuffername()); // 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(); 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 6daff79bc..e163278f8 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 2c795c844..41e508553 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 @@ -288,6 +287,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 @@ -518,7 +518,9 @@ 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); + } unsigned vmTraceThreads() const { return useTraceParallel() ? threads() : useTraceOffload() ? 1 : 0; } @@ -605,7 +607,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/V3Trace.cpp b/src/V3Trace.cpp index e7a445c1a..2d5551cfe 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 @@ -517,7 +519,9 @@ private: 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) { 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}, From 7ef033f8765ef5c2c5b43c0b49cfa049424252fb Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 19 Jul 2022 11:23:27 +0100 Subject: [PATCH 04/14] Ensure generated Makefile for hierarchical build is stable. Avoid iterating unordered_map. Iterate sorted blocks instead. --- src/V3EmitMk.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/V3EmitMk.cpp b/src/V3EmitMk.cpp index 800cf589c..188047c3c 100644 --- a/src/V3EmitMk.cpp +++ b/src/V3EmitMk.cpp @@ -369,13 +369,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 +384,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"); } From af70db88db4773f953e23f4aaab745b25e4ed7f0 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 19 Jul 2022 11:27:18 +0100 Subject: [PATCH 05/14] Remove unused method --- src/V3HierBlock.cpp | 4 ---- src/V3HierBlock.h | 1 - 2 files changed, 5 deletions(-) diff --git a/src/V3HierBlock.cpp b/src/V3HierBlock.cpp index aa80e2ee3..22006fa23 100644 --- a/src/V3HierBlock.cpp +++ b/src/V3HierBlock.cpp @@ -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); From b55ee79d8627b44256a9dab4ab4662c21371cedd Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 19 Jul 2022 12:36:21 +0100 Subject: [PATCH 06/14] Fix typo --- include/verilated_trace_imp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/verilated_trace_imp.h b/include/verilated_trace_imp.h index 9fd3f2421..2cd1038e3 100644 --- a/include/verilated_trace_imp.h +++ b/include/verilated_trace_imp.h @@ -264,7 +264,7 @@ template <> void VerilatedTrace::closeBase() { } template <> void VerilatedTrace::flushBase() { -#ifdef VL_THREDED +#ifdef VL_THREADED if (offload()) { // Hand an empty buffer to the worker thread uint32_t* const bufferp = getOffloadBuffer(); From f8b7981be4905accc3f9851ba01f9eed6c2fc852 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 19 Jul 2022 13:48:03 +0100 Subject: [PATCH 07/14] Make use of FST writer thread switchable at run-time. Always build the FST libray with -DFST_WRITER_PARALLEL, iff VL_THREADED. This supports run-time enablement of the FST writer thread, and has no measurable performance impact on single threaded tracing but simplifies the library build. Note: the actual choice of using the fst writer thread is still compile time, but can now be made run-time easily. --- include/verilated_fst_c.cpp | 6 ++---- include/verilated_fst_c.h | 8 ++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/verilated_fst_c.cpp b/include/verilated_fst_c.cpp index e6ea9f99f..8c9523de5 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 @@ -116,9 +116,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 (useFstWriterThread()) fstWriterSetParallelMode(m_fst, 1); fullDump(true); // First dump must be full for fst m_curScope.clear(); diff --git a/include/verilated_fst_c.h b/include/verilated_fst_c.h index 0587527f6..b391c8de6 100644 --- a/include/verilated_fst_c.h +++ b/include/verilated_fst_c.h @@ -60,6 +60,14 @@ private: void declare(uint32_t code, const char* name, int dtypenum, fstVarDir vardir, fstVarType vartype, bool array, int arraynum, bool bussed, int msb, int lsb); + static constexpr bool useFstWriterThread() { +#ifdef VL_TRACE_FST_WRITER_THREAD + return true; +#else + return false; +#endif + } + protected: //========================================================================= // Implementation of VerilatedTrace interface From 3a002b6cf25f27f97417d623ee0c55014fa6aae3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 19 Jul 2022 13:58:18 +0100 Subject: [PATCH 08/14] Remove VerilatedVcd::m_evcd and related dead code. The legacy code that was using this was removed earlier, and m_evcd was constant false, so removed. --- include/verilated_vcd_c.cpp | 50 +++++++++++++------------------------ include/verilated_vcd_c.h | 1 - 2 files changed, 18 insertions(+), 33 deletions(-) diff --git a/include/verilated_vcd_c.cpp b/include/verilated_vcd_c.cpp index 1f5595ed2..ee3a43583 100644 --- a/include/verilated_vcd_c.cpp +++ b/include/verilated_vcd_c.cpp @@ -261,11 +261,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. @@ -515,38 +510,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) { diff --git a/include/verilated_vcd_c.h b/include/verilated_vcd_c.h index 2a6316d10..bba0f4253 100644 --- a/include/verilated_vcd_c.h +++ b/include/verilated_vcd_c.h @@ -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 From efb5caad22a93326df556c5e821457155193bd64 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 19 Jul 2022 14:16:08 +0100 Subject: [PATCH 09/14] Improve robustness of trace configuration Always fail if adding a model to a trace file that has already executed a dump. We used to do this before as well, though in a less robust way. We will be relying on this property more in the future, so improve the check. --- include/verilated_trace.h | 4 ++-- include/verilated_trace_imp.h | 27 +++++++++++++-------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/include/verilated_trace.h b/include/verilated_trace.h index 1c83d3224..895cecd26 100644 --- a/include/verilated_trace.h +++ b/include/verilated_trace.h @@ -208,7 +208,6 @@ 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 @@ -226,6 +225,8 @@ private: char m_scopeEscape = '.'; double m_timeRes = 1e-9; // Time resolution (ns/ms etc) double m_timeUnit = 1e-0; // Time units (ns/ms etc) + 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) void addModel(VerilatedModel*) VL_MT_SAFE_EXCLUDES(m_mutex); @@ -290,7 +291,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; } diff --git a/include/verilated_trace_imp.h b/include/verilated_trace_imp.h index 2cd1038e3..c3eebc2dd 100644 --- a/include/verilated_trace_imp.h +++ b/include/verilated_trace_imp.h @@ -559,13 +559,14 @@ void VerilatedTrace::dump(uint64_t timeui) VL_MT_SAFE_EXCLUD // 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(); @@ -646,11 +647,14 @@ void VerilatedTrace::addModel(VerilatedModel* modelp) VL_MT_SAFE_EXCLUDES(m_mutex) { const VerilatedLockGuard lock{m_mutex}; VerilatedContext* const contextp = modelp->contextp(); - if (m_contextp && contextp != m_contextp) { - VL_FATAL_MT( - __FILE__, __LINE__, "", - "A trace file instance can only handle models from the same simulation context"); + if (VL_UNCOVERABLE(m_contextp && contextp != m_contextp)) { // LCOV_EXCL_START + 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 m_contextp = contextp; } @@ -659,11 +663,6 @@ void VerilatedTrace::addCallbackRecord(std::vector::addInitCb(initCb_t cb, void* userp, template <> void VerilatedTrace::addFullCb(dumpCb_t cb, void* userp, VerilatedModel* modelp) VL_MT_SAFE { - assert(!offload()); addModel(modelp); + assert(!offload()); addCallbackRecord(m_fullCbs, CallbackRecord{cb, userp}); } template <> void VerilatedTrace::addFullCb(dumpOffloadCb_t cb, void* userp, VerilatedModel* modelp) VL_MT_SAFE { - assert(offload()); addModel(modelp); + assert(offload()); addCallbackRecord(m_fullOffloadCbs, CallbackRecord{cb, userp}); } template <> void VerilatedTrace::addChgCb(dumpCb_t cb, void* userp, VerilatedModel* modelp) VL_MT_SAFE { - assert(!offload()); addModel(modelp); + assert(!offload()); addCallbackRecord(m_chgCbs, CallbackRecord{cb, userp}); } template <> void VerilatedTrace::addChgCb(dumpOffloadCb_t cb, void* userp, VerilatedModel* modelp) VL_MT_SAFE { - assert(offload()); addModel(modelp); + assert(offload()); addCallbackRecord(m_chgOffloadCbs, CallbackRecord{cb, userp}); } template <> From a4ed3c20864b5161926239edd060a14ccd198c70 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 19 Jul 2022 17:06:26 +0100 Subject: [PATCH 10/14] Make parallel tracing switchable at run-time --- include/verilated_trace.h | 10 ++- include/verilated_trace_imp.h | 80 +++++++++---------- include/verilated_vcd_c.cpp | 140 ++++++++++++++++++---------------- include/verilated_vcd_c.h | 24 +++--- 4 files changed, 138 insertions(+), 116 deletions(-) diff --git a/include/verilated_trace.h b/include/verilated_trace.h index 895cecd26..e5e80904f 100644 --- a/include/verilated_trace.h +++ b/include/verilated_trace.h @@ -182,7 +182,7 @@ private: const bool m_offload; // Whether to use the offload thread (ignored if !VL_THREADED) -#ifdef VL_TRACE_PARALLEL +#ifdef VL_THREADED struct ParallelWorkerData { const dumpCb_t m_cb; // The callback void* const m_userp; // The use pointer to pass to the callback @@ -317,6 +317,14 @@ protected: static constexpr bool offload() { return false; } #endif + inline bool parallel() const { +#ifdef VL_TRACE_PARALLEL + return true; +#else + return false; +#endif + } + //========================================================================= // Virtual functions to be provided by the format specific implementation diff --git a/include/verilated_trace_imp.h b/include/verilated_trace_imp.h index c3eebc2dd..aed3a09a4 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 @@ -462,7 +462,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); @@ -490,45 +490,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) { diff --git a/include/verilated_vcd_c.cpp b/include/verilated_vcd_c.cpp index ee3a43583..e5ce780cf 100644 --- a/include/verilated_vcd_c.cpp +++ b/include/verilated_vcd_c.cpp @@ -230,9 +230,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 } @@ -572,49 +574,55 @@ void VerilatedVcd::declDouble(uint32_t code, const char* name, bool array, int a VerilatedVcd::Buffer* VerilatedVcd::getTraceBuffer() { VerilatedVcd::Buffer* const bufp = new Buffer{*this}; -#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; +#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(); - // Initialize - bufp->m_writep = bufp->m_bufp = pair.first; - bufp->m_size = pair.second; - bufp->adjustGrowp(); #endif // Return the buffer return bufp; } void VerilatedVcd::commitTraceBuffer(VerilatedVcd::Buffer* 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); + 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; } @@ -656,35 +664,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 bba0f4253..853e976e7 100644 --- a/include/verilated_vcd_c.h +++ b/include/verilated_vcd_c.h @@ -65,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 @@ -168,30 +168,30 @@ class VerilatedVcdBuffer VL_NOT_FINAL { VerilatedVcd& m_owner; // Trace file owning this buffer. Required by subclasses. -#ifdef VL_TRACE_PARALLEL - char* m_writep = nullptr; // Write pointer into m_bufp - 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 -#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 + // 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 + void finishLine(uint32_t code, char* writep); + // CONSTRUCTOR explicit VerilatedVcdBuffer(VerilatedVcd& owner) : m_owner{owner} {} From 1d400dd98c21e9672fc17b43bdb49ef3b25721ad Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 20 Jul 2022 11:27:10 +0100 Subject: [PATCH 11/14] Configure tracing at run-time, instead of compile time (#3504) All remaining use of conditional compilation in the tracing implementation of the run-time library are replaced with the use of VerilatedModel::traceConfig, and is now done at run-time. --- include/verilated.cpp | 4 ++ include/verilated.h | 8 ++++ include/verilated.mk.in | 20 --------- include/verilated_fst_c.cpp | 22 +++++----- include/verilated_fst_c.h | 13 +++--- include/verilated_trace.h | 68 +++++++++++++++---------------- include/verilated_trace_imp.h | 76 +++++++++++++++++++++-------------- include/verilated_vcd_c.cpp | 3 +- include/verilated_vcd_c.h | 3 ++ src/V3EmitCMake.cpp | 6 --- src/V3EmitCModel.cpp | 18 ++++++++- src/V3EmitMk.cpp | 9 ----- src/V3Options.h | 1 + src/V3Trace.cpp | 3 +- verilator-config.cmake.in | 11 ----- 15 files changed, 128 insertions(+), 137 deletions(-) diff --git a/include/verilated.cpp b/include/verilated.cpp index d5821b8f7..74aaf3fbd 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; @@ -2914,6 +2916,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 34e975bcc..03a6ef35d 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 8c9523de5..69a71c5d9 100644 --- a/include/verilated_fst_c.cpp +++ b/include/verilated_fst_c.cpp @@ -93,17 +93,7 @@ static_assert(static_cast(FST_ST_VCD_PROGRAM) == static_cast(VLT_TRACE // VerilatedFst VerilatedFst::VerilatedFst(void* fst) - : -#ifdef VL_TRACE_OFFLOAD - VerilatedTrace { - true -} -#else - VerilatedTrace { - false -} -#endif -, m_fst{fst} {} + : m_fst{fst} {} VerilatedFst::~VerilatedFst() { if (m_fst) fstWriterClose(m_fst); @@ -116,7 +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 - if (useFstWriterThread()) fstWriterSetParallelMode(m_fst, 1); + if (m_useFstWriterThread) fstWriterSetParallelMode(m_fst, 1); fullDump(true); // First dump must be full for fst m_curScope.clear(); @@ -278,6 +268,14 @@ void VerilatedFst::commitTraceBuffer(VerilatedFst::Buffer* bufp) { delete bufp; } +//============================================================================= +// Configure + +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 diff --git a/include/verilated_fst_c.h b/include/verilated_fst_c.h index b391c8de6..c87b9763b 100644 --- a/include/verilated_fst_c.h +++ b/include/verilated_fst_c.h @@ -55,19 +55,13 @@ 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, fstVarType vartype, bool array, int arraynum, bool bussed, int msb, int lsb); - static constexpr bool useFstWriterThread() { -#ifdef VL_TRACE_FST_WRITER_THREAD - return true; -#else - return false; -#endif - } - protected: //========================================================================= // Implementation of VerilatedTrace interface @@ -83,6 +77,9 @@ protected: virtual Buffer* getTraceBuffer() override; virtual void commitTraceBuffer(Buffer*) override; + // Configure sub-class + virtual void configure(const VerilatedTraceConfig&) override; + public: //========================================================================= // External interface to client code diff --git a/include/verilated_trace.h b/include/verilated_trace.h index e5e80904f..66481602f 100644 --- a/include/verilated_trace.h +++ b/include/verilated_trace.h @@ -24,21 +24,6 @@ // clang-format off -// In FST mode, VL_TRACE_THREADED enables offloading, but only if we also have -// the FST writer thread. This means with --trace-threads 1, we get the FST -// writer thread only, and with --trace-threads 2 we get offloading as well -#if defined(VL_TRACE_FST_WRITER_THREAD) && defined(VL_TRACE_THREADED) -# define VL_TRACE_OFFLOAD -#endif -// VCD tracing can happen fully in parallel -#if defined(VM_TRACE_VCD) && VM_TRACE_VCD && defined(VL_TRACE_THREADED) -# define VL_TRACE_PARALLEL -#endif - -#if defined(VL_TRACE_PARALLEL) && defined(VL_TRACE_OFFLOAD) -# error "Cannot have VL_TRACE_PARALLEL and VL_TRACE_OFFLOAD together" -#endif - #include "verilated.h" #include "verilated_trace_defs.h" @@ -47,6 +32,7 @@ #include #include #include +#include #include #ifdef VL_THREADED @@ -130,6 +116,22 @@ 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 @@ -180,7 +182,8 @@ private: , m_userp{userp} {} }; - const bool m_offload; // Whether to use the offload thread (ignored if !VL_THREADED) + 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 { @@ -215,7 +218,6 @@ private: 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 @@ -227,8 +229,8 @@ private: double m_timeUnit = 1e-0; // Time units (ns/ms etc) 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) - - void addModel(VerilatedModel*) VL_MT_SAFE_EXCLUDES(m_mutex); + 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); @@ -313,18 +315,12 @@ protected: #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 - inline bool parallel() const { -#ifdef VL_TRACE_PARALLEL - return true; -#else - return false; -#endif - } - //========================================================================= // Virtual functions to be provided by the format specific implementation @@ -340,11 +336,14 @@ 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 - explicit VerilatedTrace(bool offload); + explicit VerilatedTrace(); ~VerilatedTrace(); // Set time units (s/ms, defaults to ns) @@ -366,12 +365,13 @@ public: //========================================================================= // Non-hot path internal interface to Verilator generated code - void addInitCb(initCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; - void addFullCb(dumpCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; - void addFullCb(dumpOffloadCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; - void addChgCb(dumpCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; - void addChgCb(dumpOffloadCb_t cb, void* userp, VerilatedModel*) VL_MT_SAFE; - void addCleanupCb(cleanupCb_t cb, void* userp, VerilatedModel*) 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; } diff --git a/include/verilated_trace_imp.h b/include/verilated_trace_imp.h index aed3a09a4..1401f67eb 100644 --- a/include/verilated_trace_imp.h +++ b/include/verilated_trace_imp.h @@ -293,14 +293,7 @@ template <> void VerilatedTrace::onExit(void* selfp) { //============================================================================= // VerilatedTrace -template <> -VerilatedTrace::VerilatedTrace(bool offload) - : m_offload{offload} { -#ifndef VL_THREADED - if (m_offload) { - VL_FATAL_MT(__FILE__, __LINE__, "", "Cannot use trace offloading without VL_THREADED"); - } -#endif +template <> VerilatedTrace::VerilatedTrace() { set_time_unit(Verilated::threadContextp()->timeunitString()); set_time_resolution(Verilated::threadContextp()->timeprecisionString()); } @@ -648,8 +641,17 @@ template <> void VerilatedTrace::addModel(VerilatedModel* modelp) VL_MT_SAFE_EXCLUDES(m_mutex) { const VerilatedLockGuard lock{m_mutex}; + + const bool firstModel = m_models.empty(); + const bool newModel = m_models.insert(modelp).second; VerilatedContext* const contextp = modelp->contextp(); - if (VL_UNCOVERABLE(m_contextp && contextp != m_contextp)) { // LCOV_EXCL_START + + // 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"); } @@ -657,7 +659,35 @@ void VerilatedTrace::addModel(VerilatedModel* modelp) 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 <> @@ -669,43 +699,27 @@ void VerilatedTrace::addCallbackRecord(std::vector -void VerilatedTrace::addInitCb(initCb_t cb, void* userp, - VerilatedModel* modelp) VL_MT_SAFE { - addModel(modelp); +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, - VerilatedModel* modelp) VL_MT_SAFE { - addModel(modelp); - assert(!offload()); +void VerilatedTrace::addFullCb(dumpCb_t cb, void* userp) VL_MT_SAFE { addCallbackRecord(m_fullCbs, CallbackRecord{cb, userp}); } template <> -void VerilatedTrace::addFullCb(dumpOffloadCb_t cb, void* userp, - VerilatedModel* modelp) VL_MT_SAFE { - addModel(modelp); - assert(offload()); +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, - VerilatedModel* modelp) VL_MT_SAFE { - addModel(modelp); - assert(!offload()); +void VerilatedTrace::addChgCb(dumpCb_t cb, void* userp) VL_MT_SAFE { addCallbackRecord(m_chgCbs, CallbackRecord{cb, userp}); } template <> -void VerilatedTrace::addChgCb(dumpOffloadCb_t cb, void* userp, - VerilatedModel* modelp) VL_MT_SAFE { - addModel(modelp); - assert(offload()); +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, - VerilatedModel* modelp) VL_MT_SAFE { - addModel(modelp); +void VerilatedTrace::addCleanupCb(cleanupCb_t cb, void* userp) VL_MT_SAFE { addCallbackRecord(m_cleanupCbs, CallbackRecord{cb, userp}); } diff --git a/include/verilated_vcd_c.cpp b/include/verilated_vcd_c.cpp index e5ce780cf..342ba7b2c 100644 --- a/include/verilated_vcd_c.cpp +++ b/include/verilated_vcd_c.cpp @@ -102,8 +102,7 @@ ssize_t VerilatedVcdFile::write(const char* bufp, ssize_t len) VL_MT_UNSAFE { //============================================================================= // Opening/Closing -VerilatedVcd::VerilatedVcd(VerilatedVcdFile* filep) - : VerilatedTrace{false} { +VerilatedVcd::VerilatedVcd(VerilatedVcdFile* filep) { // Not in header to avoid link issue if header is included without this .cpp file m_fileNewed = (filep == nullptr); m_filep = m_fileNewed ? new VerilatedVcdFile : filep; diff --git a/include/verilated_vcd_c.h b/include/verilated_vcd_c.h index 853e976e7..20c5440f5 100644 --- a/include/verilated_vcd_c.h +++ b/include/verilated_vcd_c.h @@ -112,6 +112,9 @@ protected: virtual Buffer* getTraceBuffer() override; virtual void commitTraceBuffer(Buffer*) override; + // Configure sub-class + virtual void configure(const VerilatedTraceConfig&) override { return; }; + public: //========================================================================= // External interface to client code 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 67c02e332..c0a8e452c 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"); @@ -487,6 +490,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) { @@ -539,8 +553,8 @@ class EmitCModel final : public EmitCFunc { puts(/**/ "}"); } puts(/**/ "if (false && levels && options) {} // Prevent unused\n"); - puts(/**/ "tfp->spTrace()->addInitCb(&" + protect("trace_init") - + ", &(vlSymsp->TOP), this);\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 188047c3c..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) { diff --git a/src/V3Options.h b/src/V3Options.h index 41e508553..78fa1b8bd 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -521,6 +521,7 @@ public: 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; } diff --git a/src/V3Trace.cpp b/src/V3Trace.cpp index 2d5551cfe..c8a665f3e 100644 --- a/src/V3Trace.cpp +++ b/src/V3Trace.cpp @@ -515,7 +515,6 @@ 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", true)); m_regFuncp->addStmtsp(new AstText(flp, ");\n", true)); } else { // Sub functions @@ -705,7 +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);\n", true)); + m_regFuncp->addStmtsp(new AstText(fl, ", vlSelf);\n", true)); // Clear global activity flag cleanupFuncp->addStmtsp( 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=$> From 1c5e5704f5310409a5e56f26af7a1cc4f06ccd31 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 20 Jul 2022 13:07:26 +0100 Subject: [PATCH 12/14] Fix iteration fixup in AstNode::addHereThisAsNext Previous version broke verialor_ext_tests due to iteration order mismatch after 3fc8249429a3cde8c925f1ef6ff87bc82865121e --- src/V3Ast.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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); From 542e3248695af6f776457152a9c3b84904b65727 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kozdra Date: Wed, 20 Jul 2022 15:01:36 +0200 Subject: [PATCH 13/14] Wildcard index type support for associative arrays (#3501). Associative arrays that specify a wildcard index type may be indexed by integral expressions of any size, with leading zeros removed automatically. A natural representation for such expressions is a string, especially that the standard explicitly specifies automatic casts from string indices to bit vectors of equivalent size. The automatic cast part is done implicitly by the existing type system. A simpler way to just make this work would be to convert wildcard index type to a string type directly in the parser code, but several new AST classes are needed to make sure illegal method calls are detected. The verilated data structure implementation is reused, because there is no need for differentiating the behavior on C++ side. --- src/V3AstNodes.cpp | 7 + src/V3AstNodes.h | 146 +++++++++++++++ src/V3Clean.cpp | 1 + src/V3EmitCFunc.cpp | 16 ++ src/V3EmitCFunc.h | 26 +++ src/V3Hasher.cpp | 5 + src/V3ParseGrammar.cpp | 2 + src/V3Width.cpp | 170 ++++++++++++++++++ src/V3WidthSel.cpp | 10 ++ src/verilog.y | 6 +- test_regress/t/t_assoc_wildcard.pl | 21 +++ ...oc_wildcard_unsup.v => t_assoc_wildcard.v} | 21 ++- test_regress/t/t_assoc_wildcard_bad.out | 73 ++++++++ ...dcard_unsup.pl => t_assoc_wildcard_bad.pl} | 0 test_regress/t/t_assoc_wildcard_bad.v | 45 +++++ test_regress/t/t_assoc_wildcard_method.pl | 21 +++ test_regress/t/t_assoc_wildcard_method.v | 127 +++++++++++++ test_regress/t/t_assoc_wildcard_unsup.out | 5 - 18 files changed, 682 insertions(+), 20 deletions(-) create mode 100755 test_regress/t/t_assoc_wildcard.pl rename test_regress/t/{t_assoc_wildcard_unsup.v => t_assoc_wildcard.v} (71%) create mode 100644 test_regress/t/t_assoc_wildcard_bad.out rename test_regress/t/{t_assoc_wildcard_unsup.pl => t_assoc_wildcard_bad.pl} (100%) create mode 100644 test_regress/t/t_assoc_wildcard_bad.v create mode 100755 test_regress/t/t_assoc_wildcard_method.pl create mode 100644 test_regress/t/t_assoc_wildcard_method.v delete mode 100644 test_regress/t/t_assoc_wildcard_unsup.out diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 33d275e4e..95ce8ba24 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 + ">"; @@ -1683,6 +1686,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 d9e4b81db..8b258db0a 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) @@ -1686,6 +1753,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: @@ -4887,6 +4992,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 ae3f8ce03..7117fee7e 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 629b90397..31d1f5a11 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -379,6 +379,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); @@ -1189,6 +1197,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/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/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/V3Width.cpp b/src/V3Width.cpp index 8a833d9f2..c98e856aa 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); @@ -3393,6 +3540,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)) { @@ -3576,6 +3725,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); @@ -4092,6 +4261,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 34fbe86b0..82faf4a7d 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -2074,10 +2074,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_assoc_wildcard.pl b/test_regress/t/t_assoc_wildcard.pl new file mode 100755 index 000000000..9a15dd2cc --- /dev/null +++ b/test_regress/t/t_assoc_wildcard.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 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(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; 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 From 30e3edb81d5b2b280587e47af8416e45bed4b498 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 20 Jul 2022 13:15:19 +0100 Subject: [PATCH 14/14] Remove deprecated and unused timescale override defines These have been 'deprecated' for 2 years and are otherwise unused except for using a temporary placeholder value, which I have inlined with the default value. Also remove the now VL_TIME_STR_CONVERT utility function (and corresponding unit tests), which have no references in any project on GitHub. --- include/verilated.cpp | 28 ++---------------- include/verilated_funcs.h | 18 ------------ test_regress/t/t_timescale.cpp | 54 ---------------------------------- test_regress/t/t_timescale.pl | 24 --------------- test_regress/t/t_timescale.v | 3 -- 5 files changed, 3 insertions(+), 124 deletions(-) delete mode 100644 test_regress/t/t_timescale.cpp delete mode 100755 test_regress/t/t_timescale.pl delete mode 100644 test_regress/t/t_timescale.v diff --git a/include/verilated.cpp b/include/verilated.cpp index 74aaf3fbd..7b9200363 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -2164,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", @@ -2308,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 { diff --git a/include/verilated_funcs.h b/include/verilated_funcs.h index c8fb91d3e..a7c3fcc73 100644 --- a/include/verilated_funcs.h +++ b/include/verilated_funcs.h @@ -256,25 +256,7 @@ extern void _vl_debug_print_w(int lbits, WDataInP const iwp); //========================================================================= // Pli macros -extern int VL_TIME_STR_CONVERT(const char* strp) VL_PURE; - -// These are deprecated and used only to establish the default precision/units. -// Use Verilator timescale-override for better control. // clang-format off -#ifndef VL_TIME_PRECISION -# ifdef VL_TIME_PRECISION_STR -# define VL_TIME_PRECISION VL_TIME_STR_CONVERT(VL_STRINGIFY(VL_TIME_PRECISION_STR)) -# else -# define VL_TIME_PRECISION (-12) ///< Timescale default units if not in Verilog - picoseconds -# endif -#endif -#ifndef VL_TIME_UNIT -# ifdef VL_TIME_UNIT_STR -# define VL_TIME_UNIT VL_TIME_STR_CONVERT(VL_STRINGIFY(VL_TIME_PRECISION_STR)) -# else -# define VL_TIME_UNIT (-12) ///< Timescale default units if not in Verilog - picoseconds -# endif -#endif #if defined(SYSTEMC_VERSION) /// Return current simulation time 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.pl b/test_regress/t/t_timescale.pl deleted file mode 100755 index 1bab927e5..000000000 --- a/test_regress/t/t_timescale.pl +++ /dev/null @@ -1,24 +0,0 @@ -#!/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-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); - -compile( - make_top_shell => 0, - make_main => 0, - v_flags2 => ["--exe $Self->{t_dir}/t_timescale.cpp"], - ); - -execute( - check_finished => 1, - ); - -ok(1); -1; 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