File: verilated_timing.h

package info (click to toggle)
verilator 5.038-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 162,552 kB
  • sloc: cpp: 139,204; python: 20,931; ansic: 10,222; yacc: 6,000; lex: 1,925; makefile: 1,260; sh: 494; perl: 282; fortran: 22
file content (477 lines) | stat: -rw-r--r-- 20,032 bytes parent folder | download | duplicates (2)
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
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
//
// Code available from: https://verilator.org
//
// Copyright 2022 by Wilson Snyder. This program is free software; you can
// redistribute it and/or modify it under the terms of either the GNU Lesser
// General Public License Version 3 or the Perl Artistic License Version 2.0.
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
//
//*************************************************************************
///
/// \file
/// \brief Verilated timing header
///
/// This file is included automatically by Verilator in some of the C++ files
/// it generates if timing features are used.
///
/// This file is not part of the Verilated public-facing API.
/// It is only for internal use.
///
/// See the internals documentation docs/internals.rst for details.
///
//*************************************************************************
#ifndef VERILATOR_VERILATED_TIMING_H_
#define VERILATOR_VERILATED_TIMING_H_

#include "verilated.h"

#include <vector>

// clang-format off
// Some preprocessor magic to support both Clang and GCC coroutines with both libc++ and libstdc++
#if defined _LIBCPP_VERSION  // libc++
# if defined(__has_include) && !__has_include(<coroutine>) && __has_include(<experimental/coroutine>)
#  if __clang_major__ > 13  // Clang > 13 warns that coroutine types in std::experimental are deprecated
#   pragma clang diagnostic push
#   pragma clang diagnostic ignored "-Wdeprecated-experimental-coroutine"
#  endif
#  include <experimental/coroutine>
   namespace std {
       using namespace experimental;  // Bring std::experimental into the std namespace
   }
# else
#  include <coroutine>
# endif
#else
# if defined __clang__ && defined __GLIBCXX__ && !defined __cpp_impl_coroutine
#  define __cpp_impl_coroutine 1  // Clang doesn't define this, but it's needed for libstdc++
# endif
# include <coroutine>
# if __clang_major__ < 14
   namespace std {  // Bring coroutine library into std::experimental, as Clang < 14 expects it to be there
       namespace experimental {
           using namespace std;
       }
   }
# endif
#endif
// clang-format on

// Placeholder for compiling with --protect-ids
#define VL_UNKNOWN "<unknown>"

//=============================================================================
// VlFileLineDebug stores a SystemVerilog source code location. Used in VlCoroutineHandle for
// debugging purposes.

class VlFileLineDebug final {
    // MEMBERS
#ifdef VL_DEBUG
    const char* m_filename = nullptr;
    int m_lineno = 0;
#endif

public:
    // CONSTRUCTORS
    // Construct
    VlFileLineDebug() = default;
    VlFileLineDebug(const char* filename, int lineno)
#ifdef VL_DEBUG
        : m_filename{filename}
        , m_lineno{lineno}
#endif
    {
    }

    // METHODS
#ifdef VL_DEBUG
    const char* filename() const { return m_filename; }
    int lineno() const { return m_lineno; }
#endif
};

//=============================================================================
// VlCoroutineHandle is a non-copyable (but movable) coroutine handle. On resume, the handle is
// cleared, as we assume that either the coroutine has finished and deleted itself, or, if it got
// suspended, another VlCoroutineHandle was created to manage it.

class VlCoroutineHandle final {
    VL_UNCOPYABLE(VlCoroutineHandle);

    // MEMBERS
    std::coroutine_handle<> m_coro;  // The wrapped coroutine handle
    VlProcessRef m_process;  // Data of the suspended process, null if not needed
    VlFileLineDebug m_fileline;

public:
    // CONSTRUCTORS
    // Construct
    // non-explicit:
    // cppcheck-suppress noExplicitConstructor
    VlCoroutineHandle(VlProcessRef process)
        : m_coro{nullptr}
        , m_process{process} {
        if (m_process) m_process->state(VlProcess::WAITING);
    }
    VlCoroutineHandle(std::coroutine_handle<> coro, VlProcessRef process, VlFileLineDebug fileline)
        : m_coro{coro}
        , m_process{process}
        , m_fileline{fileline} {
        if (m_process) m_process->state(VlProcess::WAITING);
    }
    // Move the handle, leaving a nullptr
    // non-explicit:
    // cppcheck-suppress noExplicitConstructor
    VlCoroutineHandle(VlCoroutineHandle&& moved)
        : m_coro{std::exchange(moved.m_coro, nullptr)}
        , m_process{std::exchange(moved.m_process, nullptr)}
        , m_fileline{moved.m_fileline} {}
    // Destroy if the handle isn't null
    ~VlCoroutineHandle() {
        // Usually these coroutines should get resumed; we only need to clean up if we destroy a
        // model with some coroutines suspended
        if (VL_UNLIKELY(m_coro)) {
            m_coro.destroy();
            if (m_process && m_process->state() != VlProcess::KILLED) {
                m_process->state(VlProcess::FINISHED);
            }
        }
    }
    // METHODS
    // Move the handle, leaving a null handle
    auto& operator=(VlCoroutineHandle&& moved) {
        m_coro = std::exchange(moved.m_coro, nullptr);
        m_process = std::exchange(moved.m_process, nullptr);
        m_fileline = moved.m_fileline;
        return *this;
    }
    // Resume the coroutine if the handle isn't null and the process isn't killed
    void resume();
#ifdef VL_DEBUG
    void dump() const;
#endif
};

enum class VlDelayPhase : bool { ACTIVE, INACTIVE };

//=============================================================================
// VlDelayScheduler stores coroutines to be resumed at a certain simulation time. If the current
// time is equal to a coroutine's resume time, the coroutine gets resumed.

class VlDelayScheduler final {
    // TYPES
    // Time-sorted queue of timestamps and handles
    using VlDelayedCoroutineQueue = std::multimap<uint64_t, VlCoroutineHandle>;

    // MEMBERS
    VerilatedContext& m_context;
    VlDelayedCoroutineQueue m_queue;  // Coroutines to be restored at a certain simulation time
    std::vector<VlCoroutineHandle> m_zeroDelayed;  // Coroutines waiting for #0
    std::vector<VlCoroutineHandle> m_zeroDlyResumed;  // Coroutines that waited for #0 and are
                                                      // to be resumed. Kept as a field to avoid
                                                      // reallocation.

public:
    // CONSTRUCTORS
    explicit VlDelayScheduler(VerilatedContext& context)
        : m_context{context} {}
    // METHODS
    // Resume coroutines waiting for the current simulation time
    void resume();
    // Returns the simulation time of the next time slot (aborts if there are no delayed
    // coroutines)
    uint64_t nextTimeSlot() const;
    // Are there no delayed coroutines awaiting?
    bool empty() const { return m_queue.empty() && m_zeroDelayed.empty(); }
    // Are there coroutines to resume at the current simulation time?
    bool awaitingCurrentTime() const {
        return (!m_queue.empty() && (m_queue.cbegin()->first <= m_context.time()))
               || !m_zeroDelayed.empty();
    }
#ifdef VL_DEBUG
    void dump() const;
#endif
    // Used by coroutines for co_awaiting a certain simulation time
    auto delay(uint64_t delay, VlProcessRef process, const char* filename = VL_UNKNOWN,
               int lineno = 0) {
        struct Awaitable final {
            VlProcessRef process;  // Data of the suspended process, null if not needed
            VlDelayedCoroutineQueue& queue;
            std::vector<VlCoroutineHandle>& queueZeroDelay;
            const uint64_t delay;
            const VlDelayPhase phase;
            const VlFileLineDebug fileline;

            bool await_ready() const { return false; }  // Always suspend
            void await_suspend(std::coroutine_handle<> coro) {
                if (phase == VlDelayPhase::ACTIVE) {
                    queue.emplace(delay, VlCoroutineHandle{coro, process, fileline});
                } else {
                    queueZeroDelay.emplace_back(VlCoroutineHandle{coro, process, fileline});
                }
            }
            void await_resume() const {}
        };

        const VlDelayPhase phase = (delay == 0) ? VlDelayPhase::INACTIVE : VlDelayPhase::ACTIVE;
#ifdef VL_DEBUG
        if (phase == VlDelayPhase::INACTIVE) {
            VL_WARN_MT(filename, lineno, VL_UNKNOWN,
                       "Encountered #0 delay. #0 scheduling support is incomplete and the "
                       "process will be resumed before combinational logic evaluation.");
        }
#endif

        return Awaitable{process,       m_queue,
                         m_zeroDelayed, m_context.time() + delay,
                         phase,         VlFileLineDebug{filename, lineno}};
    }
};

//=============================================================================
// VlTriggerScheduler stores coroutines to be resumed by a trigger. It does not keep track of its
// trigger, relying on calling code to resume when appropriate. Coroutines are kept in two stages
// - 'uncommitted' and 'ready'. Whenever a coroutine is suspended, it lands in the 'uncommitted'
// stage. Only when commit() is called, these coroutines get moved to the 'ready' stage. That's
// when they can be resumed. This is done to avoid resuming processes before they start waiting.

class VlTriggerScheduler final {
    // TYPES
    using VlCoroutineVec = std::vector<VlCoroutineHandle>;

    // MEMBERS
    VlCoroutineVec m_uncommitted;  // Coroutines suspended before commit() was called
                                   // (not resumable)
    VlCoroutineVec m_ready;  // Coroutines that can be resumed (all coros from m_uncommitted are
                             // moved here in commit())
    VlCoroutineVec m_resumeQueue;  // Coroutines being resumed by resume(); kept as a field to
                                   // avoid reallocation. Resumed coroutines are moved to
                                   // m_resumeQueue to allow adding coroutines to m_ready
                                   // during resume(). Outside of resume() should always be empty.

public:
    // METHODS
    // Resumes all coroutines from the 'ready' stage
    void resume(const char* eventDescription = VL_UNKNOWN);
    // Moves all coroutines from m_uncommitted to m_ready
    void commit(const char* eventDescription = VL_UNKNOWN);
    // Are there no coroutines awaiting?
    bool empty() const { return m_ready.empty() && m_uncommitted.empty(); }
#ifdef VL_DEBUG
    void dump(const char* eventDescription) const;
#endif
    // Used by coroutines for co_awaiting a certain trigger
    auto trigger(bool commit, VlProcessRef process, const char* eventDescription = VL_UNKNOWN,
                 const char* filename = VL_UNKNOWN, int lineno = 0) {
        VL_DEBUG_IF(VL_DBG_MSGF("         Suspending process waiting for %s at %s:%d\n",
                                eventDescription, filename, lineno););
        struct Awaitable final {
            VlCoroutineVec& suspended;  // Coros waiting on trigger
            VlProcessRef process;  // Data of the suspended process, null if not needed
            VlFileLineDebug fileline;

            bool await_ready() const { return false; }  // Always suspend
            void await_suspend(std::coroutine_handle<> coro) {
                suspended.emplace_back(coro, process, fileline);
            }
            void await_resume() const {}
        };
        return Awaitable{commit ? m_ready : m_uncommitted, process,
                         VlFileLineDebug{filename, lineno}};
    }
};

//=============================================================================
// VlDynamicTriggerScheduler is used for cases where triggers cannot be statically referenced and
// evaluated. Coroutines that make use of this scheduler must adhere to a certain procedure:
//     __Vtrigger = 0;
//     <locals and inits required for trigger eval>
//     while (!__Vtrigger) {
//         co_await __VdynSched.evaluation();
//         <pre updates>;
//         __Vtrigger = <trigger eval>;
//         __VdynShed.anyTriggered(__Vtrigger);
//         [optionally] co_await __VdynSched.postUpdate();
//         <post updates>;
//     }
//    co_await __VdynSched.resumption();
// The coroutines get resumed at trigger evaluation time, evaluate their local triggers, optionally
// await the post update step, and if the trigger is set, await proper resumption in the 'act' eval
// step.

class VlDynamicTriggerScheduler final {
    // TYPES
    using VlCoroutineVec = std::vector<VlCoroutineHandle>;

    // MEMBERS
    bool m_anyTriggered = false;  // If true, at least one trigger was set
    VlCoroutineVec m_suspended;  // Suspended coroutines awaiting trigger evaluation
    VlCoroutineVec m_evaluated;  // Coroutines currently being evaluated (for evaluate())
    VlCoroutineVec m_triggered;  // Coroutines whose triggers were set, and are awaiting resumption
    VlCoroutineVec m_post;  // Coroutines awaiting the post update step (only relevant for triggers
                            // with destructive post updates, e.g. named events)

    // METHODS
    auto awaitable(VlProcessRef process, VlCoroutineVec& queue, const char* filename, int lineno) {
        struct Awaitable final {
            VlProcessRef process;  // Data of the suspended process, null if not needed
            VlCoroutineVec& suspended;  // Coros waiting on trigger
            VlFileLineDebug fileline;

            bool await_ready() const { return false; }  // Always suspend
            void await_suspend(std::coroutine_handle<> coro) {
                suspended.emplace_back(coro, process, fileline);
            }
            void await_resume() const {}
        };
        return Awaitable{process, queue, VlFileLineDebug{filename, lineno}};
    }

public:
    // Evaluates all dynamic triggers (resumed coroutines that co_await evaluation())
    bool evaluate();
    // Called by coroutines that evaluate triggers to notify the scheduler if any triggers were set
    void anyTriggered(bool triggered) { m_anyTriggered = m_anyTriggered || triggered; }
    // Runs post updates for all dynamic triggers (resumes coroutines that co_await postUpdate())
    void doPostUpdates();
    // Resumes all coroutines whose triggers are set (those that co_await resumption())
    void resume();
#ifdef VL_DEBUG
    void dump() const;
#endif
    // Used by coroutines for co_awaiting trigger evaluation
    auto evaluation(VlProcessRef process, const char* eventDescription, const char* filename,
                    int lineno) {
        VL_DEBUG_IF(VL_DBG_MSGF("         Suspending process waiting for %s at %s:%d\n",
                                eventDescription, filename, lineno););
        return awaitable(process, m_suspended, filename, lineno);
    }
    // Used by coroutines for co_awaiting the trigger post update step
    auto postUpdate(VlProcessRef process, const char* eventDescription, const char* filename,
                    int lineno) {
        VL_DEBUG_IF(
            VL_DBG_MSGF("         Process waiting for %s at %s:%d awaiting the post update step\n",
                        eventDescription, filename, lineno););
        return awaitable(process, m_post, filename, lineno);
    }
    // Used by coroutines for co_awaiting the resumption step (in 'act' eval)
    auto resumption(VlProcessRef process, const char* eventDescription, const char* filename,
                    int lineno) {
        VL_DEBUG_IF(VL_DBG_MSGF("         Process waiting for %s at %s:%d awaiting resumption\n",
                                eventDescription, filename, lineno););
        return awaitable(process, m_triggered, filename, lineno);
    }
};

//=============================================================================
// VlForever is a helper awaitable type for suspending coroutines forever. Used for constant
// wait statements.

struct VlForever final {
    bool await_ready() const { return false; }  // Always suspend
    void await_suspend(std::coroutine_handle<> coro) const { coro.destroy(); }
    void await_resume() const {}
};

//=============================================================================
// VlForkSync is used to manage fork..join and fork..join_any constructs.

class VlForkSync final {
    // VlJoin stores the handle of a suspended coroutine that did a fork..join or fork..join_any.
    // If the counter reaches 0, the suspended coroutine shall be resumed.
    struct VlJoin final {
        size_t m_counter = 0;  // When reaches 0, resume suspended coroutine
        VlCoroutineHandle m_susp;  // Coroutine to resume
    };

    // The join info is shared among all forked processes
    std::shared_ptr<VlJoin> m_join;

public:
    // Create the join object and set the counter to the specified number
    void init(size_t count, VlProcessRef process) { m_join.reset(new VlJoin{count, {process}}); }
    // Called whenever any of the forked processes finishes. If the join counter reaches 0, the
    // main process gets resumed
    void done(const char* filename = VL_UNKNOWN, int lineno = 0);
    // Used by coroutines for co_awaiting a join
    auto join(VlProcessRef process, const char* filename = VL_UNKNOWN, int lineno = 0) {
        assert(m_join);
        VL_DEBUG_IF(
            VL_DBG_MSGF("             Awaiting join of fork at: %s:%d\n", filename, lineno););
        struct Awaitable final {
            VlProcessRef process;  // Data of the suspended process, null if not needed
            const std::shared_ptr<VlJoin> join;  // Join to await on
            VlFileLineDebug fileline;

            bool await_ready() { return join->m_counter == 0; }  // Suspend if join still exists
            void await_suspend(std::coroutine_handle<> coro) {
                join->m_susp = {coro, process, fileline};
            }
            void await_resume() const {}
        };
        return Awaitable{process, m_join, VlFileLineDebug{filename, lineno}};
    }
};

//=============================================================================
// VlCoroutine
// Return value of a coroutine. Used for chaining coroutine suspension/resumption.

class VlCoroutine final {
private:
    // TYPES
    struct VlPromise final {
        std::coroutine_handle<> m_continuation;  // Coroutine to resume after this one finishes
        VlCoroutine* m_corop = nullptr;  // Pointer to the coroutine return object

        ~VlPromise();

        VlCoroutine get_return_object() { return {this}; }

        // Never suspend at the start of the coroutine
        std::suspend_never initial_suspend() const { return {}; }

        // Never suspend at the end of the coroutine (thanks to this, the coroutine will clean up
        // after itself)
        std::suspend_never final_suspend() noexcept;

        void unhandled_exception() const { std::abort(); }
        void return_void() const {}
    };

    // MEMBERS
    VlPromise* m_promisep;  // The promise created for this coroutine

public:
    // TYPES
    using promise_type = VlPromise;  // promise_type has to be public

    // CONSTRUCTORS
    // Construct
    // cppcheck-suppress noExplicitConstructor
    VlCoroutine(VlPromise* promisep)
        : m_promisep{promisep} {
        m_promisep->m_corop = this;
    }
    // Move. Update the pointers each time the return object is moved
    // cppcheck-suppress noExplicitConstructor
    VlCoroutine(VlCoroutine&& other)
        : m_promisep{std::exchange(other.m_promisep, nullptr)} {
        if (m_promisep) m_promisep->m_corop = this;
    }
    ~VlCoroutine() {
        // Indicate to the promise that the return object is gone
        if (m_promisep) m_promisep->m_corop = nullptr;
    }

    // METHODS
    // Suspend the awaiter if the coroutine is suspended (the promise exists)
    bool await_ready() const noexcept { return !m_promisep; }
    // Set the awaiting coroutine as the continuation of the current coroutine
    void await_suspend(std::coroutine_handle<> coro) { m_promisep->m_continuation = coro; }
    void await_resume() const noexcept {}
};

#endif  // Guard