verilator/src/V3SchedReplicate.cpp
2024-07-09 18:31:58 -04:00

310 lines
13 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Scheduling - replicate combinational logic
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// 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.
//
//*************************************************************************
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
#include "V3Graph.h"
#include "V3Sched.h"
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);
}
// 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 "";
}
}
// 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);
}
// 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(); }
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;
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;
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
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