2022-05-15 17:03:32 +02:00
|
|
|
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
// DESCRIPTION: Verilator: Scheduling - break combinational cycles
|
|
|
|
|
//
|
|
|
|
|
// Code available from: https://verilator.org
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
//
|
2025-01-01 14:30:25 +01:00
|
|
|
// Copyright 2003-2025 by Wilson Snyder. This program is free software; you
|
2022-05-15 17:03:32 +02: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 loops are broken by the introduction of instances of the
|
|
|
|
|
// 'hybrid' logic. Hybrid logic is like combinational logic, but also has
|
|
|
|
|
// explicit sensitivities. Any explicit sensitivity of hybrid logic suppresses
|
|
|
|
|
// the implicit sensitivity of the logic on the same variable. This enables us
|
|
|
|
|
// to cut combinational logic loops and perform ordering as if the logic is
|
|
|
|
|
// acyclic. See the internals documentation for more details.
|
|
|
|
|
//
|
|
|
|
|
// To achieve this we build a dependency graph of all combinational logic in
|
|
|
|
|
// the design, and then breaks all combinational cycles by converting all
|
|
|
|
|
// combinational logic that consumes a variable driven via a 'back-edge' into
|
|
|
|
|
// hybrid logic. Here back-edge' just means a graph edge that points from a
|
|
|
|
|
// higher rank vertex to a lower rank vertex in some consistent ranking of
|
|
|
|
|
// the directed graph. Variables driven via a back-edge in the dependency
|
|
|
|
|
// graph are marked, and all combinational logic that depends on such
|
|
|
|
|
// variables is converted into hybrid logic, with the back-edge driven
|
|
|
|
|
// variables listed as explicit 'changed' sensitivities.
|
|
|
|
|
//
|
|
|
|
|
//*************************************************************************
|
|
|
|
|
|
2023-10-18 04:50:27 +02:00
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
2023-10-18 12:37:46 +02:00
|
|
|
|
|
|
|
|
#include "V3Graph.h"
|
2022-05-15 17:03:32 +02:00
|
|
|
#include "V3Sched.h"
|
2022-08-05 13:15:59 +02:00
|
|
|
#include "V3SenTree.h"
|
2022-05-15 17:03:32 +02:00
|
|
|
#include "V3SplitVar.h"
|
|
|
|
|
#include "V3Stats.h"
|
|
|
|
|
|
|
|
|
|
#include <tuple>
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
#include <utility>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
2022-09-22 18:28:42 +02:00
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
|
|
2022-05-15 17:03:32 +02:00
|
|
|
namespace V3Sched {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2022-09-22 18:28:42 +02:00
|
|
|
// ##############################################################################
|
|
|
|
|
// Data structures (graph types)
|
2022-05-15 17:03:32 +02:00
|
|
|
|
2023-09-01 00:00:53 +02:00
|
|
|
class SchedAcyclicLogicVertex final : public V3GraphVertex {
|
|
|
|
|
VL_RTTI_IMPL(SchedAcyclicLogicVertex, V3GraphVertex)
|
2022-05-15 17:03:32 +02:00
|
|
|
AstNode* const m_logicp; // The logic node this vertex represents
|
|
|
|
|
AstScope* const m_scopep; // The enclosing AstScope of the logic node
|
|
|
|
|
|
|
|
|
|
public:
|
2023-09-01 00:00:53 +02:00
|
|
|
SchedAcyclicLogicVertex(V3Graph* graphp, AstNode* logicp, AstScope* scopep)
|
2022-05-15 17:03:32 +02:00
|
|
|
: V3GraphVertex{graphp}
|
|
|
|
|
, m_logicp{logicp}
|
|
|
|
|
, m_scopep{scopep} {}
|
2023-03-17 01:22:08 +01:00
|
|
|
V3GraphVertex* clone(V3Graph* graphp) const override {
|
2023-09-01 00:00:53 +02:00
|
|
|
return new SchedAcyclicLogicVertex{graphp, logicp(), scopep()};
|
2023-03-17 00:42:13 +01:00
|
|
|
}
|
|
|
|
|
|
2022-05-15 17:03:32 +02:00
|
|
|
AstNode* logicp() const { return m_logicp; }
|
|
|
|
|
AstScope* scopep() const { return m_scopep; }
|
|
|
|
|
|
2022-05-16 21:02:49 +02:00
|
|
|
// LCOV_EXCL_START // Debug code
|
2023-03-18 01:24:15 +01:00
|
|
|
string name() const override VL_MT_STABLE { return m_logicp->fileline()->ascii(); };
|
2023-03-17 00:42:13 +01:00
|
|
|
string dotShape() const override { return "rectangle2"; }
|
2022-05-16 21:02:49 +02:00
|
|
|
// LCOV_EXCL_STOP
|
2022-05-15 17:03:32 +02:00
|
|
|
};
|
|
|
|
|
|
2023-09-01 00:00:53 +02:00
|
|
|
class SchedAcyclicVarVertex final : public V3GraphVertex {
|
|
|
|
|
VL_RTTI_IMPL(SchedAcyclicVarVertex, V3GraphVertex)
|
2022-05-15 17:03:32 +02:00
|
|
|
AstVarScope* const m_vscp; // The AstVarScope this vertex represents
|
|
|
|
|
|
|
|
|
|
public:
|
2023-09-01 00:00:53 +02:00
|
|
|
SchedAcyclicVarVertex(V3Graph* graphp, AstVarScope* vscp)
|
2022-05-15 17:03:32 +02:00
|
|
|
: V3GraphVertex{graphp}
|
|
|
|
|
, m_vscp{vscp} {}
|
|
|
|
|
AstVarScope* vscp() const { return m_vscp; }
|
|
|
|
|
AstVar* varp() const { return m_vscp->varp(); }
|
2023-09-01 00:00:53 +02:00
|
|
|
V3GraphVertex* clone(V3Graph* graphp) const override {
|
|
|
|
|
return new SchedAcyclicVarVertex{graphp, vscp()};
|
|
|
|
|
}
|
2022-05-15 17:03:32 +02:00
|
|
|
|
2022-05-16 21:02:49 +02:00
|
|
|
// LCOV_EXCL_START // Debug code
|
2023-03-18 01:24:15 +01:00
|
|
|
string name() const override VL_MT_STABLE { return m_vscp->name(); }
|
2022-05-15 17:03:32 +02:00
|
|
|
string dotShape() const override { return "ellipse"; }
|
|
|
|
|
string dotColor() const override { return "blue"; }
|
2022-05-16 21:02:49 +02:00
|
|
|
// LCOV_EXCL_STOP
|
2022-05-15 17:03:32 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class Graph final : public V3Graph {
|
2025-05-10 22:20:12 +02:00
|
|
|
string loopsVertexCb(V3GraphVertex* vtxp) override {
|
2023-09-01 00:00:53 +02:00
|
|
|
if (SchedAcyclicLogicVertex* const lvtxp = vtxp->cast<SchedAcyclicLogicVertex>()) {
|
2022-05-15 17:03:32 +02:00
|
|
|
AstNode* const logicp = lvtxp->logicp();
|
Internals: Make AstAssignW a procedural statement (#6280) (#6556)
Initial idea was to remodel AssignW as Assign under Alway. Trying that
uncovered some issues, the most difficult of them was that a delay
attached to a continuous assignment behaves differently from a delay
attached to a blocking assignment statement, so we need to keep the
knowledge of which flavour an assignment was until V3Timing.
So instead of removing AstAssignW, we always wrap it in an AstAlways,
with a special `keyword()` type. This makes it into a proper procedural
statement, which is almost equivalent to AstAssign, except for the case
when they contain a delay. We still gain the benefits of #6280 and can
simplify some code. Every AstNodeStmt should now be under an
AstNodeProcedure - which we should rename to AstProcess, or an
AstNodeFTask). As a result, V3Table can now handle AssignW for free.
Also uncovered and fixed a bug in handling intra-assignment delays if
a function is present on the RHS of an AssignW.
There is more work to be done towards #6280, and potentially simplifying
AssignW handing, but this is the minimal change required to tick it off
the TODO list for #6280.
2025-10-14 10:05:19 +02:00
|
|
|
std::string logicName = logicp->prettyTypeName();
|
|
|
|
|
if (const AstAlways* const alwaysp = VN_CAST(logicp, Always)) {
|
|
|
|
|
if (alwaysp->keyword() == VAlwaysKwd::CONT_ASSIGN) {
|
|
|
|
|
logicName = "ASSIGNW"; // Keep using historiacl name until we have better
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return logicp->fileline()->warnOther() + " Example path: " + logicName + "\n";
|
2022-05-16 21:02:49 +02:00
|
|
|
} else {
|
2023-09-01 00:00:53 +02:00
|
|
|
SchedAcyclicVarVertex* const vvtxp = vtxp->as<SchedAcyclicVarVertex>();
|
2022-05-15 17:03:32 +02:00
|
|
|
AstVarScope* const vscp = vvtxp->vscp();
|
2025-05-10 22:20:12 +02:00
|
|
|
return vscp->fileline()->warnOther() + " Example path: " + vscp->prettyName()
|
|
|
|
|
+ "\n";
|
2022-05-15 17:03:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//##############################################################################
|
|
|
|
|
// Algorithm implementation
|
|
|
|
|
|
|
|
|
|
std::unique_ptr<Graph> buildGraph(const LogicByScope& lbs) {
|
|
|
|
|
std::unique_ptr<Graph> graphp{new Graph};
|
|
|
|
|
|
|
|
|
|
// AstVarScope::user1() -> VarVertx
|
|
|
|
|
const VNUser1InUse user1InUse;
|
|
|
|
|
const auto getVarVertex = [&](AstVarScope* vscp) {
|
2023-09-01 00:00:53 +02:00
|
|
|
if (!vscp->user1p()) vscp->user1p(new SchedAcyclicVarVertex{graphp.get(), vscp});
|
|
|
|
|
return vscp->user1u().to<SchedAcyclicVarVertex*>();
|
2022-05-15 17:03:32 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const auto addEdge = [&](V3GraphVertex* fromp, V3GraphVertex* top, int weight, bool cuttable) {
|
|
|
|
|
new V3GraphEdge{graphp.get(), fromp, top, weight, cuttable};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const auto& pair : lbs) {
|
|
|
|
|
AstScope* const scopep = pair.first;
|
|
|
|
|
AstActive* const activep = pair.second;
|
|
|
|
|
UASSERT_OBJ(activep->hasCombo(), activep, "Not combinational logic");
|
|
|
|
|
for (AstNode* nodep = activep->stmtsp(); nodep; nodep = nodep->nextp()) {
|
|
|
|
|
// Can safely ignore Postponed as we generate them all
|
|
|
|
|
if (VN_IS(nodep, AlwaysPostponed)) continue;
|
|
|
|
|
|
2023-09-01 00:00:53 +02:00
|
|
|
SchedAcyclicLogicVertex* const lvtxp
|
|
|
|
|
= new SchedAcyclicLogicVertex{graphp.get(), nodep, scopep};
|
2022-05-15 17:03:32 +02:00
|
|
|
const VNUser2InUse user2InUse;
|
|
|
|
|
const VNUser3InUse user3InUse;
|
|
|
|
|
|
2022-10-20 14:48:44 +02:00
|
|
|
nodep->foreach([&](AstVarRef* refp) {
|
2022-05-15 17:03:32 +02:00
|
|
|
AstVarScope* const vscp = refp->varScopep();
|
2023-09-01 00:00:53 +02:00
|
|
|
SchedAcyclicVarVertex* const vvtxp = getVarVertex(vscp);
|
2022-05-15 17:03:32 +02:00
|
|
|
// We want to cut the narrowest signals
|
|
|
|
|
const int weight = vscp->width() / 8 + 1;
|
|
|
|
|
// If written, add logic -> var edge
|
2025-03-03 02:02:55 +01:00
|
|
|
if (refp->access().isWriteOrRW() && !refp->varp()->ignoreSchedWrite()
|
|
|
|
|
&& !vscp->user2SetOnce())
|
2022-05-15 17:03:32 +02:00
|
|
|
addEdge(lvtxp, vvtxp, weight, true);
|
|
|
|
|
// 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() && !vscp->user2())
|
|
|
|
|
addEdge(vvtxp, lvtxp, weight, true);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return graphp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void removeNonCyclic(Graph* graphp) {
|
|
|
|
|
// Work queue
|
|
|
|
|
std::vector<V3GraphVertex*> queue;
|
|
|
|
|
|
|
|
|
|
const auto enqueue = [&](V3GraphVertex* vtxp) {
|
|
|
|
|
if (vtxp->user()) return; // Already in queue
|
|
|
|
|
vtxp->user(1);
|
|
|
|
|
queue.push_back(vtxp);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Start with vertices with no inputs or outputs
|
2024-03-26 00:06:25 +01:00
|
|
|
for (V3GraphVertex& vtx : graphp->vertices()) {
|
|
|
|
|
if (vtx.inEmpty() || vtx.outEmpty()) enqueue(&vtx);
|
2022-05-15 17:03:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Iterate while we still have candidates
|
|
|
|
|
while (!queue.empty()) {
|
|
|
|
|
// Pop next candidate
|
|
|
|
|
V3GraphVertex* const vtxp = queue.back();
|
|
|
|
|
queue.pop_back();
|
|
|
|
|
vtxp->user(0); // No longer in queue
|
|
|
|
|
|
|
|
|
|
if (vtxp->inEmpty()) {
|
|
|
|
|
// Enqueue children for consideration, remove out edges, and delete this vertex
|
2024-03-26 00:06:25 +01:00
|
|
|
for (V3GraphEdge* const edgep : vtxp->outEdges().unlinkable()) {
|
2022-05-15 17:03:32 +02:00
|
|
|
enqueue(edgep->top());
|
|
|
|
|
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
|
|
|
|
|
}
|
|
|
|
|
VL_DO_DANGLING(vtxp->unlinkDelete(graphp), vtxp);
|
|
|
|
|
} else if (vtxp->outEmpty()) {
|
|
|
|
|
// Enqueue parents for consideration, remove in edges, and delete this vertex
|
2024-03-26 00:06:25 +01:00
|
|
|
for (V3GraphEdge* const edgep : vtxp->inEdges().unlinkable()) {
|
2022-05-15 17:03:32 +02:00
|
|
|
enqueue(edgep->fromp());
|
|
|
|
|
VL_DO_DANGLING(edgep->unlinkDelete(), edgep);
|
|
|
|
|
}
|
|
|
|
|
VL_DO_DANGLING(vtxp->unlinkDelete(graphp), vtxp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Has this VarVertex been cut? (any edges in or out has been cut)
|
2023-09-01 00:00:53 +02:00
|
|
|
bool isCut(const SchedAcyclicVarVertex* vtxp) {
|
2024-03-26 00:06:25 +01:00
|
|
|
for (const V3GraphEdge& edge : vtxp->inEdges()) {
|
|
|
|
|
if (edge.weight() == 0) return true;
|
2022-05-15 17:03:32 +02:00
|
|
|
}
|
2024-03-26 00:06:25 +01:00
|
|
|
for (const V3GraphEdge& edge : vtxp->outEdges()) {
|
|
|
|
|
if (edge.weight() == 0) return true;
|
2022-05-15 17:03:32 +02:00
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 00:00:53 +02:00
|
|
|
std::vector<SchedAcyclicVarVertex*> findCutVertices(Graph* graphp) {
|
|
|
|
|
std::vector<SchedAcyclicVarVertex*> result;
|
2022-05-15 17:03:32 +02:00
|
|
|
const VNUser1InUse user1InUse; // bool: already added to result
|
2024-03-26 00:06:25 +01:00
|
|
|
for (V3GraphVertex& vtx : graphp->vertices()) {
|
|
|
|
|
if (SchedAcyclicVarVertex* const vvtxp = vtx.cast<SchedAcyclicVarVertex>()) {
|
2022-05-15 17:03:32 +02:00
|
|
|
if (!vvtxp->vscp()->user1SetOnce() && isCut(vvtxp)) result.push_back(vvtxp);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 00:00:53 +02:00
|
|
|
void resetEdgeWeights(const std::vector<SchedAcyclicVarVertex*>& cutVertices) {
|
|
|
|
|
for (SchedAcyclicVarVertex* const vvtxp : cutVertices) {
|
2024-03-26 00:06:25 +01:00
|
|
|
for (V3GraphEdge& e : vvtxp->inEdges()) e.weight(1);
|
|
|
|
|
for (V3GraphEdge& e : vvtxp->outEdges()) e.weight(1);
|
2022-05-15 17:03:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A VarVertex together with its fanout
|
2023-09-01 00:00:53 +02:00
|
|
|
using Candidate = std::pair<SchedAcyclicVarVertex*, unsigned>;
|
2022-05-15 17:03:32 +02:00
|
|
|
|
|
|
|
|
// Gather all splitting candidates that are in the same SCC as the given vertex
|
|
|
|
|
void gatherSCCCandidates(V3GraphVertex* vtxp, std::vector<Candidate>& candidates) {
|
|
|
|
|
if (vtxp->user()) return; // Already done
|
|
|
|
|
vtxp->user(true);
|
|
|
|
|
|
2023-09-01 00:00:53 +02:00
|
|
|
if (SchedAcyclicVarVertex* const vvtxp = vtxp->cast<SchedAcyclicVarVertex>()) {
|
2022-05-15 17:03:32 +02:00
|
|
|
AstVar* const varp = vvtxp->varp();
|
|
|
|
|
const string name = varp->prettyName();
|
|
|
|
|
if (!varp->user3SetOnce() // Only consider each AstVar once
|
|
|
|
|
&& varp->width() != 1 // Ignore 1-bit signals (they cannot be split further)
|
|
|
|
|
&& name.find("__Vdly") == string::npos // Ignore internal signals
|
|
|
|
|
&& name.find("__Vcell") == string::npos) {
|
|
|
|
|
// Also compute the fanout of this vertex
|
2024-03-26 00:06:25 +01:00
|
|
|
const unsigned fanout = vtxp->outEdges().size();
|
2022-05-15 17:03:32 +02:00
|
|
|
candidates.emplace_back(vvtxp, fanout);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Iterate through all the vertices within the same strongly connected component (same color)
|
2024-03-26 00:06:25 +01:00
|
|
|
for (V3GraphEdge& edge : vtxp->outEdges()) {
|
|
|
|
|
V3GraphVertex* const top = edge.top();
|
2022-05-15 17:03:32 +02:00
|
|
|
if (top->color() == vtxp->color()) gatherSCCCandidates(top, candidates);
|
|
|
|
|
}
|
2024-03-26 00:06:25 +01:00
|
|
|
for (V3GraphEdge& edge : vtxp->inEdges()) {
|
|
|
|
|
V3GraphVertex* const fromp = edge.fromp();
|
2022-05-15 17:03:32 +02:00
|
|
|
if (fromp->color() == vtxp->color()) gatherSCCCandidates(fromp, candidates);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find all variables in a loop (SCC) that are candidates for splitting to break loops.
|
2025-05-10 22:20:12 +02:00
|
|
|
std::string reportLoopVars(FileLine* warnFl, Graph* graphp, SchedAcyclicVarVertex* vvtxp) {
|
|
|
|
|
std::ostringstream ss;
|
2022-05-15 17:03:32 +02:00
|
|
|
// Vector of variables in UNOPTFLAT loop that are candidates for splitting.
|
|
|
|
|
std::vector<Candidate> candidates;
|
|
|
|
|
{
|
|
|
|
|
// AstNode::user3 is used to mark if we have done a particular variable.
|
|
|
|
|
// V3GraphVertex::user is used to mark if we have seen this vertex before.
|
|
|
|
|
const VNUser3InUse user3InUse;
|
|
|
|
|
graphp->userClearVertices();
|
|
|
|
|
gatherSCCCandidates(vvtxp, candidates);
|
|
|
|
|
graphp->userClearVertices();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Possible we only have candidates the user cannot do anything about, so don't bother them.
|
2025-05-10 22:20:12 +02:00
|
|
|
if (candidates.empty()) return "";
|
2022-05-15 17:03:32 +02:00
|
|
|
|
|
|
|
|
// There may be a very large number of candidates, so only report up to 10 of the "most
|
|
|
|
|
// important" signals.
|
|
|
|
|
unsigned splittable = 0;
|
2025-05-10 22:20:12 +02:00
|
|
|
const auto reportFirst10
|
|
|
|
|
= [&](std::function<bool(const Candidate&, const Candidate&)> less) -> string {
|
2022-05-15 17:03:32 +02:00
|
|
|
std::stable_sort(candidates.begin(), candidates.end(), less);
|
2025-05-10 22:20:12 +02:00
|
|
|
std::ostringstream ss2;
|
2022-05-15 17:03:32 +02:00
|
|
|
for (size_t i = 0; i < 10; i++) {
|
|
|
|
|
if (i == candidates.size()) break;
|
|
|
|
|
const Candidate& candidate = candidates[i];
|
|
|
|
|
AstVar* const varp = candidate.first->varp();
|
2025-05-10 22:20:12 +02:00
|
|
|
|
|
|
|
|
ss2 << V3Error::warnMore() << " " << varp->fileline() << ' ' << varp->prettyName()
|
|
|
|
|
<< ", width " << std::dec << varp->width() << ", circular fanout "
|
|
|
|
|
<< candidate.second;
|
2022-05-15 17:03:32 +02:00
|
|
|
if (V3SplitVar::canSplitVar(varp)) {
|
2025-05-10 22:20:12 +02:00
|
|
|
ss2 << ", can split_var";
|
2022-05-15 17:03:32 +02:00
|
|
|
++splittable;
|
|
|
|
|
}
|
2025-05-10 22:20:12 +02:00
|
|
|
ss2 << '\n';
|
2022-05-15 17:03:32 +02:00
|
|
|
}
|
2025-05-10 22:20:12 +02:00
|
|
|
return ss2.str();
|
2022-05-15 17:03:32 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Widest variables
|
2025-05-10 22:20:12 +02:00
|
|
|
ss << V3Error::warnMore() << "... Widest variables candidate to splitting:\n"
|
|
|
|
|
<< reportFirst10([](const Candidate& a, const Candidate& b) {
|
|
|
|
|
return a.first->varp()->width() > b.first->varp()->width();
|
|
|
|
|
});
|
2022-05-15 17:03:32 +02:00
|
|
|
|
|
|
|
|
// Highest fanout
|
2025-05-10 22:20:12 +02:00
|
|
|
ss << V3Error::warnMore() << "... Candidates with the highest fanout:\n"
|
|
|
|
|
<< reportFirst10([](const Candidate& a, const Candidate& b) { //
|
|
|
|
|
return a.second > b.second;
|
|
|
|
|
});
|
2022-05-15 17:03:32 +02:00
|
|
|
|
|
|
|
|
if (splittable) {
|
2025-05-10 22:20:12 +02:00
|
|
|
ss << V3Error::warnMore()
|
|
|
|
|
<< "... Suggest add /*verilator split_var*/ or /*verilator "
|
|
|
|
|
"isolate_assignments*/ to appropriate variables above.\n";
|
2022-05-15 17:03:32 +02:00
|
|
|
}
|
|
|
|
|
V3Stats::addStat("Scheduling, split_var, candidates", splittable);
|
2025-05-10 22:20:12 +02:00
|
|
|
return ss.str();
|
2022-05-15 17:03:32 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-01 00:00:53 +02:00
|
|
|
void reportCycles(Graph* graphp, const std::vector<SchedAcyclicVarVertex*>& cutVertices) {
|
|
|
|
|
for (SchedAcyclicVarVertex* vvtxp : cutVertices) {
|
2022-05-15 17:03:32 +02:00
|
|
|
AstVarScope* const vscp = vvtxp->vscp();
|
|
|
|
|
FileLine* const flp = vscp->fileline();
|
|
|
|
|
|
|
|
|
|
// First v3warn not inside warnIsOff so we can see the suppressions with --debug
|
2025-05-10 22:20:12 +02:00
|
|
|
if (flp->warnIsOff(V3ErrorCode::UNOPTFLAT)) {
|
|
|
|
|
// First v3warn not inside warnIsOff so we can see the suppressions with --debug
|
|
|
|
|
vscp->v3warn(UNOPTFLAT, "Signal unoptimizable: Circular combinational logic: "
|
|
|
|
|
<< vscp->prettyNameQ());
|
|
|
|
|
} else {
|
|
|
|
|
vscp->v3warn(UNOPTFLAT,
|
|
|
|
|
"Signal unoptimizable: Circular combinational logic: "
|
|
|
|
|
<< vscp->prettyNameQ() << '\n'
|
|
|
|
|
<< vscp->warnContextPrimary()
|
|
|
|
|
<< V3Error::warnAdditionalInfo()
|
|
|
|
|
// Calls Graph::loopsVertexCb
|
|
|
|
|
<< graphp->reportLoops(&V3GraphEdge::followAlwaysTrue, vvtxp)
|
|
|
|
|
// Report candidate variables for splitting
|
|
|
|
|
<< (v3Global.opt.reportUnoptflat()
|
|
|
|
|
? reportLoopVars(vscp->fileline(), graphp, vvtxp)
|
|
|
|
|
: ""));
|
2022-05-15 17:03:32 +02:00
|
|
|
// Complain just once
|
|
|
|
|
flp->modifyWarnOff(V3ErrorCode::UNOPTFLAT, true);
|
2025-05-10 22:20:12 +02:00
|
|
|
// Create a subgraph for the UNOPTFLAT loop
|
2022-05-15 17:03:32 +02:00
|
|
|
if (v3Global.opt.reportUnoptflat()) {
|
|
|
|
|
V3Graph loopGraph;
|
|
|
|
|
graphp->subtreeLoops(&V3GraphEdge::followAlwaysTrue, vvtxp, &loopGraph);
|
|
|
|
|
loopGraph.dumpDotFilePrefixedAlways("unoptflat");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 00:00:53 +02:00
|
|
|
LogicByScope fixCuts(AstNetlist* netlistp,
|
|
|
|
|
const std::vector<SchedAcyclicVarVertex*>& cutVertices) {
|
2022-05-15 17:03:32 +02:00
|
|
|
// For all logic that reads a cut vertex, build a map from logic -> list of cut AstVarScope
|
|
|
|
|
// they read. Also build a vector of the involved logic for deterministic results.
|
2023-09-01 00:00:53 +02:00
|
|
|
std::unordered_map<SchedAcyclicLogicVertex*, std::vector<AstVarScope*>> lvtx2Cuts;
|
|
|
|
|
std::vector<SchedAcyclicLogicVertex*> lvtxps;
|
2022-05-15 17:03:32 +02:00
|
|
|
{
|
|
|
|
|
const VNUser1InUse user1InUse; // bool: already added to 'lvtxps'
|
2023-09-01 00:00:53 +02:00
|
|
|
for (SchedAcyclicVarVertex* const vvtxp : cutVertices) {
|
2024-03-26 00:06:25 +01:00
|
|
|
for (V3GraphEdge& edge : vvtxp->outEdges()) {
|
2023-09-01 00:00:53 +02:00
|
|
|
SchedAcyclicLogicVertex* const lvtxp
|
2024-03-26 00:06:25 +01:00
|
|
|
= static_cast<SchedAcyclicLogicVertex*>(edge.top());
|
2022-05-15 17:03:32 +02:00
|
|
|
if (!lvtxp->logicp()->user1SetOnce()) lvtxps.push_back(lvtxp);
|
|
|
|
|
lvtx2Cuts[lvtxp].push_back(vvtxp->vscp());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make the logic reading cut vertices use a hybrid sensitivity (combinational, but with some
|
|
|
|
|
// explicit additional triggers on the cut variables)
|
|
|
|
|
LogicByScope result;
|
|
|
|
|
SenTreeFinder finder{netlistp};
|
2023-09-01 00:00:53 +02:00
|
|
|
for (SchedAcyclicLogicVertex* const lvtxp : lvtxps) {
|
2022-05-15 17:03:32 +02:00
|
|
|
AstNode* const logicp = lvtxp->logicp();
|
|
|
|
|
logicp->unlinkFrBack();
|
|
|
|
|
FileLine* const flp = logicp->fileline();
|
|
|
|
|
// Build the hybrid sensitivity list
|
|
|
|
|
AstSenItem* senItemsp = nullptr;
|
|
|
|
|
for (AstVarScope* const vscp : lvtx2Cuts[lvtxp]) {
|
|
|
|
|
AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ};
|
|
|
|
|
AstSenItem* const nextp = new AstSenItem{flp, VEdgeType::ET_HYBRID, refp};
|
2022-09-05 18:03:43 +02:00
|
|
|
senItemsp = AstNode::addNext(senItemsp, nextp);
|
2022-05-15 17:03:32 +02:00
|
|
|
}
|
|
|
|
|
AstSenTree* const senTree = new AstSenTree{flp, senItemsp};
|
|
|
|
|
// Add logic to result with new sensitivity
|
|
|
|
|
result.add(lvtxp->scopep(), finder.getSenTree(senTree), logicp);
|
|
|
|
|
// SenTreeFinder::getSenTree clones, so clean up
|
|
|
|
|
VL_DO_DANGLING(senTree->deleteTree(), senTree);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2023-09-01 00:29:58 +02:00
|
|
|
LogicByScope breakCycles(AstNetlist* netlistp, const LogicByScope& combinationalLogic) {
|
2022-05-15 17:03:32 +02:00
|
|
|
// Build the dataflow (dependency) graph
|
|
|
|
|
const std::unique_ptr<Graph> graphp = buildGraph(combinationalLogic);
|
|
|
|
|
|
|
|
|
|
// Remove nodes that don't form part of a cycle
|
|
|
|
|
removeNonCyclic(graphp.get());
|
|
|
|
|
|
|
|
|
|
// Nothing to do if no cycles, yay!
|
|
|
|
|
if (graphp->empty()) return LogicByScope{};
|
|
|
|
|
|
|
|
|
|
// Dump for debug
|
2023-05-04 00:04:10 +02:00
|
|
|
if (dumpGraphLevel() >= 6) graphp->dumpDotFilePrefixed("sched-comb-cycles");
|
2022-05-15 17:03:32 +02:00
|
|
|
|
|
|
|
|
// Make graph acyclic by cutting some edges. Note: This also colors strongly connected
|
|
|
|
|
// components which reportCycles uses to print each SCCs separately.
|
|
|
|
|
// TODO: A more optimal algorithm that cuts by removing/marking VarVertex vertices is possible
|
|
|
|
|
// Search for "Feedback vertex set" (current algorithm is "Feedback arc set")
|
|
|
|
|
graphp->acyclic(&V3GraphEdge::followAlwaysTrue);
|
|
|
|
|
|
|
|
|
|
// Find all cut vertices
|
2023-09-01 00:00:53 +02:00
|
|
|
const std::vector<SchedAcyclicVarVertex*> cutVertices = findCutVertices(graphp.get());
|
2022-05-15 17:03:32 +02:00
|
|
|
|
|
|
|
|
// Reset edge weights for reporting
|
|
|
|
|
resetEdgeWeights(cutVertices);
|
|
|
|
|
|
|
|
|
|
// Report warnings/diagnostics
|
|
|
|
|
reportCycles(graphp.get(), cutVertices);
|
|
|
|
|
|
|
|
|
|
// Fix cuts by converting dependent logic to use hybrid sensitivities
|
|
|
|
|
return fixCuts(netlistp, cutVertices);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace V3Sched
|