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.
This commit is contained in:
Geza Lore 2022-07-18 15:32:14 +01:00
parent 9085e34d70
commit db59c07f27
12 changed files with 337 additions and 240 deletions

View File

@ -93,7 +93,17 @@ static_assert(static_cast<int>(FST_ST_VCD_PROGRAM) == static_cast<int>(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<OffloadBuffer*>(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<VerilatedFst, VerilatedFstBuffer>{owner} {}
//=============================================================================
// Trace rendering primitives

View File

@ -43,7 +43,7 @@ public:
using Super = VerilatedTrace<VerilatedFst, VerilatedFstBuffer>;
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<VerilatedFst, VerilatedFstBuffer> {
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<VerilatedFst, Veril
// String buffer long enough to hold maxBits() chars
char* const m_strbuf = m_owner.m_strbuf;
public:
// CONSTRUCTOR
explicit VerilatedFstBuffer(VerilatedFst& owner);
~VerilatedFstBuffer() = default;
explicit VerilatedFstBuffer(VerilatedFst& owner)
: m_owner{owner} {}
virtual ~VerilatedFstBuffer() = default;
//=========================================================================
// Implementation of VerilatedTraceBuffer interface

View File

@ -49,7 +49,7 @@
#include <unordered_map>
#include <vector>
#ifdef VL_TRACE_OFFLOAD
#ifdef VL_THREADED
# include <deque>
# include <thread>
#endif
@ -57,9 +57,10 @@
// clang-format on
class VlThreadPool;
template <class T_Trace, class T_Buffer> class VerilatedTraceBuffer;
template <class T_Buffer> class VerilatedTraceBuffer;
template <class T_Buffer> 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 T_Trace, class T_Buffer> class VerilatedTrace VL_NOT_FINAL {
// Give the buffer (both base and derived) access to the private bits
friend VerilatedTraceBuffer<T_Trace, T_Buffer>;
friend T_Buffer;
public:
using Buffer = T_Buffer;
using Buffer = VerilatedTraceBuffer<T_Buffer>;
using OffloadBuffer = VerilatedTraceOffloadBuffer<T_Buffer>;
//=========================================================================
// 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<bool> m_sigs_enabledVec; // Staging for m_sigs_enabledp
std::vector<CallbackRecord> m_initCbs; // Routines to initialize tracing
std::vector<CallbackRecord> m_fullCbs; // Routines to perform full dump
std::vector<CallbackRecord> m_fullOffloadCbs; // Routines to perform offloaded full dump
std::vector<CallbackRecord> m_chgCbs; // Routines to perform incremental dump
std::vector<CallbackRecord> m_chgOffloadCbs; // Routines to perform offloaded incremental dump
std::vector<CallbackRecord> 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<T_Trace*>(this); }
void runCallbacks(const std::vector<CallbackRecord>& cbVec);
void runOffloadedCallbacks(const std::vector<CallbackRecord>& 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 T_Trace, class T_Buffer> class VerilatedTraceBuffer VL_NOT_FINAL {
friend T_Trace; // Give the trace file access to the private bits
template <class T_Buffer> //
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<decltype(T_Buffer::m_owner)>::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<T_Buffer>::value, "");
static_assert(std::is_base_of<VerilatedTrace<Trace, T_Buffer>, 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<VerilatedTraceBuffer<T_Buffer>>;
// 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<T_Buffer*>(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<QData*>(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<double*>(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 T_Buffer> //
class VerilatedTraceOffloadBuffer final : public VerilatedTraceBuffer<T_Buffer> {
using typename VerilatedTraceBuffer<T_Buffer>::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<QData*>(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<double*>(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

View File

@ -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<VL_SUB_T, VL_BUF_T>::offloadWorkerThreadMain() {
const uint32_t* readp = bufferp;
std::unique_ptr<VL_BUF_T> traceBufp; // We own the passed tracebuffer
std::unique_ptr<Buffer> traceBufp; // We own the passed tracebuffer
while (true) {
const uint32_t cmd = readp[0];
@ -143,44 +143,44 @@ template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::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<const QData*>(readp), top);
traceBufp->chgQData(oldp, *reinterpret_cast<const QData*>(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<const double*>(readp));
traceBufp->chgDouble(oldp, *reinterpret_cast<const double*>(readp));
readp += 2;
continue;
@ -196,7 +196,7 @@ template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::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<VL_BUF_T* const*>(readp));
traceBufp.reset(*reinterpret_cast<Buffer* const*>(readp));
readp += 2;
continue;
@ -252,24 +252,28 @@ template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::shutdownOffloadWorker() {
// Life cycle
template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::onExit(void* selfp) {
//=============================================================================
// VerilatedTrace
template <> VerilatedTrace<VL_SUB_T, VL_BUF_T>::VerilatedTrace() {
template <>
VerilatedTrace<VL_SUB_T, VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::~VerilatedTrace() {
if (m_sigs_enabledp) VL_DO_CLEAR(delete[] m_sigs_enabledp, m_sigs_enabledp = nullptr);
Verilated::removeFlushCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::onFlush, this);
Verilated::removeExitCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::onExit, this);
#ifdef VL_TRACE_OFFLOAD
closeBase();
#endif
if (offload()) closeBase();
}
//=========================================================================
@ -355,17 +364,19 @@ template <> void VerilatedTrace<VL_SUB_T, VL_BUF_T>::traceInit() VL_MT_UNSAFE {
Verilated::addFlushCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::onFlush, this);
Verilated::addExitCb(VerilatedTrace<VL_SUB_T, VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::offloadWorkerThreadMain, this});
// Start the worker thread
m_workerThread.reset(
new std::thread{&VerilatedTrace<VL_SUB_T, VL_BUF_T>::offloadWorkerThreadMain, this});
}
#endif
}
@ -527,6 +538,21 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::runCallbacks(const std::vector<Callback
}
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::runOffloadedCallbacks(
const std::vector<CallbackRecord>& 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<OffloadBuffer*>(traceBufferp));
commitTraceBuffer(traceBufferp);
#else
VL_FATAL_MT(__FILE__, __LINE__, "", "Unreachable");
#endif
}
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::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<uint64_t*>(m_offloadBufferWritep + 1) = timeui;
m_offloadBufferWritep += 3;
// Tell worker to update time point
m_offloadBufferWritep[0] = VerilatedTraceOffloadCommand::TIME_CHANGE;
*reinterpret_cast<uint64_t*>(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<VL_SUB_T, VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::addInitCb(initCb_t cb, void* userp,
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addFullCb(dumpCb_t cb, void* userp,
VerilatedModel* modelp) VL_MT_SAFE {
assert(!offload());
addModel(modelp);
addCallbackRecord(m_fullCbs, CallbackRecord{cb, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addFullCb(dumpOffloadCb_t cb, void* userp,
VerilatedModel* modelp) VL_MT_SAFE {
assert(offload());
addModel(modelp);
addCallbackRecord(m_fullOffloadCbs, CallbackRecord{cb, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addChgCb(dumpCb_t cb, void* userp,
VerilatedModel* modelp) VL_MT_SAFE {
assert(!offload());
addModel(modelp);
addCallbackRecord(m_chgCbs, CallbackRecord{cb, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addChgCb(dumpOffloadCb_t cb, void* userp,
VerilatedModel* modelp) VL_MT_SAFE {
assert(offload());
addModel(modelp);
addCallbackRecord(m_chgOffloadCbs, CallbackRecord{cb, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::VerilatedTraceBuffer(VL_SUB_T& owner)
: m_owner{owner} {
#ifdef VL_TRACE_OFFLOAD
if (m_offloadBufferWritep) {
using This = VerilatedTraceBuffer<VL_SUB_T, VL_BUF_T>*;
// 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<This*>(m_offloadBufferWritep) = this;
m_offloadBufferWritep += 2;
}
#endif
}
VerilatedTraceBuffer<VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::VerilatedTraceBuffer(VL_SUB_T& owner)
// the emit* functions can be inlined for performance.
template <> //
void VerilatedTraceBuffer<VL_SUB_T, VL_BUF_T>::fullBit(uint32_t* oldp, CData newval) {
void VerilatedTraceBuffer<VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::fullCData(uint32_t* oldp, CData newval, int bits) {
void VerilatedTraceBuffer<VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::fullSData(uint32_t* oldp, SData newval, int bits) {
void VerilatedTraceBuffer<VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::fullIData(uint32_t* oldp, IData newval, int bits) {
void VerilatedTraceBuffer<VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::fullQData(uint32_t* oldp, QData newval, int bits) {
void VerilatedTraceBuffer<VL_BUF_T>::fullQData(uint32_t* oldp, QData newval, int bits) {
const uint32_t code = oldp - m_sigs_oldvalp;
*reinterpret_cast<QData*>(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<VL_SUB_T, VL_BUF_T>::fullWData(uint32_t* oldp, const WData* newvalp,
int bits) {
void VerilatedTraceBuffer<VL_BUF_T>::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<VL_SUB_T, VL_BUF_T>::fullDouble(uint32_t* oldp, double newval) {
template <> //
void VerilatedTraceBuffer<VL_BUF_T>::fullDouble(uint32_t* oldp, double newval) {
const uint32_t code = oldp - m_sigs_oldvalp;
*reinterpret_cast<double*>(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<VL_BUF_T>::VerilatedTraceOffloadBuffer(VL_SUB_T& owner)
: VerilatedTraceBuffer<VL_BUF_T>{owner}
, m_offloadBufferWritep{owner.m_offloadBufferWritep}
, m_offloadBufferEndp{owner.m_offloadBufferEndp} {
if (m_offloadBufferWritep) {
using This = VerilatedTraceBuffer<VL_BUF_T>*;
// 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<This*>(m_offloadBufferWritep) = static_cast<This>(this);
m_offloadBufferWritep += 2;
}
}
#endif
#endif // VL_CPPCHECK

View File

@ -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<VerilatedVcd, VerilatedVcdBuffer>{owner}
, m_writep{bufp}
, m_bufp{bufp}
, m_size{size} {
adjustGrowp();
}
#else
VerilatedVcdBuffer::VerilatedVcdBuffer(VerilatedVcd& owner)
: VerilatedTraceBuffer<VerilatedVcd, VerilatedVcdBuffer>{owner} {}
#endif
//=============================================================================
// Trace rendering primitives

View File

@ -41,7 +41,7 @@ public:
using Super = VerilatedTrace<VerilatedVcd, VerilatedVcdBuffer>;
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<VerilatedVcd, VerilatedVcdBuffer> {
// 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 VerilatedTraceBuffer<VerilatedVcd, Veril
}
#endif
public:
// CONSTRUCTOR
#ifdef VL_TRACE_PARALLEL
explicit VerilatedVcdBuffer(VerilatedVcd& owner, char* bufp, size_t size);
#else
explicit VerilatedVcdBuffer(VerilatedVcd& owner);
#endif
~VerilatedVcdBuffer() = default;
explicit VerilatedVcdBuffer(VerilatedVcd& owner)
: m_owner{owner} {}
virtual ~VerilatedVcdBuffer() = default;
//=========================================================================
// Implementation of VerilatedTraceBuffer interface
// Implementations of duck-typed methods for VerilatedTraceBuffer. These are
// called from only one place (the full* methods), so always inline them.
VL_ATTR_ALWINLINE inline void emitBit(uint32_t code, CData newval);

View File

@ -156,7 +156,7 @@ V3StringList V3HierBlock::commandArgs(bool forCMake) const {
opts.push_back(" --lib-create " + modp()->name()); // possibly mangled name
if (v3Global.opt.protectKeyProvided())
opts.push_back(" --protect-key " + v3Global.opt.protectKeyDefaulted());
opts.push_back(" --hierarchical-child");
opts.push_back(" --hierarchical-child " + cvtToStr(std::max(1, v3Global.opt.threads())));
const StrGParams gparamsStr = stringifyParams(gparams(), true);
for (StrGParams::const_iterator paramIt = gparamsStr.begin(); paramIt != gparamsStr.end();

View File

@ -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) {

View File

@ -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)); });

View File

@ -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

View File

@ -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) {

View File

@ -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},