2022-05-15 17:03:32 +02:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// DESCRIPTION: Verilator: Scheduling
|
|
|
|
|
//
|
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2025-01-01 14:30:25 +01:00
|
|
|
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
|
2022-05-15 17:03:32 +02:00
|
|
|
// 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
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
|
|
|
|
|
#ifndef VERILATOR_V3SCHED_H_
|
|
|
|
|
#define VERILATOR_V3SCHED_H_
|
|
|
|
|
|
|
|
|
|
#include "config_build.h"
|
|
|
|
|
#include "verilatedos.h"
|
2022-08-05 13:15:59 +02:00
|
|
|
|
2022-05-15 17:03:32 +02:00
|
|
|
#include "V3Ast.h"
|
|
|
|
|
|
|
|
|
|
#include <functional>
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
#include <unordered_map>
|
2022-05-15 17:03:32 +02:00
|
|
|
#include <utility>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
2025-10-27 16:16:28 +01:00
|
|
|
class SenExprBuilder;
|
|
|
|
|
|
2022-05-15 17:03:32 +02:00
|
|
|
//============================================================================
|
|
|
|
|
|
|
|
|
|
namespace V3Sched {
|
|
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
|
// Throughout scheduling, we need to keep hold of AstActive nodes, together with the AstScope that
|
|
|
|
|
// they are under. LogicByScope is simply a vector of such pairs, with some additional convenience
|
|
|
|
|
// methods.
|
|
|
|
|
struct LogicByScope final : public std::vector<std::pair<AstScope*, AstActive*>> {
|
|
|
|
|
// Add logic
|
|
|
|
|
void add(AstScope* scopep, AstSenTree* senTreep, AstNode* logicp) {
|
|
|
|
|
UASSERT_OBJ(!logicp->backp(), logicp, "Already linked");
|
2025-08-18 01:14:34 +02:00
|
|
|
if (empty() || back().first != scopep || back().second->sentreep() != senTreep) {
|
2022-05-15 17:03:32 +02:00
|
|
|
emplace_back(scopep, new AstActive{logicp->fileline(), "", senTreep});
|
|
|
|
|
}
|
|
|
|
|
back().second->addStmtsp(logicp);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Create copy, with the AstActives cloned
|
|
|
|
|
LogicByScope clone() const {
|
|
|
|
|
LogicByScope result;
|
|
|
|
|
for (const auto& pair : *this) {
|
|
|
|
|
result.emplace_back(pair.first, pair.second->cloneTree(false));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete actives (they should all be empty)
|
|
|
|
|
void deleteActives() {
|
|
|
|
|
for (const auto& pair : *this) {
|
|
|
|
|
AstActive* const activep = pair.second;
|
2025-09-09 14:55:00 +02:00
|
|
|
if (v3Global.opt.debugCheck()) {
|
|
|
|
|
for (AstNode* nodep = activep->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
|
|
|
AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure);
|
|
|
|
|
UASSERT_OBJ(procp && !procp->stmtsp(), activep, "Leftover logic");
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-15 17:03:32 +02:00
|
|
|
if (activep->backp()) activep->unlinkFrBack();
|
|
|
|
|
activep->deleteTree();
|
|
|
|
|
}
|
|
|
|
|
clear();
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-30 18:49:30 +02:00
|
|
|
void foreachLogic(const std::function<void(AstNode*)>& f) const {
|
2022-05-15 17:03:32 +02:00
|
|
|
for (const auto& pair : *this) {
|
|
|
|
|
for (AstNode* nodep = pair.second->stmtsp(); nodep; nodep = nodep->nextp()) f(nodep);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Logic in the design is classified based on what can trigger its execution.
|
|
|
|
|
// For details see the internals documentation.
|
|
|
|
|
struct LogicClasses final {
|
|
|
|
|
LogicByScope m_static; // Static variable initializers
|
|
|
|
|
LogicByScope m_initial; // initial blocks
|
|
|
|
|
LogicByScope m_final; // final blocks
|
|
|
|
|
LogicByScope m_comb; // Combinational logic (logic with implicit sensitivities)
|
2022-12-03 00:46:38 +01:00
|
|
|
LogicByScope m_clocked; // Clocked (or sequential) logic (logic with explicit sensitivities)
|
2022-05-15 17:03:32 +02:00
|
|
|
LogicByScope m_hybrid; // Hybrid logic (combinational logic with some explicit sensitivities)
|
2022-10-13 21:04:43 +02:00
|
|
|
LogicByScope m_postponed; // Postponed logic ($strobe)
|
2022-12-23 13:34:49 +01:00
|
|
|
LogicByScope m_observed; // Observed logic (contains AstAlwaysObserved)
|
|
|
|
|
LogicByScope m_reactive; // Reactive logic (contains AstAlwaysReactive)
|
2022-05-15 17:03:32 +02:00
|
|
|
|
|
|
|
|
LogicClasses() = default;
|
|
|
|
|
VL_UNCOPYABLE(LogicClasses);
|
|
|
|
|
LogicClasses(LogicClasses&&) = default;
|
|
|
|
|
LogicClasses& operator=(LogicClasses&&) = default;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Combinational (including hybrid) logic, and clocked logic in partitioned to compute all clock
|
|
|
|
|
// signals in the 'act' region. For details see the internals documentation.
|
|
|
|
|
struct LogicRegions final {
|
2025-08-19 10:27:59 +02:00
|
|
|
LogicByScope m_pre; // AstAlwaysPre logic in 'act' region
|
2022-05-15 17:03:32 +02:00
|
|
|
LogicByScope m_act; // 'act' region logic
|
|
|
|
|
LogicByScope m_nba; // 'nba' region logic
|
2024-07-10 00:31:58 +02:00
|
|
|
LogicByScope m_obs; // AstAlwaysObserved logic in 'obs' region
|
|
|
|
|
LogicByScope m_react; // AstAlwaysReactive logic in 're' region
|
2022-05-15 17:03:32 +02:00
|
|
|
|
|
|
|
|
LogicRegions() = default;
|
|
|
|
|
VL_UNCOPYABLE(LogicRegions);
|
|
|
|
|
LogicRegions(LogicRegions&&) = default;
|
|
|
|
|
LogicRegions& operator=(LogicRegions&&) = default;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Combinational (including hybrid) logic is replicated into the various scheduling regions.
|
|
|
|
|
// For details see the internals documentation.
|
|
|
|
|
struct LogicReplicas final {
|
|
|
|
|
LogicByScope m_ico; // Logic replicated into the 'ico' (Input Combinational) region
|
|
|
|
|
LogicByScope m_act; // Logic replicated into the 'act' region
|
|
|
|
|
LogicByScope m_nba; // Logic replicated into the 'nba' region
|
2024-07-10 00:31:58 +02:00
|
|
|
LogicByScope m_obs; // Logic replicated into the 'obs' region
|
|
|
|
|
LogicByScope m_react; // Logic replicated into the 're' region
|
2022-05-15 17:03:32 +02:00
|
|
|
|
|
|
|
|
LogicReplicas() = default;
|
|
|
|
|
VL_UNCOPYABLE(LogicReplicas);
|
|
|
|
|
LogicReplicas(LogicReplicas&&) = default;
|
|
|
|
|
LogicReplicas& operator=(LogicReplicas&&) = default;
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-01 16:43:20 +01:00
|
|
|
// A TriggerKit holds all the components related to a scheduling region triggers
|
|
|
|
|
//
|
|
|
|
|
// Each piece of code is executed when some trigger bits are set. There is no
|
|
|
|
|
// code after scheduling which is not conditional on at least one trigger bit.
|
|
|
|
|
//
|
|
|
|
|
// There are 3 different kinds of triggers
|
|
|
|
|
// 1. "Sense" triggers, which correspond to a unique SenItem in the design
|
|
|
|
|
// 2. "Extra" triggers, which represent non SenItem based conditions
|
|
|
|
|
// 3. "Pre" triggers, which are only used in the 'act' region. These are a copy
|
|
|
|
|
// of some of the "Sense" triggers but only ever fire during one evaluation
|
|
|
|
|
// of the 'act' loop. They are used for executing AlwaysPre block that
|
|
|
|
|
// reside in the 'act' region. (AlwaysPre in 'nba' use the "Sense" triggers.)
|
|
|
|
|
//
|
|
|
|
|
// All trigger bits are stored in an unpacked array of fixed sized words, which
|
|
|
|
|
// is informally referred to in various places as the "trigger vector".
|
|
|
|
|
// There is only one trigger vector for each eval loop (~scheduling region).
|
|
|
|
|
//
|
|
|
|
|
// The organization of the trigger vector (array) is shown here, LSB on the
|
|
|
|
|
// right, MSB on the left. There are 4 main sections:
|
|
|
|
|
//
|
|
|
|
|
// | <- bit N-1 bit 0 -> |
|
|
|
|
|
// +--------------------+----------------+----------------------------------+--------------------+
|
|
|
|
|
// | Pre triggers | Extra triggers | Sense triggers | Pre Sense triggers |
|
|
|
|
|
// +--------------------+----------------+----------------------------------+--------------------+
|
|
|
|
|
// | 'pre' | 'vec' |
|
|
|
|
|
//
|
|
|
|
|
// The section labelled "Pre Sense triggers" contains regular "Sense" triggers
|
|
|
|
|
// that are also duplicated in the "Pre triggers" section the first time they
|
|
|
|
|
// fire.
|
|
|
|
|
//
|
|
|
|
|
// The "Sense triggers" section contains the rest of the "Sense" triggers that
|
|
|
|
|
// do not need a copy in "Pre triggers".
|
|
|
|
|
//
|
|
|
|
|
// The "Sense triggers" and "Pre Sense triggers" together contain all "Sense"
|
|
|
|
|
// triggers described in point 1 above. "Extra triggers" contains the
|
|
|
|
|
// non-SenItem based additional conditions described in point 2, and the
|
|
|
|
|
// "Per triggers" section contains the "Pre" triggers described in point 3.
|
|
|
|
|
//
|
|
|
|
|
// All 4 sections in the trigger vector are padded to contain a whole word
|
|
|
|
|
// worth of bits (padding bits are always zero). Any one of the 4 sections
|
|
|
|
|
// can be empty, but "Pre triggers" and "Pre Sense triggers" are always the
|
|
|
|
|
// same size.
|
|
|
|
|
//
|
|
|
|
|
// The portion holding the Sense triggers and Extra triggers is referred to
|
|
|
|
|
// in various places as the 'vec' part, and is also informally referred to as
|
|
|
|
|
// the trigger vector.
|
|
|
|
|
//
|
|
|
|
|
// The portion holding the Pre trigger is named 'pre'
|
|
|
|
|
//
|
|
|
|
|
// The combination of 'pre' + 'vec' is the "extended trigger vector" referred
|
|
|
|
|
// to as 'ext' in various places.
|
|
|
|
|
//
|
|
|
|
|
// In realistic designs there are often no "Pre" triggers, and only a few
|
|
|
|
|
// "Extra" triggers, with "Sense" triggers taking up the bulk of the bits.
|
|
|
|
|
//
|
2025-10-31 19:29:11 +01:00
|
|
|
class TriggerKit final {
|
|
|
|
|
// Triggers are storead as an UnpackedArray with a fixed word size
|
|
|
|
|
static constexpr uint32_t WORD_SIZE_LOG2 = 6; // 64-bits / VL_QUADSIZE
|
|
|
|
|
static constexpr uint32_t WORD_SIZE = 1 << WORD_SIZE_LOG2;
|
|
|
|
|
|
|
|
|
|
const std::string m_name; // TriggerKit name
|
|
|
|
|
const bool m_slow; // TriggerKit is for schedulign 'slow' code
|
2025-11-01 16:43:20 +01:00
|
|
|
const uint32_t m_nSenseWords; // Number of words for Sense triggers
|
|
|
|
|
const uint32_t m_nExtraWords; // Number of words for Extra triggers
|
|
|
|
|
const uint32_t m_nPreWords; // Number of words for 'pre' part
|
|
|
|
|
const uint32_t m_nVecWords = m_nSenseWords + m_nExtraWords; // Number of words in 'vec' part
|
2025-10-31 19:29:11 +01:00
|
|
|
|
|
|
|
|
// Data type of a single trigger word
|
|
|
|
|
AstNodeDType* m_wordDTypep = nullptr;
|
2025-11-01 16:43:20 +01:00
|
|
|
// Data type of a trigger vector holding one copy of all triggers
|
|
|
|
|
AstUnpackArrayDType* m_trigVecDTypep = nullptr;
|
|
|
|
|
// Data type of an extended trigger vector holding one copy of all triggers
|
|
|
|
|
// + additional copy of 'pre' triggers
|
|
|
|
|
AstUnpackArrayDType* m_trigExtDTypep = nullptr;
|
|
|
|
|
// The AstVarScope representing the extended trigger vector
|
2025-10-31 19:29:11 +01:00
|
|
|
AstVarScope* m_vscp = nullptr;
|
2025-10-27 16:16:28 +01:00
|
|
|
// The AstCFunc that computes the current active triggers
|
2025-10-31 19:29:11 +01:00
|
|
|
AstCFunc* m_compp = nullptr;
|
2025-11-01 16:43:20 +01:00
|
|
|
// The AstCFunc that dumps a trigger vector
|
2025-10-31 19:29:11 +01:00
|
|
|
AstCFunc* m_dumpp = nullptr;
|
2025-11-01 16:43:20 +01:00
|
|
|
// The AstCFunc that dumps an exended trigger vector - create lazily
|
|
|
|
|
mutable AstCFunc* m_dumpExtp = nullptr;
|
2025-10-31 19:29:11 +01:00
|
|
|
// The AstCFunc testing if a trigger vector has any bits set - create lazily
|
2025-11-01 16:43:20 +01:00
|
|
|
mutable AstCFunc* m_anySetVecp = nullptr;
|
|
|
|
|
mutable AstCFunc* m_anySetExtp = nullptr;
|
2025-10-31 19:29:11 +01:00
|
|
|
// The AstCFunc setting bits in a trigger vector that are set in another - create lazily
|
2025-11-01 16:43:20 +01:00
|
|
|
mutable AstCFunc* m_orIntoVecp = nullptr;
|
|
|
|
|
mutable AstCFunc* m_orIntoExtp = nullptr;
|
2025-10-31 19:29:11 +01:00
|
|
|
// The AstCFunc setting a trigger vector to all zeroes - create lazily
|
|
|
|
|
mutable AstCFunc* m_clearp = nullptr;
|
|
|
|
|
|
2025-11-01 16:43:20 +01:00
|
|
|
// The map from 'pre' input SenTree to trigger SenTree
|
|
|
|
|
std::unordered_map<const AstSenTree*, AstSenTree*> m_mapPre;
|
|
|
|
|
// The map from other input SenTree to trigger SenTree
|
|
|
|
|
std::unordered_map<const AstSenTree*, AstSenTree*> m_mapVec;
|
2025-10-31 19:29:11 +01:00
|
|
|
|
|
|
|
|
// Methods to lazy construct functions processing trigger vectors
|
2025-11-01 16:43:20 +01:00
|
|
|
AstCFunc* createDumpExtFunc() const;
|
|
|
|
|
AstCFunc* createAnySetFunc(AstUnpackArrayDType* const dtypep) const;
|
2025-10-31 19:29:11 +01:00
|
|
|
AstCFunc* createClearFunc() const;
|
2025-11-01 16:43:20 +01:00
|
|
|
AstCFunc* createOrIntoFunc(AstUnpackArrayDType* const iDtypep) const;
|
2025-10-27 16:16:28 +01:00
|
|
|
|
2025-11-01 16:43:20 +01:00
|
|
|
// Create an AstSenTree that is sensitive to the given trigger indices
|
|
|
|
|
AstSenTree* newTriggerSenTree(AstVarScope* vscp, const std::vector<uint32_t>& indices) const;
|
|
|
|
|
|
|
|
|
|
TriggerKit(const std::string& name, bool slow, uint32_t nSenseWords, uint32_t nExtraWords,
|
|
|
|
|
uint32_t nPreWords);
|
2025-10-31 19:29:11 +01:00
|
|
|
VL_UNCOPYABLE(TriggerKit);
|
|
|
|
|
TriggerKit& operator=(TriggerKit&&) = delete;
|
2025-10-27 16:16:28 +01:00
|
|
|
|
2025-10-31 19:29:11 +01:00
|
|
|
public:
|
|
|
|
|
// Move constructible
|
|
|
|
|
TriggerKit(TriggerKit&&) = default;
|
|
|
|
|
~TriggerKit() = default;
|
|
|
|
|
|
|
|
|
|
// Utility for extra trigger allocation
|
|
|
|
|
class ExtraTriggers final {
|
|
|
|
|
friend class TriggerKit;
|
|
|
|
|
std::vector<string> m_descriptions; // Human readable description of extra triggers
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
ExtraTriggers() = default;
|
|
|
|
|
~ExtraTriggers() = default;
|
|
|
|
|
|
|
|
|
|
uint32_t allocate(const string& description) {
|
|
|
|
|
m_descriptions.push_back(description);
|
|
|
|
|
return m_descriptions.size() - 1;
|
|
|
|
|
}
|
|
|
|
|
uint32_t size() const { return m_descriptions.size(); }
|
|
|
|
|
};
|
2025-10-27 16:16:28 +01:00
|
|
|
|
|
|
|
|
// Create a TriggerKit for the given AstSenTree vector
|
2025-10-31 19:29:11 +01:00
|
|
|
static TriggerKit create(AstNetlist* netlistp, //
|
|
|
|
|
AstCFunc* const initFuncp, //
|
|
|
|
|
SenExprBuilder& senExprBuilder, //
|
2025-11-01 16:43:20 +01:00
|
|
|
const std::vector<const AstSenTree*>& preTreeps, //
|
2025-10-31 19:29:11 +01:00
|
|
|
const std::vector<const AstSenTree*>& senTreeps, //
|
|
|
|
|
const string& name, //
|
|
|
|
|
const ExtraTriggers& extraTriggers, //
|
|
|
|
|
bool slow);
|
|
|
|
|
|
|
|
|
|
// ACCESSORS
|
|
|
|
|
AstVarScope* vscp() const { return m_vscp; }
|
|
|
|
|
AstCFunc* compp() const { return m_compp; }
|
2025-11-01 16:43:20 +01:00
|
|
|
const std::unordered_map<const AstSenTree*, AstSenTree*>& mapPre() const { return m_mapPre; }
|
|
|
|
|
const std::unordered_map<const AstSenTree*, AstSenTree*>& mapVec() const { return m_mapVec; }
|
2025-10-31 19:29:11 +01:00
|
|
|
|
|
|
|
|
// Helpers for code generation - lazy construct relevant functions
|
|
|
|
|
AstNodeStmt* newAndNotCall(AstVarScope* op, AstVarScope* ap, AstVarScope* bp) const;
|
|
|
|
|
AstNodeExpr* newAnySetCall(AstVarScope* vscp) const;
|
|
|
|
|
AstNodeStmt* newClearCall(AstVarScope* vscp) const;
|
|
|
|
|
AstNodeStmt* newOrIntoCall(AstVarScope* op, AstVarScope* ip) const;
|
|
|
|
|
// Helpers for code generation
|
2025-11-01 16:43:20 +01:00
|
|
|
AstNodeStmt* newCompCall(AstVarScope* vscp = nullptr) const;
|
2025-10-31 19:29:11 +01:00
|
|
|
AstNodeStmt* newDumpCall(AstVarScope* vscp, const std::string& tag, bool debugOnly) const;
|
2025-11-01 16:43:20 +01:00
|
|
|
// Create a new (non-extended) trigger vector - might return nullptr if there are no triggers
|
2025-10-31 19:29:11 +01:00
|
|
|
AstVarScope* newTrigVec(const std::string& name) const;
|
|
|
|
|
|
2025-11-01 16:43:20 +01:00
|
|
|
// Create an AstSenTree that is sensitive to the given Extra trigger
|
|
|
|
|
AstSenTree* newExtraTriggerSenTree(AstVarScope* vscp, uint32_t index) const;
|
2025-10-31 19:29:11 +01:00
|
|
|
|
|
|
|
|
// Set then extra trigger bit at 'index' to the value of 'vscp', then set 'vscp' to 0
|
|
|
|
|
void addExtraTriggerAssignment(AstVarScope* vscp, uint32_t index) const;
|
2025-10-27 16:16:28 +01:00
|
|
|
};
|
|
|
|
|
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
// Everything needed for combining timing with static scheduling.
|
|
|
|
|
class TimingKit final {
|
|
|
|
|
AstCFunc* m_resumeFuncp = nullptr; // Global timing resume function
|
|
|
|
|
AstCFunc* m_commitFuncp = nullptr; // Global timing commit function
|
|
|
|
|
|
|
|
|
|
// Additional var sensitivities for V3Order
|
|
|
|
|
std::map<const AstVarScope*, std::set<AstSenTree*>> m_externalDomains;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
LogicByScope m_lbs; // Actives that resume timing schedulers
|
2022-10-22 16:05:39 +02:00
|
|
|
AstNodeStmt* m_postUpdates = nullptr; // Post updates for the trigger eval function
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
|
|
|
|
|
// Remaps external domains using the specified trigger map
|
2023-09-25 04:12:23 +02:00
|
|
|
std::map<const AstVarScope*, std::vector<AstSenTree*>> remapDomains(
|
|
|
|
|
const std::unordered_map<const AstSenTree*, AstSenTree*>& trigMap) const VL_MT_DISABLED;
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
// Creates a timing resume call (if needed, else returns null)
|
2023-09-25 04:12:23 +02:00
|
|
|
AstCCall* createResume(AstNetlist* const netlistp) VL_MT_DISABLED;
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
// Creates a timing commit call (if needed, else returns null)
|
2023-09-25 04:12:23 +02:00
|
|
|
AstCCall* createCommit(AstNetlist* const netlistp) VL_MT_DISABLED;
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
|
|
|
|
|
TimingKit() = default;
|
2022-10-22 16:05:39 +02:00
|
|
|
TimingKit(LogicByScope&& lbs, AstNodeStmt* postUpdates,
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
std::map<const AstVarScope*, std::set<AstSenTree*>>&& externalDomains)
|
|
|
|
|
: m_externalDomains{externalDomains}
|
2022-10-22 16:05:39 +02:00
|
|
|
, m_lbs{lbs}
|
|
|
|
|
, m_postUpdates{postUpdates} {}
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
VL_UNCOPYABLE(TimingKit);
|
|
|
|
|
TimingKit(TimingKit&&) = default;
|
|
|
|
|
TimingKit& operator=(TimingKit&&) = default;
|
|
|
|
|
};
|
|
|
|
|
|
2023-12-05 04:11:07 +01:00
|
|
|
class VirtIfaceTriggers final {
|
2025-07-12 03:04:51 +02:00
|
|
|
// Represents a specific member in a virtual interface
|
|
|
|
|
struct IfaceMember final {
|
|
|
|
|
const AstIface* m_ifacep; // Interface type
|
2025-10-29 22:27:15 +01:00
|
|
|
const AstVar* m_memberp; // Member variable
|
2025-07-12 03:04:51 +02:00
|
|
|
|
2025-10-29 22:27:15 +01:00
|
|
|
// TODO: sorting by pointer is non-deterministic
|
2025-07-12 03:04:51 +02:00
|
|
|
bool operator<(const IfaceMember& other) const {
|
|
|
|
|
if (m_ifacep != other.m_ifacep) return m_ifacep < other.m_ifacep;
|
2025-10-29 22:27:15 +01:00
|
|
|
return m_memberp < other.m_memberp;
|
2025-07-12 03:04:51 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-18 15:07:31 +02:00
|
|
|
public:
|
2023-12-05 04:11:07 +01:00
|
|
|
using IfaceSensMap = std::map<const AstIface*, AstSenTree*>;
|
2025-10-29 22:27:15 +01:00
|
|
|
using IfaceMemberSensMap = std::map<IfaceMember, AstSenTree*>;
|
2025-07-12 03:04:51 +02:00
|
|
|
|
2025-10-29 22:27:15 +01:00
|
|
|
std::vector<std::pair<const AstIface*, AstVarScope*>> m_ifaceTriggers;
|
|
|
|
|
std::vector<std::pair<IfaceMember, AstVarScope*>> m_memberTriggers;
|
2023-12-05 04:11:07 +01:00
|
|
|
|
2025-10-29 22:27:15 +01:00
|
|
|
void addIfaceTrigger(const AstIface* ifacep, AstVarScope* vscp) {
|
|
|
|
|
m_ifaceTriggers.emplace_back(ifacep, vscp);
|
|
|
|
|
}
|
|
|
|
|
void addMemberTrigger(const AstIface* ifacep, const AstVar* memberp, AstVarScope* vscp) {
|
|
|
|
|
m_memberTriggers.emplace_back(IfaceMember{ifacep, memberp}, vscp);
|
2025-07-12 03:04:51 +02:00
|
|
|
}
|
|
|
|
|
|
2025-10-29 22:27:15 +01:00
|
|
|
AstVarScope* findMemberTrigger(const AstIface* ifacep, const AstVar* memberp) const {
|
2025-07-12 03:04:51 +02:00
|
|
|
for (const auto& pair : m_memberTriggers) {
|
2025-10-29 22:27:15 +01:00
|
|
|
const IfaceMember& item = pair.first;
|
|
|
|
|
if (item.m_ifacep == ifacep && item.m_memberp == memberp) return pair.second;
|
2025-07-12 03:04:51 +02:00
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 19:29:11 +01:00
|
|
|
IfaceMemberSensMap makeMemberToSensMap(const TriggerKit& trigKit, uint32_t vifTriggerIndex,
|
2025-07-12 03:04:51 +02:00
|
|
|
AstVarScope* trigVscp) const;
|
|
|
|
|
|
2025-10-31 19:29:11 +01:00
|
|
|
IfaceSensMap makeIfaceToSensMap(const TriggerKit& trigKit, uint32_t vifTriggerIndex,
|
2023-12-05 04:11:07 +01:00
|
|
|
AstVarScope* trigVscp) const;
|
2025-07-12 03:04:51 +02:00
|
|
|
|
2023-12-05 04:11:07 +01:00
|
|
|
VL_UNCOPYABLE(VirtIfaceTriggers);
|
|
|
|
|
VirtIfaceTriggers() = default;
|
|
|
|
|
VirtIfaceTriggers(VirtIfaceTriggers&&) = default;
|
|
|
|
|
VirtIfaceTriggers& operator=(VirtIfaceTriggers&&) = default;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Creates trigger vars for signals driven via virtual interfaces
|
|
|
|
|
VirtIfaceTriggers makeVirtIfaceTriggers(AstNetlist* nodep) VL_MT_DISABLED;
|
|
|
|
|
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
// Creates the timing kit and marks variables written by suspendables
|
2023-09-25 04:12:23 +02:00
|
|
|
TimingKit prepareTiming(AstNetlist* const netlistp) VL_MT_DISABLED;
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
|
|
|
|
|
// Transforms fork sub-statements into separate functions
|
2023-09-25 04:12:23 +02:00
|
|
|
void transformForks(AstNetlist* const netlistp) VL_MT_DISABLED;
|
Timing support (#3363)
Adds timing support to Verilator. It makes it possible to use delays,
event controls within processes (not just at the start), wait
statements, and forks.
Building a design with those constructs requires a compiler that
supports C++20 coroutines (GCC 10, Clang 5).
The basic idea is to have processes and tasks with delays/event controls
implemented as C++20 coroutines. This allows us to suspend and resume
them at any time.
There are five main runtime classes responsible for managing suspended
coroutines:
* `VlCoroutineHandle`, a wrapper over C++20's `std::coroutine_handle`
with move semantics and automatic cleanup.
* `VlDelayScheduler`, for coroutines suspended by delays. It resumes
them at a proper simulation time.
* `VlTriggerScheduler`, for coroutines suspended by event controls. It
resumes them if its corresponding trigger was set.
* `VlForkSync`, used for syncing `fork..join` and `fork..join_any`
blocks.
* `VlCoroutine`, the return type of all verilated coroutines. It allows
for suspending a stack of coroutines (normally, C++ coroutines are
stackless).
There is a new visitor in `V3Timing.cpp` which:
* scales delays according to the timescale,
* simplifies intra-assignment timing controls and net delays into
regular timing controls and assignments,
* simplifies wait statements into loops with event controls,
* marks processes and tasks with timing controls in them as
suspendable,
* creates delay, trigger scheduler, and fork sync variables,
* transforms timing controls and fork joins into C++ awaits
There are new functions in `V3SchedTiming.cpp` (used by `V3Sched.cpp`)
that integrate static scheduling with timing. This involves providing
external domains for variables, so that the necessary combinational
logic gets triggered after coroutine resumption, as well as statements
that need to be injected into the design eval function to perform this
resumption at the correct time.
There is also a function that transforms forked processes into separate
functions.
See the comments in `verilated_timing.h`, `verilated_timing.cpp`,
`V3Timing.cpp`, and `V3SchedTiming.cpp`, as well as the internals
documentation for more details.
Signed-off-by: Krzysztof Bieganski <kbieganski@antmicro.com>
2022-08-22 14:26:32 +02:00
|
|
|
|
2022-05-15 17:03:32 +02:00
|
|
|
// Top level entry point to scheduling
|
2023-09-25 04:12:23 +02:00
|
|
|
void schedule(AstNetlist*) VL_MT_DISABLED;
|
2022-05-15 17:03:32 +02:00
|
|
|
|
|
|
|
|
// Sub-steps
|
2023-09-25 04:12:23 +02:00
|
|
|
LogicByScope breakCycles(AstNetlist* netlistp,
|
|
|
|
|
const LogicByScope& combinationalLogic) VL_MT_DISABLED;
|
2022-05-15 17:03:32 +02:00
|
|
|
LogicRegions partition(LogicByScope& clockedLogic, LogicByScope& combinationalLogic,
|
2023-09-25 04:12:23 +02:00
|
|
|
LogicByScope& hybridLogic) VL_MT_DISABLED;
|
|
|
|
|
LogicReplicas replicateLogic(LogicRegions&) VL_MT_DISABLED;
|
2022-05-15 17:03:32 +02:00
|
|
|
|
2025-10-27 16:16:28 +01:00
|
|
|
// Utility functions used by various steps in scheduling
|
|
|
|
|
namespace util {
|
|
|
|
|
// Create a new top level entry point
|
|
|
|
|
AstCFunc* makeTopFunction(AstNetlist* netlistp, const string& name, bool slow);
|
|
|
|
|
// Create a new sub function (not an entry point)
|
|
|
|
|
AstCFunc* makeSubFunction(AstNetlist* netlistp, const string& name, bool slow);
|
|
|
|
|
// Create statement that sets the given 'vscp' to 'val'
|
|
|
|
|
AstNodeStmt* setVar(AstVarScope* vscp, uint32_t val);
|
|
|
|
|
// Create statement that increments the given 'vscp' by one
|
|
|
|
|
AstNodeStmt* incrementVar(AstVarScope* vscp);
|
|
|
|
|
// Create statement that calls the given 'void' returning function
|
|
|
|
|
AstNodeStmt* callVoidFunc(AstCFunc* funcp);
|
|
|
|
|
// Create statement that checks counterp' to see if the eval loop iteration limit is reached
|
|
|
|
|
AstNodeStmt* checkIterationLimit(AstNetlist* netlistp, const string& name, AstVarScope* counterp,
|
2025-10-31 19:29:11 +01:00
|
|
|
AstNodeStmt* dumpCallp);
|
2025-10-27 16:16:28 +01:00
|
|
|
// Split large function according to --output-split-cfuncs
|
|
|
|
|
void splitCheck(AstCFunc* ofuncp);
|
|
|
|
|
// Build an AstIf conditional on the given SenTree being triggered
|
|
|
|
|
AstIf* createIfFromSenTree(AstSenTree* senTreep);
|
|
|
|
|
} // namespace util
|
|
|
|
|
|
2022-05-15 17:03:32 +02:00
|
|
|
} // namespace V3Sched
|
|
|
|
|
|
|
|
|
|
#endif // Guard
|