File: verilated_trace.h

package info (click to toggle)
verilator 4.038-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 29,596 kB
  • sloc: cpp: 90,585; perl: 15,101; ansic: 8,573; yacc: 3,626; lex: 1,616; makefile: 1,101; sh: 175; python: 145
file content (403 lines) | stat: -rw-r--r-- 15,740 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
// -*- mode: C++; c-file-style: "cc-mode" -*-
//=============================================================================
//
// THIS MODULE IS PUBLICLY LICENSED
//
// Copyright 2001-2020 by Wilson Snyder. This program is free software; you
// can redistribute it and/or modify it under the terms of either the GNU
// Lesser General Public License Version 3 or the Perl Artistic License
// Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//=============================================================================
///
/// \file
/// \brief Tracing functionality common to all formats
///
//=============================================================================
// SPDIFF_OFF

#ifndef _VERILATED_TRACE_H_
#define _VERILATED_TRACE_H_ 1

// clang-format off

#include "verilated.h"

#include <string>
#include <vector>

#ifdef VL_TRACE_THREADED
# include <condition_variable>
# include <deque>
# include <thread>
#endif

// clang-format on

#ifdef VL_TRACE_THREADED
//=============================================================================
// Threaded tracing

// A simple synchronized first in first out queue
template <class T> class VerilatedThreadQueue {  // LCOV_EXCL_LINE  // lcov bug
private:
    VerilatedMutex m_mutex;  // Protects m_queue
    std::condition_variable_any m_cv;
    std::deque<T> m_queue VL_GUARDED_BY(m_mutex);

public:
    // Put an element at the back of the queue
    void put(T value) {
        VerilatedLockGuard lock(m_mutex);
        m_queue.push_back(value);
        m_cv.notify_one();
    }

    // Put an element at the front of the queue
    void put_front(T value) {
        VerilatedLockGuard lock(m_mutex);
        m_queue.push_front(value);
        m_cv.notify_one();
    }

    // Get an element from the front of the queue. Blocks if none available
    T get() {
        VerilatedLockGuard lock(m_mutex);
        m_cv.wait(lock, [this]() VL_REQUIRES(m_mutex) { return !m_queue.empty(); });
        assert(!m_queue.empty());
        T value = m_queue.front();
        m_queue.pop_front();
        return value;
    }

    // Non blocking get
    bool tryGet(T& result) {
        const VerilatedLockGuard lockGuard(m_mutex);
        if (m_queue.empty()) { return false; }
        result = m_queue.front();
        m_queue.pop_front();
        return true;
    }
};

// Commands used by thread tracing. Anonymous enum in class, as we want
// it scoped, but we also want the automatic conversion to integer types.
class VerilatedTraceCommand {
public:
    // These must all fit in 4 bit at the moment, as the tracing routines
    // pack parameters in the top bits.
    enum {
        CHG_BIT_0 = 0x0,
        CHG_BIT_1 = 0x1,
        CHG_CDATA = 0x2,
        CHG_SDATA = 0x3,
        CHG_IDATA = 0x4,
        CHG_QDATA = 0x5,
        CHG_WDATA = 0x6,
        CHG_DOUBLE = 0x8,
        // TODO: full..
        TIME_CHANGE = 0xd,
        END = 0xe,  // End of buffer
        SHUTDOWN = 0xf  // Shutdown worker thread, also marks end of buffer
    };
};
#endif

//=============================================================================
// VerilatedTrace

// VerilatedTrace uses F-bounded polymorphism to access duck-typed
// implementations in the format specific derived class, which must be passed
// as the type parameter T_Derived
template <class T_Derived> class VerilatedTrace {
public:
    //=========================================================================
    // Generic tracing internals

    typedef void (*initCb_t)(void*, T_Derived*, uint32_t);  // Type of init callbacks
    typedef void (*dumpCb_t)(void*, T_Derived*);  // Type of all but init callbacks

private:
    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
        // assignment operator on inserting into collections, so they don't work
        // with const fields...
        union {
            initCb_t m_initCb;  // The callback function
            dumpCb_t m_dumpCb;  // The callback function
        };
        void* m_userp;  // The user pointer to pass to the callback (the symbol table)
        CallbackRecord(initCb_t cb, void* userp)
            : m_initCb(cb)
            , m_userp(userp) {}
        CallbackRecord(dumpCb_t cb, void* userp)
            : m_dumpCb(cb)
            , m_userp(userp) {}
    };

    vluint32_t* m_sigs_oldvalp;  ///< Old value store
    vluint64_t m_timeLastDump;  ///< Last time we did a dump
    std::vector<CallbackRecord> m_initCbs;  ///< Routines to initialize traciong
    std::vector<CallbackRecord> m_fullCbs;  ///< Routines to perform full dump
    std::vector<CallbackRecord> m_chgCbs;  ///< Routines to perform incremental dump
    std::vector<CallbackRecord> m_cleanupCbs;  ///< Routines to call at the end of dump
    bool m_fullDump;  ///< Whether a full dump is required on the next call to 'dump'
    vluint32_t m_nextCode;  ///< Next code number to assign
    vluint32_t m_numSignals;  ///< Number of distinct signals
    vluint32_t m_maxBits;  ///< Number of bits in the widest signal
    std::string m_moduleName;  ///< Name of module being trace initialized now
    char m_scopeEscape;
    double m_timeRes;  ///< Time resolution (ns/ms etc)
    double m_timeUnit;  ///< Time units (ns/ms etc)

    void addCallbackRecord(std::vector<CallbackRecord>& cbVec, CallbackRecord& cbRec);

    // Equivalent to 'this' but is of the sub-type 'T_Derived*'. Use 'self()->'
    // to access duck-typed functions to avoid a virtual function call.
    T_Derived* self() { return static_cast<T_Derived*>(this); }

    // 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_THREADED
    // Number of total trace buffers that have been allocated
    vluint32_t m_numTraceBuffers;

    // Size of trace buffers
    size_t m_traceBufferSize;

    // Buffers handed to worker for processing
    VerilatedThreadQueue<vluint32_t*> m_buffersToWorker;
    // Buffers returned from worker after processing
    VerilatedThreadQueue<vluint32_t*> m_buffersFromWorker;

    // Get a new trace buffer that can be populated. May block if none available
    vluint32_t* getTraceBuffer();

    // Write pointer into current buffer
    vluint32_t* m_traceBufferWritep;

    // End of trace buffer
    vluint32_t* m_traceBufferEndp;

    // The worker thread itself
    std::unique_ptr<std::thread> m_workerThread;

    // The function executed by the worker thread
    void workerThreadMain();

    // Wait until given buffer is placed in m_buffersFromWorker
    void waitForBuffer(const vluint32_t* bufferp);

    // Shut down and join worker, if it's running, otherwise do nothing
    void shutdownWorker();
#endif

    // CONSTRUCTORS
    VL_UNCOPYABLE(VerilatedTrace);

protected:
    //=========================================================================
    // Internals available to format specific implementations

    VerilatedAssertOneThread m_assertOne;  ///< Assert only called from single thread

    vluint32_t nextCode() const { return m_nextCode; }
    vluint32_t numSignals() const { return m_numSignals; }
    vluint32_t maxBits() const { return m_maxBits; }
    const std::string& moduleName() const { return m_moduleName; }
    void fullDump(bool value) { m_fullDump = value; }
    vluint64_t timeLastDump() { return m_timeLastDump; }

    double timeRes() const { return m_timeRes; }
    double timeUnit() const { return m_timeUnit; }
    std::string timeResStr() const;
    std::string timeUnitStr() const;

    void traceInit() VL_MT_UNSAFE;

    void declCode(vluint32_t code, vluint32_t bits, bool tri);

    /// Is this an escape?
    bool isScopeEscape(char c) { return isspace(c) || c == m_scopeEscape; }
    /// Character that splits scopes.  Note whitespace are ALWAYS escapes.
    char scopeEscape() { return m_scopeEscape; }

    void close();
    void flush();

    //=========================================================================
    // Virtual functions to be provided by the format specific implementation

    // Called when the trace moves forward to a new time point
    virtual void emitTimeChange(vluint64_t timeui) = 0;

    // These hooks are called before a full or change based dump is produced.
    // The return value indicates whether to proceed with the dump.
    virtual bool preFullDump() = 0;
    virtual bool preChangeDump() = 0;

public:
    //=========================================================================
    // External interface to client code

    explicit VerilatedTrace();
    ~VerilatedTrace();

    // Set time units (s/ms, defaults to ns)
    void set_time_unit(const char* unitp);
    void set_time_unit(const std::string& unit);
    // Set time resolution (s/ms, defaults to ns)
    void set_time_resolution(const char* unitp);
    void set_time_resolution(const std::string& unit);

    // Call
    void dump(vluint64_t timeui);

    //=========================================================================
    // Non-hot path internal interface to Verilator generated code

    void addInitCb(initCb_t cb, void* userp) VL_MT_UNSAFE_ONE;
    void addFullCb(dumpCb_t cb, void* userp) VL_MT_UNSAFE_ONE;
    void addChgCb(dumpCb_t cb, void* userp) VL_MT_UNSAFE_ONE;
    void addCleanupCb(dumpCb_t cb, void* userp) VL_MT_UNSAFE_ONE;

    void changeThread() { m_assertOne.changeThread(); }

    void module(const std::string& name) VL_MT_UNSAFE_ONE {
        m_assertOne.check();
        m_moduleName = name;
    }

    void scopeEscape(char flag) { m_scopeEscape = flag; }

    //=========================================================================
    // Hot path internal interface to Verilator generated code

    // Implementation note: We rely on the following duck-typed implementations
    // in the derived class T_Derived. These emit* functions record a format
    // specific trace entry. Normally one would use pure virtual functions for
    // these here, but we cannot afford dynamic dispatch for calling these as
    // this is very hot code during tracing.

    // duck-typed void emitBit(vluint32_t code, CData newval) = 0;
    // duck-typed void emitCData(vluint32_t code, CData newval, int bits) = 0;
    // duck-typed void emitSData(vluint32_t code, SData newval, int bits) = 0;
    // duck-typed void emitIData(vluint32_t code, IData newval, int bits) = 0;
    // duck-typed void emitQData(vluint32_t code, QData newval, int bits) = 0;
    // duck-typed void emitWData(vluint32_t code, const WData* newvalp, int bits) = 0;
    // duck-typed void emitDouble(vluint32_t code, double newval) = 0;

    vluint32_t* oldp(vluint32_t code) { return m_sigs_oldvalp + code; }

    // Write to previous value buffer value and emit trace entry.
    void fullBit(vluint32_t* oldp, CData newval);
    void fullCData(vluint32_t* oldp, CData newval, int bits);
    void fullSData(vluint32_t* oldp, SData newval, int bits);
    void fullIData(vluint32_t* oldp, IData newval, int bits);
    void fullQData(vluint32_t* oldp, QData newval, int bits);
    void fullWData(vluint32_t* oldp, const WData* newvalp, int bits);
    void fullDouble(vluint32_t* oldp, double newval);

#ifdef VL_TRACE_THREADED
    // Threaded tracing. Just dump everything in the trace buffer
    inline void chgBit(vluint32_t code, CData newval) {
        m_traceBufferWritep[0] = VerilatedTraceCommand::CHG_BIT_0 | newval;
        m_traceBufferWritep[1] = code;
        m_traceBufferWritep += 2;
        VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
    }
    inline void chgCData(vluint32_t code, CData newval, int bits) {
        m_traceBufferWritep[0] = (bits << 4) | VerilatedTraceCommand::CHG_CDATA;
        m_traceBufferWritep[1] = code;
        m_traceBufferWritep[2] = newval;
        m_traceBufferWritep += 3;
        VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
    }
    inline void chgSData(vluint32_t code, SData newval, int bits) {
        m_traceBufferWritep[0] = (bits << 4) | VerilatedTraceCommand::CHG_SDATA;
        m_traceBufferWritep[1] = code;
        m_traceBufferWritep[2] = newval;
        m_traceBufferWritep += 3;
        VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
    }
    inline void chgIData(vluint32_t code, IData newval, int bits) {
        m_traceBufferWritep[0] = (bits << 4) | VerilatedTraceCommand::CHG_IDATA;
        m_traceBufferWritep[1] = code;
        m_traceBufferWritep[2] = newval;
        m_traceBufferWritep += 3;
        VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
    }
    inline void chgQData(vluint32_t code, QData newval, int bits) {
        m_traceBufferWritep[0] = (bits << 4) | VerilatedTraceCommand::CHG_QDATA;
        m_traceBufferWritep[1] = code;
        *reinterpret_cast<QData*>(m_traceBufferWritep + 2) = newval;
        m_traceBufferWritep += 4;
        VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
    }
    inline void chgWData(vluint32_t code, const WData* newvalp, int bits) {
        m_traceBufferWritep[0] = (bits << 4) | VerilatedTraceCommand::CHG_WDATA;
        m_traceBufferWritep[1] = code;
        m_traceBufferWritep += 2;
        for (int i = 0; i < (bits + 31) / 32; ++i) { *m_traceBufferWritep++ = newvalp[i]; }
        VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
    }
    inline void chgDouble(vluint32_t code, double newval) {
        m_traceBufferWritep[0] = VerilatedTraceCommand::CHG_DOUBLE;
        m_traceBufferWritep[1] = code;
        // cppcheck-suppress invalidPointerCast
        *reinterpret_cast<double*>(m_traceBufferWritep + 2) = newval;
        m_traceBufferWritep += 4;
        VL_DEBUG_IF(assert(m_traceBufferWritep <= m_traceBufferEndp););
    }

#define CHG(name) chg##name##Impl
#else
#define CHG(name) chg##name
#endif

    // In non-threaded mode, these are called directly by the trace callbacks,
    // and are called chg*. In threaded 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
    inline void CHG(Bit)(vluint32_t* oldp, CData newval) {
        const vluint32_t diff = *oldp ^ newval;
        if (VL_UNLIKELY(diff)) fullBit(oldp, newval);
    }
    inline void CHG(CData)(vluint32_t* oldp, CData newval, int bits) {
        const vluint32_t diff = *oldp ^ newval;
        if (VL_UNLIKELY(diff)) fullCData(oldp, newval, bits);
    }
    inline void CHG(SData)(vluint32_t* oldp, SData newval, int bits) {
        const vluint32_t diff = *oldp ^ newval;
        if (VL_UNLIKELY(diff)) fullSData(oldp, newval, bits);
    }
    inline void CHG(IData)(vluint32_t* oldp, IData newval, int bits) {
        const vluint32_t diff = *oldp ^ newval;
        if (VL_UNLIKELY(diff)) fullIData(oldp, newval, bits);
    }
    inline void CHG(QData)(vluint32_t* oldp, QData newval, int bits) {
        const vluint64_t diff = *reinterpret_cast<QData*>(oldp) ^ newval;
        if (VL_UNLIKELY(diff)) fullQData(oldp, newval, bits);
    }
    inline void CHG(WData)(vluint32_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;
            }
        }
    }
    inline void CHG(Double)(vluint32_t* oldp, double newval) {
        // cppcheck-suppress invalidPointerCast
        if (VL_UNLIKELY(*reinterpret_cast<double*>(oldp) != newval)) fullDouble(oldp, newval);
    }

#undef CHG
};
#endif  // guard