verilator/src/V3SchedReplicate.cpp

310 lines
13 KiB
C++
Raw Normal View History

// -*- 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
// 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
#include "V3Graph.h"
2022-08-05 11:15:59 +00:00
#include "V3Sched.h"
2022-09-22 16:28:42 +00:00
VL_DEFINE_DEBUG_FUNCTIONS;
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
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
};
//##############################################################################
// Data structures (graph types)
class SchedReplicateVertex VL_NOT_FINAL : public V3GraphVertex {
VL_RTTI_IMPL(SchedReplicateVertex, V3GraphVertex)
RegionFlags m_drivingRegions{RegionFlags::NONE}; // The regions driving this vertex
public:
explicit SchedReplicateVertex(V3Graph* graphp)
: 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
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
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-16 19:02:49 +00:00
// LCOV_EXCL_STOP
};
class SchedReplicateLogicVertex final : public SchedReplicateVertex {
VL_RTTI_IMPL(SchedReplicateLogicVertex, SchedReplicateVertex)
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:
SchedReplicateLogicVertex(V3Graph* graphp, AstScope* scopep, AstSenTree* senTreep,
AstNode* logicp, RegionFlags assignedRegion)
: SchedReplicateVertex{graphp}
, 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
string name() const override VL_MT_STABLE { return m_logicp->fileline()->ascii(); };
string dotShape() const override { return "rectangle"; }
};
class SchedReplicateVarVertex final : public SchedReplicateVertex {
VL_RTTI_IMPL(SchedReplicateVarVertex, SchedReplicateVertex)
AstVarScope* const m_vscp; // The AstVarScope this vertex represents
public:
SchedReplicateVarVertex(V3Graph* graphp, AstVarScope* vscp)
: SchedReplicateVertex{graphp}
, m_vscp{vscp} {
// Top level inputs are
if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi()
|| varp()->sensIfacep()) {
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);
}
AstVarScope* vscp() const { return m_vscp; }
AstVar* varp() const { return m_vscp->varp(); }
AstScope* scopep() const { return m_vscp->scopep(); }
// For graph dumping
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"; }
};
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) {
if (!vscp->user1p()) vscp->user1p(new SchedReplicateVarVertex{graphp.get(), vscp});
return vscp->user1u().to<SchedReplicateVarVertex*>();
};
const auto addEdge = [&](SchedReplicateVertex* fromp, SchedReplicateVertex* top) {
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(); };
senTreep->foreach([](const AstVarRef* refp) { //
refp->varScopep()->user4(true);
});
}
for (AstNode* nodep = activep->stmtsp(); nodep; nodep = nodep->nextp()) {
SchedReplicateLogicVertex* const lvtxp
= new SchedReplicateLogicVertex{graphp.get(), scopep, senTreep, nodep, region};
const VNUser2InUse user2InUse;
const VNUser3InUse user3InUse;
nodep->foreach([&](AstVarRef* refp) {
AstVarScope* const vscp = refp->varScopep();
SchedReplicateVarVertex* const vvtxp = getVarVertex(vscp);
// 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);
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);
return graphp;
}
void propagateDrivingRegions(SchedReplicateVertex* vtxp) {
// 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>();
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>()) {
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);
if (targetRegions & OBSERVED) replicateTo(result.m_obs);
if (targetRegions & REACTIVE) replicateTo(result.m_react);
}
}
return result;
}
} // namespace
LogicReplicas replicateLogic(LogicRegions& logicRegionsRegions) {
// Build the dataflow (dependency) graph
const std::unique_ptr<Graph> graphp = buildGraph(logicRegionsRegions);
// Dump for debug
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("sched-replicate");
// Propagate driving region flags
2024-03-25 23:06:25 +00:00
for (V3GraphVertex& vtx : graphp->vertices()) {
propagateDrivingRegions(vtx.as<SchedReplicateVertex>());
}
// Dump for debug
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("sched-replicate-propagated");
// Replicate the necessary logic
return replicate(graphp.get());
}
} // namespace V3Sched