2022-05-15 15:03:32 +00:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
//*************************************************************************
|
|
|
|
// DESCRIPTION: Verilator: Scheduling
|
|
|
|
//
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
//
|
|
|
|
//*************************************************************************
|
|
|
|
//
|
2023-01-01 15:18:39 +00:00
|
|
|
// Copyright 2003-2023 by Wilson Snyder. This program is free software; you
|
2022-05-15 15:03:32 +00: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 11:15:59 +00:00
|
|
|
|
2022-05-15 15:03:32 +00: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 12:26:32 +00:00
|
|
|
#include <unordered_map>
|
2022-05-15 15:03:32 +00:00
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
|
|
|
|
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");
|
|
|
|
if (empty() || back().first != scopep || back().second->sensesp() != senTreep) {
|
|
|
|
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;
|
|
|
|
UASSERT_OBJ(!activep->stmtsp(), activep, "Leftover logic");
|
|
|
|
if (activep->backp()) activep->unlinkFrBack();
|
|
|
|
activep->deleteTree();
|
|
|
|
}
|
|
|
|
clear();
|
|
|
|
};
|
|
|
|
|
2022-07-30 16:49:30 +00:00
|
|
|
void foreachLogic(const std::function<void(AstNode*)>& f) const {
|
2022-05-15 15:03:32 +00: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-02 23:46:38 +00:00
|
|
|
LogicByScope m_clocked; // Clocked (or sequential) logic (logic with explicit sensitivities)
|
2022-05-15 15:03:32 +00:00
|
|
|
LogicByScope m_hybrid; // Hybrid logic (combinational logic with some explicit sensitivities)
|
2022-10-13 19:04:43 +00:00
|
|
|
LogicByScope m_postponed; // Postponed logic ($strobe)
|
2022-12-23 12:34:49 +00:00
|
|
|
LogicByScope m_observed; // Observed logic (contains AstAlwaysObserved)
|
|
|
|
LogicByScope m_reactive; // Reactive logic (contains AstAlwaysReactive)
|
2022-05-15 15:03:32 +00: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 {
|
|
|
|
LogicByScope m_pre; // AstAssignPre logic in 'act' region
|
|
|
|
LogicByScope m_act; // 'act' region logic
|
|
|
|
LogicByScope m_nba; // 'nba' region logic
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
LogicReplicas() = default;
|
|
|
|
VL_UNCOPYABLE(LogicReplicas);
|
|
|
|
LogicReplicas(LogicReplicas&&) = default;
|
|
|
|
LogicReplicas& operator=(LogicReplicas&&) = default;
|
|
|
|
};
|
|
|
|
|
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 12:26:32 +00: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 14:05:39 +00: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 12:26:32 +00:00
|
|
|
|
|
|
|
// Remaps external domains using the specified trigger map
|
|
|
|
std::map<const AstVarScope*, std::vector<AstSenTree*>>
|
|
|
|
remapDomains(const std::unordered_map<const AstSenTree*, AstSenTree*>& trigMap) const;
|
|
|
|
// Creates a timing resume call (if needed, else returns null)
|
|
|
|
AstCCall* createResume(AstNetlist* const netlistp);
|
|
|
|
// Creates a timing commit call (if needed, else returns null)
|
|
|
|
AstCCall* createCommit(AstNetlist* const netlistp);
|
|
|
|
|
|
|
|
TimingKit() = default;
|
2022-10-22 14:05:39 +00: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 12:26:32 +00:00
|
|
|
std::map<const AstVarScope*, std::set<AstSenTree*>>&& externalDomains)
|
|
|
|
: m_externalDomains{externalDomains}
|
2022-10-22 14:05:39 +00: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 12:26:32 +00:00
|
|
|
VL_UNCOPYABLE(TimingKit);
|
|
|
|
TimingKit(TimingKit&&) = default;
|
|
|
|
TimingKit& operator=(TimingKit&&) = default;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Creates the timing kit and marks variables written by suspendables
|
|
|
|
TimingKit prepareTiming(AstNetlist* const netlistp);
|
|
|
|
|
|
|
|
// Transforms fork sub-statements into separate functions
|
|
|
|
void transformForks(AstNetlist* const netlistp);
|
|
|
|
|
2022-05-15 15:03:32 +00:00
|
|
|
// Top level entry point to scheduling
|
|
|
|
void schedule(AstNetlist*);
|
|
|
|
|
|
|
|
// Sub-steps
|
|
|
|
LogicByScope breakCycles(AstNetlist* netlistp, LogicByScope& combinationalLogic);
|
|
|
|
LogicRegions partition(LogicByScope& clockedLogic, LogicByScope& combinationalLogic,
|
|
|
|
LogicByScope& hybridLogic);
|
|
|
|
LogicReplicas replicateLogic(LogicRegions&);
|
|
|
|
|
|
|
|
} // namespace V3Sched
|
|
|
|
|
|
|
|
#endif // Guard
|