mirror of
https://github.com/verilator/verilator.git
synced 2025-01-04 05:37:48 +00:00
310 lines
13 KiB
C++
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
|