2022-05-15 15:03:32 +00:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
//*************************************************************************
|
|
|
|
// DESCRIPTION: Verilator: Scheduling - replicate combinational logic
|
|
|
|
//
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
//
|
|
|
|
//*************************************************************************
|
|
|
|
//
|
2024-01-01 08:19:59 +00:00
|
|
|
// Copyright 2003-2024 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
|
|
|
|
//
|
|
|
|
//*************************************************************************
|
|
|
|
//
|
|
|
|
// Combinational (including hybrid) logic driven from both the 'act' and 'nba'
|
|
|
|
// region needs to be re-evaluated even if only one of those regions updates
|
|
|
|
// an input variable. We achieve this by replicating such combinational logic
|
|
|
|
// in both the 'act' and 'nba' regions.
|
|
|
|
//
|
|
|
|
// Furthermore we also replicate all combinational logic driven from a top
|
|
|
|
// level input into a separate 'ico' (Input Combinational) region which is
|
|
|
|
// executed at the beginning of the time step. This allows us to change both
|
|
|
|
// data and clock signals during the same 'eval' call while maintaining the
|
|
|
|
// combinational invariant required by V3Order.
|
|
|
|
//
|
|
|
|
// The implementation is a simple graph algorithm, where we build a dependency
|
|
|
|
// graph of all logic in the design, and then propagate the driving region
|
|
|
|
// information through it. We then replicate any logic into its additional
|
|
|
|
// driving regions.
|
|
|
|
//
|
|
|
|
// For more details, please see the internals documentation.
|
|
|
|
//
|
|
|
|
//*************************************************************************
|
|
|
|
|
2023-10-18 02:50:27 +00:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
2023-10-18 10:37:46 +00:00
|
|
|
|
|
|
|
#include "V3Graph.h"
|
2022-08-05 11:15:59 +00:00
|
|
|
#include "V3Sched.h"
|
2022-05-15 15:03:32 +00:00
|
|
|
|
2022-09-22 16:28:42 +00:00
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
2022-05-15 15:03:32 +00:00
|
|
|
|
|
|
|
namespace V3Sched {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
// Driving region flags
|
|
|
|
enum RegionFlags : uint8_t {
|
|
|
|
NONE = 0x0, //
|
|
|
|
INPUT = 0x1, // Variable/logic is driven from top level input
|
|
|
|
ACTIVE = 0x2, // Variable/logic is driven from 'act' region logic
|
2024-07-09 22:31:58 +00:00
|
|
|
NBA = 0x4, // Variable/logic is driven from 'nba' region logic
|
|
|
|
OBSERVED = 0x8, // Variable/logic is driven from 'obs' region logic
|
|
|
|
REACTIVE = 0x10, // Variable/logic is driven from 're' region logic
|
2022-05-15 15:03:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
//##############################################################################
|
|
|
|
// Data structures (graph types)
|
|
|
|
|
2023-08-31 22:00:53 +00:00
|
|
|
class SchedReplicateVertex VL_NOT_FINAL : public V3GraphVertex {
|
|
|
|
VL_RTTI_IMPL(SchedReplicateVertex, V3GraphVertex)
|
|
|
|
RegionFlags m_drivingRegions{RegionFlags::NONE}; // The regions driving this vertex
|
2022-05-15 15:03:32 +00:00
|
|
|
|
|
|
|
public:
|
2023-08-31 22:00:53 +00:00
|
|
|
explicit SchedReplicateVertex(V3Graph* graphp)
|
2022-05-15 15:03:32 +00:00
|
|
|
: V3GraphVertex{graphp} {}
|
|
|
|
uint8_t drivingRegions() const { return m_drivingRegions; }
|
|
|
|
void addDrivingRegions(uint8_t regions) {
|
|
|
|
m_drivingRegions = static_cast<RegionFlags>(m_drivingRegions | regions);
|
|
|
|
}
|
|
|
|
|
2022-05-16 19:02:49 +00:00
|
|
|
// LCOV_EXCL_START // Debug code
|
2022-05-15 15:03:32 +00:00
|
|
|
string dotColor() const override {
|
|
|
|
switch (static_cast<unsigned>(m_drivingRegions)) {
|
|
|
|
case NONE: return "black";
|
|
|
|
case INPUT: return "red";
|
|
|
|
case ACTIVE: return "green";
|
|
|
|
case NBA: return "blue";
|
|
|
|
case INPUT | ACTIVE: return "yellow";
|
|
|
|
case INPUT | NBA: return "magenta";
|
|
|
|
case ACTIVE | NBA: return "cyan";
|
|
|
|
case INPUT | ACTIVE | NBA: return "gray80"; // don't want white on white background
|
2024-07-09 22:31:58 +00:00
|
|
|
|
|
|
|
case REACTIVE: return "gray60";
|
|
|
|
case REACTIVE | INPUT: return "lightcoral";
|
|
|
|
case REACTIVE | ACTIVE: return "lightgreen";
|
|
|
|
case REACTIVE | NBA: return "lightblue";
|
|
|
|
case REACTIVE | INPUT | ACTIVE: return "lightyellow";
|
|
|
|
case REACTIVE | INPUT | NBA: return "lightpink";
|
|
|
|
case REACTIVE | ACTIVE | NBA: return "lightcyan";
|
|
|
|
case REACTIVE | INPUT | ACTIVE | NBA: return "gray90";
|
|
|
|
|
|
|
|
case OBSERVED: return "gray20";
|
|
|
|
case OBSERVED | INPUT: return "darkred";
|
|
|
|
case OBSERVED | ACTIVE: return "darkgreen";
|
|
|
|
case OBSERVED | NBA: return "darkblue";
|
|
|
|
case OBSERVED | INPUT | ACTIVE: return "gold";
|
|
|
|
case OBSERVED | INPUT | NBA: return "purple";
|
|
|
|
case OBSERVED | ACTIVE | NBA: return "darkcyan";
|
|
|
|
case OBSERVED | INPUT | ACTIVE | NBA: return "gray30";
|
|
|
|
|
|
|
|
case REACTIVE | OBSERVED: return "gray40";
|
|
|
|
case REACTIVE | OBSERVED | INPUT: return "indianred";
|
|
|
|
case REACTIVE | OBSERVED | ACTIVE: return "olive";
|
|
|
|
case REACTIVE | OBSERVED | NBA: return "dodgerBlue";
|
|
|
|
case REACTIVE | OBSERVED | INPUT | ACTIVE: return "khaki";
|
|
|
|
case REACTIVE | OBSERVED | INPUT | NBA: return "plum";
|
|
|
|
case REACTIVE | OBSERVED | ACTIVE | NBA: return "lightSeaGreen";
|
|
|
|
case REACTIVE | OBSERVED | INPUT | ACTIVE | NBA: return "gray50";
|
|
|
|
default: v3fatal("There are only 5 region bits"); return "";
|
2022-05-15 15:03:32 +00:00
|
|
|
}
|
|
|
|
}
|
2022-05-16 19:02:49 +00:00
|
|
|
// LCOV_EXCL_STOP
|
2022-05-15 15:03:32 +00:00
|
|
|
};
|
|
|
|
|
2023-08-31 22:00:53 +00:00
|
|
|
class SchedReplicateLogicVertex final : public SchedReplicateVertex {
|
|
|
|
VL_RTTI_IMPL(SchedReplicateLogicVertex, SchedReplicateVertex)
|
2022-05-15 15:03:32 +00:00
|
|
|
AstScope* const m_scopep; // The enclosing AstScope of the logic node
|
|
|
|
AstSenTree* const m_senTreep; // The sensitivity of the logic node
|
|
|
|
AstNode* const m_logicp; // The logic node this vertex represents
|
|
|
|
RegionFlags const m_assignedRegion; // The region this logic is originally assigned to
|
|
|
|
|
|
|
|
public:
|
2023-08-31 22:00:53 +00:00
|
|
|
SchedReplicateLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* senTreep,
|
|
|
|
AstNode* logicp, RegionFlags assignedRegion)
|
|
|
|
: SchedReplicateVertex{graphp}
|
2022-05-15 15:03:32 +00:00
|
|
|
, m_scopep{scopep}
|
|
|
|
, m_senTreep{senTreep}
|
|
|
|
, m_logicp{logicp}
|
|
|
|
, m_assignedRegion{assignedRegion} {
|
|
|
|
addDrivingRegions(assignedRegion);
|
|
|
|
}
|
|
|
|
AstScope* scopep() const { return m_scopep; }
|
|
|
|
AstSenTree* senTreep() const { return m_senTreep; }
|
|
|
|
AstNode* logicp() const { return m_logicp; }
|
|
|
|
RegionFlags assignedRegion() const { return m_assignedRegion; }
|
|
|
|
|
|
|
|
// For graph dumping
|
2023-03-18 00:24:15 +00:00
|
|
|
string name() const override VL_MT_STABLE { return m_logicp->fileline()->ascii(); };
|
2022-05-15 15:03:32 +00:00
|
|
|
string dotShape() const override { return "rectangle"; }
|
|
|
|
};
|
|
|
|
|
2023-08-31 22:00:53 +00:00
|
|
|
class SchedReplicateVarVertex final : public SchedReplicateVertex {
|
|
|
|
VL_RTTI_IMPL(SchedReplicateVarVertex, SchedReplicateVertex)
|
2022-05-15 15:03:32 +00:00
|
|
|
AstVarScope* const m_vscp; // The AstVarScope this vertex represents
|
|
|
|
|
|
|
|
public:
|
2023-08-31 22:00:53 +00:00
|
|
|
SchedReplicateVarVertex(V3Graph* graphp, AstVarScope* vscp)
|
|
|
|
: SchedReplicateVertex{graphp}
|
2022-05-15 15:03:32 +00:00
|
|
|
, m_vscp{vscp} {
|
2022-07-14 11:35:44 +00:00
|
|
|
// Top level inputs are
|
2023-12-05 03:11:07 +00:00
|
|
|
if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi()
|
|
|
|
|| varp()->sensIfacep()) {
|
2022-07-15 15:16:43 +00:00
|
|
|
addDrivingRegions(INPUT);
|
|
|
|
}
|
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
|
|
|
// Currently we always execute suspendable processes at the beginning of
|
|
|
|
// the act region, which means combinational logic driven from a suspendable
|
|
|
|
// processes must be present in the 'act' region
|
|
|
|
if (varp()->isWrittenBySuspendable()) addDrivingRegions(ACTIVE);
|
2022-05-15 15:03:32 +00:00
|
|
|
}
|
|
|
|
AstVarScope* vscp() const { return m_vscp; }
|
|
|
|
AstVar* varp() const { return m_vscp->varp(); }
|
|
|
|
AstScope* scopep() const { return m_vscp->scopep(); }
|
|
|
|
|
|
|
|
// For graph dumping
|
2023-03-18 00:24:15 +00:00
|
|
|
string name() const override VL_MT_STABLE { return m_vscp->name(); }
|
2022-07-15 15:18:41 +00:00
|
|
|
string dotShape() const override { return varp()->isPrimaryInish() ? "invhouse" : "ellipse"; }
|
2022-05-15 15:03:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class Graph final : public V3Graph {};
|
|
|
|
|
|
|
|
//##############################################################################
|
|
|
|
// Algorithm implementation
|
|
|
|
|
|
|
|
std::unique_ptr<Graph> buildGraph(const LogicRegions& logicRegions) {
|
|
|
|
std::unique_ptr<Graph> graphp{new Graph};
|
|
|
|
|
|
|
|
// AstVarScope::user1() -> VarVertx
|
|
|
|
const VNUser1InUse user1InUse;
|
|
|
|
const auto getVarVertex = [&](AstVarScope* vscp) {
|
2023-08-31 22:00:53 +00:00
|
|
|
if (!vscp->user1p()) vscp->user1p(new SchedReplicateVarVertex{graphp.get(), vscp});
|
|
|
|
return vscp->user1u().to<SchedReplicateVarVertex*>();
|
2022-05-15 15:03:32 +00:00
|
|
|
};
|
|
|
|
|
2023-08-31 22:00:53 +00:00
|
|
|
const auto addEdge = [&](SchedReplicateVertex* fromp, SchedReplicateVertex* top) {
|
2022-05-15 15:03:32 +00:00
|
|
|
new V3GraphEdge{graphp.get(), fromp, top, 1};
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto addLogic = [&](RegionFlags region, AstScope* scopep, AstActive* activep) {
|
|
|
|
AstSenTree* const senTreep = activep->sensesp();
|
|
|
|
|
|
|
|
// Predicate for whether a read of the given variable triggers this block
|
|
|
|
std::function<bool(AstVarScope*)> readTriggersThisLogic;
|
|
|
|
|
|
|
|
const VNUser4InUse user4InUse; // bool: Explicit sensitivity of hybrid logic just below
|
|
|
|
|
|
|
|
if (senTreep->hasClocked()) {
|
|
|
|
// Clocked logic is never triggered by reads
|
|
|
|
readTriggersThisLogic = [](AstVarScope*) { return false; };
|
|
|
|
} else if (senTreep->hasCombo()) {
|
|
|
|
// Combinational logic is always triggered by reads
|
|
|
|
readTriggersThisLogic = [](AstVarScope*) { return true; };
|
|
|
|
} else {
|
|
|
|
UASSERT_OBJ(senTreep->hasHybrid(), activep, "unexpected");
|
|
|
|
// Hybrid logic is triggered by all reads, except for reads of the explicit
|
|
|
|
// sensitivities
|
|
|
|
readTriggersThisLogic = [](AstVarScope* vscp) { return !vscp->user4(); };
|
2022-10-20 12:48:44 +00:00
|
|
|
senTreep->foreach([](const AstVarRef* refp) { //
|
2022-05-15 15:03:32 +00:00
|
|
|
refp->varScopep()->user4(true);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
for (AstNode* nodep = activep->stmtsp(); nodep; nodep = nodep->nextp()) {
|
2023-08-31 22:00:53 +00:00
|
|
|
SchedReplicateLogicVertex* const lvtxp
|
|
|
|
= new SchedReplicateLogicVertex{graphp.get(), scopep, senTreep, nodep, region};
|
2022-05-15 15:03:32 +00:00
|
|
|
const VNUser2InUse user2InUse;
|
|
|
|
const VNUser3InUse user3InUse;
|
|
|
|
|
2022-10-20 12:48:44 +00:00
|
|
|
nodep->foreach([&](AstVarRef* refp) {
|
2022-05-15 15:03:32 +00:00
|
|
|
AstVarScope* const vscp = refp->varScopep();
|
2023-08-31 22:00:53 +00:00
|
|
|
SchedReplicateVarVertex* const vvtxp = getVarVertex(vscp);
|
2022-05-15 15:03:32 +00:00
|
|
|
|
|
|
|
// If read, add var -> logic edge
|
|
|
|
// Note: Use same heuristic as ordering does to ignore written variables
|
|
|
|
// TODO: Use live variable analysis.
|
|
|
|
if (refp->access().isReadOrRW() && !vscp->user3SetOnce()
|
|
|
|
&& readTriggersThisLogic(vscp) && !vscp->user2()) { //
|
|
|
|
addEdge(vvtxp, lvtxp);
|
|
|
|
}
|
|
|
|
// If written, add logic -> var edge
|
|
|
|
// Note: See V3Order for why AlwaysPostponed is safe to be ignored. We ignore it
|
|
|
|
// as otherwise we would end up with a false cycle.
|
|
|
|
if (refp->access().isWriteOrRW() && !vscp->user2SetOnce()
|
|
|
|
&& !VN_IS(nodep, AlwaysPostponed)) { //
|
|
|
|
addEdge(lvtxp, vvtxp);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
for (const auto& pair : logicRegions.m_pre) addLogic(ACTIVE, pair.first, pair.second);
|
|
|
|
for (const auto& pair : logicRegions.m_act) addLogic(ACTIVE, pair.first, pair.second);
|
|
|
|
for (const auto& pair : logicRegions.m_nba) addLogic(NBA, pair.first, pair.second);
|
2024-07-09 22:31:58 +00:00
|
|
|
for (const auto& pair : logicRegions.m_obs) addLogic(OBSERVED, pair.first, pair.second);
|
|
|
|
for (const auto& pair : logicRegions.m_react) addLogic(REACTIVE, pair.first, pair.second);
|
2022-05-15 15:03:32 +00:00
|
|
|
|
|
|
|
return graphp;
|
|
|
|
}
|
|
|
|
|
2023-08-31 22:00:53 +00:00
|
|
|
void propagateDrivingRegions(SchedReplicateVertex* vtxp) {
|
2022-05-15 15:03:32 +00:00
|
|
|
// Note: The graph is always acyclic, so the recursion will terminate
|
|
|
|
|
|
|
|
// Nothing to do if already visited
|
|
|
|
if (vtxp->user()) return;
|
|
|
|
|
|
|
|
// Compute union of driving regions of all inputs
|
|
|
|
uint8_t drivingRegions = 0;
|
2024-03-25 23:06:25 +00:00
|
|
|
for (V3GraphEdge& edge : vtxp->inEdges()) {
|
|
|
|
SchedReplicateVertex* const srcp = edge.fromp()->as<SchedReplicateVertex>();
|
2022-05-15 15:03:32 +00:00
|
|
|
propagateDrivingRegions(srcp);
|
|
|
|
drivingRegions |= srcp->drivingRegions();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add any new driving regions
|
|
|
|
vtxp->addDrivingRegions(drivingRegions);
|
|
|
|
|
|
|
|
// Mark as visited
|
|
|
|
vtxp->user(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
LogicReplicas replicate(Graph* graphp) {
|
|
|
|
LogicReplicas result;
|
2024-03-25 23:06:25 +00:00
|
|
|
for (V3GraphVertex& vtx : graphp->vertices()) {
|
|
|
|
if (SchedReplicateLogicVertex* const lvtxp = vtx.cast<SchedReplicateLogicVertex>()) {
|
2022-05-15 15:03:32 +00:00
|
|
|
const auto replicateTo = [&](LogicByScope& lbs) {
|
|
|
|
lbs.add(lvtxp->scopep(), lvtxp->senTreep(), lvtxp->logicp()->cloneTree(false));
|
|
|
|
};
|
|
|
|
const uint8_t targetRegions = lvtxp->drivingRegions() & ~lvtxp->assignedRegion();
|
|
|
|
UASSERT(!lvtxp->senTreep()->hasClocked() || targetRegions == 0,
|
|
|
|
"replicating clocked logic");
|
|
|
|
if (targetRegions & INPUT) replicateTo(result.m_ico);
|
|
|
|
if (targetRegions & ACTIVE) replicateTo(result.m_act);
|
|
|
|
if (targetRegions & NBA) replicateTo(result.m_nba);
|
2024-07-09 22:31:58 +00:00
|
|
|
if (targetRegions & OBSERVED) replicateTo(result.m_obs);
|
|
|
|
if (targetRegions & REACTIVE) replicateTo(result.m_react);
|
2022-05-15 15:03:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
LogicReplicas replicateLogic(LogicRegions& logicRegionsRegions) {
|
|
|
|
// Build the dataflow (dependency) graph
|
|
|
|
const std::unique_ptr<Graph> graphp = buildGraph(logicRegionsRegions);
|
|
|
|
// Dump for debug
|
2023-05-03 22:04:10 +00:00
|
|
|
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("sched-replicate");
|
2022-05-15 15:03:32 +00:00
|
|
|
// Propagate driving region flags
|
2024-03-25 23:06:25 +00:00
|
|
|
for (V3GraphVertex& vtx : graphp->vertices()) {
|
|
|
|
propagateDrivingRegions(vtx.as<SchedReplicateVertex>());
|
2022-05-15 15:03:32 +00:00
|
|
|
}
|
|
|
|
// Dump for debug
|
2023-05-03 22:04:10 +00:00
|
|
|
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("sched-replicate-propagated");
|
2022-05-15 15:03:32 +00:00
|
|
|
// Replicate the necessary logic
|
|
|
|
return replicate(graphp.get());
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace V3Sched
|