verilator/src/V3GraphStream.h

247 lines
9.5 KiB
C
Raw Normal View History

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Dependency graph iterator. Iterates over nodes
// in any DAG, following dependency order.
//
// Code available from: http://www.veripool.org/verilator
//
//*************************************************************************
//
// Copyright 2003-2018 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.
//
// Verilator is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
//*************************************************************************
#ifndef _V3GRAPHSTREAM_H_
#define _V3GRAPHSTREAM_H_
#include "config_build.h"
#include "verilatedos.h"
#include <set>
#include VL_INCLUDE_UNORDERED_MAP
#include "V3Graph.h"
//######################################################################
// GraphStream
//
// Template 'T_Compare' is a tie-breaker for ordering nodes that the DAG
// itself does not order. It must provide an operator() that does a logical
// less-than on two V3GraphVertex*'s, with the same signature as
// std::less<const V3GraphVertex*>::operator(). This does not default to
// std::less<const V3GraphVertex*> because that is nondeterministic, and so
// not generally safe. If you want a raw pointer compare, see
// GraphStreamUnordered below.
template <class T_Compare> class GraphStream {
private:
// TYPES
class VxHolder {
public:
// MEMBERS
const V3GraphVertex* m_vxp; // [mtask] Vertex
uint32_t m_pos; // Sort position
uint32_t m_numBlockingEdges; // Number of blocking edges
// CONSTRUCTORS
VxHolder(const V3GraphVertex* vxp, uint32_t pos, uint32_t numBlockingEdges)
: m_vxp(vxp)
, m_pos(pos)
, m_numBlockingEdges(numBlockingEdges) {}
// METHODS
const V3GraphVertex* vertexp() const { return m_vxp; }
// Decrement blocking edges count, return true if the vertex is
// newly unblocked
bool unblock() {
if (m_numBlockingEdges <= 0) vertexp()->v3fatalSrc("Underflow of blocking edges");
m_numBlockingEdges--;
return (m_numBlockingEdges == 0);
}
};
class VxHolderCmp {
public:
// MEMBERS
T_Compare m_lessThan; // Sorting functor
// CONSTRUCTORS
explicit VxHolderCmp(const T_Compare& lessThan)
: m_lessThan(lessThan) {}
// METHODS
bool operator() (const VxHolder& a, const VxHolder& b) const {
if (m_lessThan.operator()(a.vertexp(), b.vertexp())) return true;
if (m_lessThan.operator()(b.vertexp(), a.vertexp())) return false;
return a.m_pos < b.m_pos;
}
private:
VL_UNCOPYABLE(VxHolderCmp);
};
typedef std::set<VxHolder, VxHolderCmp&> ReadyVertices;
typedef vl_unordered_map<const V3GraphVertex*, VxHolder> WaitingVertices;
// MEMBERS
VxHolderCmp m_vxHolderCmp; // Vertext comparison functor
ReadyVertices m_readyVertices; // List of ready verticies
WaitingVertices m_waitingVertices; // List of wiating verticies
typename ReadyVertices::iterator m_last; // Previously returned element
GraphWay m_way; // FORWARD or REVERSE order of traversal
public:
// CONSTRUCTORS
GraphStream(const V3Graph* graphp,
GraphWay way = GraphWay::FORWARD,
const T_Compare& lessThan = T_Compare())
// NOTE: Perhaps REVERSE way should also reverse the sense of the
// lessThan function? For now the only usage of REVERSE is not
// sensitive to its lessThan at all, so it doesn't matter.
: m_vxHolderCmp(lessThan)
, m_readyVertices(m_vxHolderCmp)
, m_last(m_readyVertices.end())
, m_way(way) {
uint32_t pos = 0;
for (const V3GraphVertex* vxp = graphp->verticesBeginp();
vxp; vxp=vxp->verticesNextp()) {
// Every vertex initially is waiting, or ready.
if (way == GraphWay::FORWARD) {
if (vxp->inEmpty()) {
VxHolder newVx(vxp, pos++, 0);
m_readyVertices.insert(newVx);
} else {
uint32_t depCount = 0;
for (V3GraphEdge* depp = vxp->inBeginp();
depp; depp = depp->inNextp()) {
depCount++;
}
VxHolder newVx(vxp, pos++, depCount);
m_waitingVertices.insert(make_pair(vxp, newVx));
}
} else { // REVERSE
if (vxp->outEmpty()) {
VxHolder newVx(vxp, pos++, 0);
m_readyVertices.insert(newVx);
} else {
uint32_t depCount = 0;
for (V3GraphEdge* depp = vxp->outBeginp();
depp; depp = depp->outNextp()) {
depCount++;
}
VxHolder newVx(vxp, pos++, depCount);
m_waitingVertices.insert(make_pair(vxp, newVx));
}
}
}
}
~GraphStream() {}
// METHODS
// Each call to nextp() returns a unique vertex in the graph, in
// dependency order.
//
// Dependencies alone don't fully specify the order. Usually a graph
// has many "ready" vertices, any of which might return next.
//
// To decide among the "ready" vertices, GraphStream keeps an ordered
// list of ready vertices, sorted first by lessThan and second by
// original graph order.
//
// You might expect that nextp() would return the first item from this
// sorted list -- but that's not what it does! What nextp() actually
// does is to return the next item in the list, following the position
// where the previously-returned item would have been. This maximizes
// locality: given an appropriate lessThan, nextp() will stay on a
// given domain (or domscope, or mtask, or whatever) for as long as
// possible before an unmet dependency forces us to switch to another
// one.
//
// Within a group of vertices that lessThan considers equivalent,
// nextp() returns them in the original graph order (presumably also
// good locality.) V3Order.cpp relies on this to order the logic
// vertices within a given mtask without jumping over domains too much.
const V3GraphVertex* nextp() {
const V3GraphVertex* resultp = NULL;
typename ReadyVertices::iterator curIt;
if (m_last == m_readyVertices.end()) {
// First call to nextp()
curIt = m_readyVertices.begin();
} else {
// Subsequent call to nextp()
curIt = m_last;
++curIt;
// Remove previously-returned element
m_readyVertices.erase(m_last);
// Wrap curIt. Expect to wrap, and make another pass, to find
// newly-ready elements that could have appeared ahead of the
// m_last iterator
if (curIt == m_readyVertices.end()) {
curIt = m_readyVertices.begin();
}
}
if (curIt != m_readyVertices.end()) {
resultp = curIt->vertexp();
unblockDeps(resultp);
} else {
// No ready vertices; waiting should be empty too, otherwise we
// were fed a graph with cycles (which is not supported.)
UASSERT(m_waitingVertices.empty(), "DGS fed non-DAG");
}
m_last = curIt;
return resultp;
}
private:
void unblockDeps(const V3GraphVertex* vertexp) {
if (m_way == GraphWay::FORWARD) {
for (V3GraphEdge* edgep = vertexp->outBeginp();
edgep; edgep=edgep->outNextp()) {
V3GraphVertex* toVertexp = edgep->top();
typename WaitingVertices::iterator it =
m_waitingVertices.find(toVertexp);
if (it == m_waitingVertices.end()) {
toVertexp->v3fatalSrc("Found edge into vertex not in waiting list.");
}
if (it->second.unblock()) {
m_readyVertices.insert(it->second);
m_waitingVertices.erase(it);
}
}
} else {
for (V3GraphEdge* edgep = vertexp->inBeginp();
edgep; edgep=edgep->inNextp()) {
V3GraphVertex* fromVertexp = edgep->fromp();
typename WaitingVertices::iterator it =
m_waitingVertices.find(fromVertexp);
if (it == m_waitingVertices.end()) {
fromVertexp->v3fatalSrc("Found edge into vertex not in waiting list.");
}
if (it->second.unblock()) {
m_readyVertices.insert(it->second);
m_waitingVertices.erase(it);
}
}
}
}
VL_UNCOPYABLE(GraphStream);
};
//######################################################################
// GraphStreamUnordered is GraphStream using a plain pointer compare to
// break ties in the graph order. This WILL return nodes in
// nondeterministic order.
typedef GraphStream<std::less<const V3GraphVertex*> > GraphStreamUnordered;
#endif // Guard