2024-04-15 06:55:54 -05:00
|
|
|
/*
|
|
|
|
* yosys -- Yosys Open SYnthesis Suite
|
|
|
|
*
|
|
|
|
* Copyright (C) 2024 Jannis Harder <jix@yosyshq.com> <me@jix.one>
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef TOPO_SCC_H
|
|
|
|
#define TOPO_SCC_H
|
|
|
|
|
|
|
|
#include "kernel/yosys.h"
|
|
|
|
|
|
|
|
YOSYS_NAMESPACE_BEGIN
|
|
|
|
|
|
|
|
class SigCellGraph {
|
|
|
|
public:
|
|
|
|
typedef int node_type;
|
|
|
|
|
|
|
|
struct successor_enumerator {
|
|
|
|
std::vector<std::pair<int, int>>::const_iterator current, end;
|
|
|
|
bool finished() const { return current == end; }
|
|
|
|
node_type next() {
|
|
|
|
log_assert(!finished());
|
|
|
|
node_type result = current->second;
|
|
|
|
++current;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct node_enumerator {
|
|
|
|
int current, end;
|
|
|
|
bool finished() const { return current == end; }
|
|
|
|
node_type next() {
|
|
|
|
log_assert(!finished());
|
|
|
|
node_type result = current;
|
|
|
|
++current;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private:
|
|
|
|
idict<RTLIL::Cell *> cell_ids;
|
|
|
|
idict<RTLIL::SigBit> sig_ids;
|
|
|
|
std::vector<std::pair<int, int>> edges;
|
|
|
|
std::vector<std::pair<int, int>> edge_ranges;
|
|
|
|
std::vector<int> indices_;
|
|
|
|
int offset;
|
|
|
|
bool computed = false;
|
|
|
|
|
|
|
|
void compute() {
|
|
|
|
offset = GetSize(sig_ids);
|
|
|
|
edge_ranges.clear();
|
|
|
|
indices_.clear();
|
|
|
|
indices_.resize(GetSize(sig_ids) + GetSize(cell_ids), -1);
|
|
|
|
|
|
|
|
std::sort(edges.begin(), edges.end());
|
|
|
|
auto last = std::unique(edges.begin(), edges.end());
|
|
|
|
edges.erase(last, edges.end());
|
|
|
|
auto edge = edges.begin();
|
|
|
|
auto edge_end = edges.end();
|
|
|
|
int range_begin = 0;
|
|
|
|
for (int node = -offset, node_end = GetSize(cell_ids); node != node_end; ++node) {
|
|
|
|
while (edge != edge_end && edge->first <= node)
|
|
|
|
++edge;
|
|
|
|
int range_end = edge - edges.begin();
|
|
|
|
edge_ranges.emplace_back(std::make_pair(range_begin, range_end));
|
|
|
|
range_begin = range_end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
|
|
node_type node(RTLIL::Cell *cell) { return cell_ids(cell); }
|
|
|
|
node_type node(SigBit const &bit) { return ~sig_ids(bit); }
|
|
|
|
|
|
|
|
bool is_cell(node_type node) { return node >= 0; }
|
|
|
|
bool is_sig(node_type node) { return node < 0; }
|
|
|
|
|
|
|
|
Cell *cell(node_type node) { return node >= 0 ? cell_ids[node] : nullptr; }
|
|
|
|
SigBit sig(node_type node) { return node < 0 ? sig_ids[~node] : SigBit(); }
|
|
|
|
|
|
|
|
template<typename Src, typename Dst>
|
|
|
|
void add_edge(Src &&src, Dst &&dst) {
|
|
|
|
computed = false;
|
|
|
|
node_type src_node = node(std::forward<Src>(src));
|
|
|
|
node_type dst_node = node(std::forward<Dst>(dst));
|
|
|
|
edges.emplace_back(std::make_pair(src_node, dst_node));
|
|
|
|
}
|
|
|
|
|
|
|
|
node_enumerator enumerate_nodes() {
|
|
|
|
if (!computed) compute();
|
|
|
|
return {-GetSize(sig_ids), GetSize(cell_ids)};
|
|
|
|
}
|
|
|
|
|
|
|
|
successor_enumerator enumerate_successors(node_type const &node) const {
|
|
|
|
auto range = edge_ranges[node + offset];
|
|
|
|
return {edges.begin() + range.first, edges.begin() + range.second};
|
|
|
|
}
|
|
|
|
|
|
|
|
int &dfs_index(node_type const &node) {
|
|
|
|
return indices_[node + offset];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class IntGraph {
|
|
|
|
public:
|
|
|
|
typedef int node_type;
|
|
|
|
|
|
|
|
struct successor_enumerator {
|
|
|
|
std::vector<std::pair<int, int>>::const_iterator current, end;
|
|
|
|
bool finished() const { return current == end; }
|
|
|
|
node_type next() {
|
|
|
|
log_assert(!finished());
|
|
|
|
node_type result = current->second;
|
|
|
|
++current;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct node_enumerator {
|
|
|
|
int current, end;
|
|
|
|
bool finished() const { return current == end; }
|
|
|
|
node_type next() {
|
|
|
|
log_assert(!finished());
|
|
|
|
node_type result = current;
|
|
|
|
++current;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::vector<std::pair<int, int>> edges;
|
|
|
|
std::vector<std::pair<int, int>> edge_ranges;
|
|
|
|
std::vector<int> indices_;
|
|
|
|
bool computed = false;
|
|
|
|
|
|
|
|
void compute() {
|
|
|
|
edge_ranges.clear();
|
|
|
|
|
|
|
|
int node_end = 0;
|
|
|
|
for (auto const &edge : edges)
|
|
|
|
node_end = std::max(node_end, std::max(edge.first, edge.second) + 1);
|
|
|
|
indices_.clear();
|
|
|
|
indices_.resize(node_end, -1);
|
|
|
|
|
|
|
|
std::sort(edges.begin(), edges.end());
|
|
|
|
auto last = std::unique(edges.begin(), edges.end());
|
|
|
|
edges.erase(last, edges.end());
|
|
|
|
auto edge = edges.begin();
|
|
|
|
auto edge_end = edges.end();
|
|
|
|
int range_begin = 0;
|
|
|
|
for (int node = 0; node != node_end; ++node) {
|
|
|
|
while (edge != edge_end && edge->first <= node)
|
|
|
|
++edge;
|
|
|
|
int range_end = edge - edges.begin();
|
|
|
|
edge_ranges.emplace_back(std::make_pair(range_begin, range_end));
|
|
|
|
range_begin = range_end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
|
|
|
void add_edge(int src, int dst) {
|
|
|
|
log_assert(src >= 0);
|
|
|
|
log_assert(dst >= 0);
|
|
|
|
computed = false;
|
|
|
|
edges.emplace_back(std::make_pair(src, dst));
|
|
|
|
}
|
|
|
|
|
|
|
|
node_enumerator enumerate_nodes() {
|
|
|
|
if (!computed) compute();
|
|
|
|
return {0, GetSize(indices_)};
|
|
|
|
}
|
|
|
|
|
|
|
|
successor_enumerator enumerate_successors(int node) const {
|
|
|
|
auto range = edge_ranges[node];
|
|
|
|
return {edges.begin() + range.first, edges.begin() + range.second};
|
|
|
|
}
|
|
|
|
|
|
|
|
int &dfs_index(node_type const &node) {
|
|
|
|
return indices_[node];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
template<typename G, typename ComponentCallback>
|
2024-07-25 04:35:32 -05:00
|
|
|
class TopoSortedSccs
|
2024-04-15 06:55:54 -05:00
|
|
|
{
|
|
|
|
typedef typename G::node_enumerator node_enumerator;
|
|
|
|
typedef typename G::successor_enumerator successor_enumerator;
|
|
|
|
typedef typename G::node_type node_type;
|
|
|
|
|
|
|
|
struct dfs_entry {
|
|
|
|
node_type node;
|
|
|
|
successor_enumerator successors;
|
|
|
|
int lowlink;
|
|
|
|
|
|
|
|
dfs_entry(node_type node, successor_enumerator successors, int lowlink) :
|
|
|
|
node(node), successors(successors), lowlink(lowlink)
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
2024-07-25 04:35:32 -05:00
|
|
|
G &graph;
|
|
|
|
ComponentCallback component;
|
|
|
|
|
2024-04-15 06:55:54 -05:00
|
|
|
std::vector<dfs_entry> dfs_stack;
|
|
|
|
std::vector<node_type> component_stack;
|
|
|
|
int next_index = 0;
|
|
|
|
|
2024-07-25 04:35:32 -05:00
|
|
|
public:
|
|
|
|
TopoSortedSccs(G &graph, ComponentCallback component)
|
|
|
|
: graph(graph), component(component) {}
|
2024-04-15 06:55:54 -05:00
|
|
|
|
2024-07-25 04:35:32 -05:00
|
|
|
// process all sources (nodes without a successor)
|
|
|
|
TopoSortedSccs &process_sources() {
|
|
|
|
node_enumerator nodes = graph.enumerate_nodes();
|
2024-04-11 06:47:14 -05:00
|
|
|
while (!nodes.finished()) {
|
|
|
|
node_type node = nodes.next();
|
|
|
|
successor_enumerator successors = graph.enumerate_successors(node);
|
|
|
|
if (successors.finished())
|
|
|
|
{
|
|
|
|
graph.dfs_index(node) = next_index;
|
|
|
|
next_index++;
|
|
|
|
component_stack.push_back(node);
|
|
|
|
component(component_stack.data(), component_stack.data() + 1);
|
|
|
|
component_stack.clear();
|
|
|
|
graph.dfs_index(node) = INT_MAX;
|
|
|
|
}
|
|
|
|
}
|
2024-07-25 04:35:32 -05:00
|
|
|
return *this;
|
2024-04-11 06:47:14 -05:00
|
|
|
}
|
|
|
|
|
2024-07-25 04:35:32 -05:00
|
|
|
// process all remaining nodes in the graph
|
|
|
|
TopoSortedSccs &process_all() {
|
|
|
|
node_enumerator nodes = graph.enumerate_nodes();
|
|
|
|
// iterate over all nodes to ensure we process the whole graph
|
|
|
|
while (!nodes.finished())
|
|
|
|
process(nodes.next());
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// process all nodes that are reachable from a given start node
|
|
|
|
TopoSortedSccs &process(node_type node) {
|
2024-04-15 06:55:54 -05:00
|
|
|
// only start a new search if the node wasn't visited yet
|
|
|
|
if (graph.dfs_index(node) >= 0)
|
2024-07-25 04:35:32 -05:00
|
|
|
return *this;
|
2024-04-15 06:55:54 -05:00
|
|
|
while (true) {
|
|
|
|
// at this point we're visiting the node for the first time during
|
|
|
|
// the DFS search
|
|
|
|
|
|
|
|
// we record the timestamp of when we first visited the node as the
|
|
|
|
// dfs_index
|
|
|
|
int lowlink = next_index;
|
|
|
|
next_index++;
|
|
|
|
graph.dfs_index(node) = lowlink;
|
|
|
|
|
|
|
|
// and we add the node to the component stack where it will remain
|
|
|
|
// until all nodes of the component containing this node are popped
|
|
|
|
component_stack.push_back(node);
|
|
|
|
|
|
|
|
// then we start iterating over the successors of this node
|
|
|
|
successor_enumerator successors = graph.enumerate_successors(node);
|
|
|
|
while (true) {
|
|
|
|
if (successors.finished()) {
|
|
|
|
// when we processed all successors, i.e. when we visited
|
|
|
|
// the complete DFS subtree rooted at the current node, we
|
|
|
|
// first check whether the current node is a SCC root
|
|
|
|
//
|
|
|
|
// (why this check identifies SCC roots is out of scope for
|
|
|
|
// this comment, see other material on Tarjan's SCC
|
|
|
|
// algorithm)
|
|
|
|
if (lowlink == graph.dfs_index(node)) {
|
|
|
|
// the SCC containing the current node is at the top of
|
|
|
|
// the component stack, with the current node at the bottom
|
|
|
|
int current = GetSize(component_stack);
|
|
|
|
do {
|
|
|
|
--current;
|
|
|
|
} while (component_stack[current] != node);
|
|
|
|
|
|
|
|
// we invoke the callback with a pointer range of the
|
|
|
|
// nodes in the SCC
|
|
|
|
|
|
|
|
node_type *stack_ptr = component_stack.data();
|
|
|
|
node_type *component_begin = stack_ptr + current;
|
|
|
|
node_type *component_end = stack_ptr + component_stack.size();
|
|
|
|
|
|
|
|
// note that we allow the callback to permute the nodes
|
|
|
|
// in this range as well as to modify dfs_index of the
|
|
|
|
// nodes in the SCC.
|
|
|
|
component(component_begin, component_end);
|
|
|
|
|
|
|
|
// by setting the dfs_index of all already emitted
|
|
|
|
// nodes to INT_MAX, we don't need a separate check for
|
|
|
|
// whether successor nodes are still on the component
|
|
|
|
// stack before updating the lowlink value
|
|
|
|
for (; component_begin != component_end; ++component_begin)
|
|
|
|
graph.dfs_index(*component_begin) = INT_MAX;
|
|
|
|
component_stack.resize(current);
|
|
|
|
}
|
|
|
|
|
|
|
|
// after checking for a completed SCC the DFS either
|
|
|
|
// continues the search at the parent node or returns to
|
|
|
|
// the outer loop if we already are at the root node.
|
|
|
|
if (dfs_stack.empty())
|
2024-07-25 04:35:32 -05:00
|
|
|
return *this;
|
2024-04-15 06:55:54 -05:00
|
|
|
auto &dfs_top = dfs_stack.back();
|
|
|
|
|
|
|
|
node = dfs_top.node;
|
|
|
|
successors = std::move(dfs_top.successors);
|
|
|
|
|
|
|
|
// the parent's lowlink is updated when returning
|
|
|
|
lowlink = min(lowlink, dfs_top.lowlink);
|
|
|
|
dfs_stack.pop_back();
|
|
|
|
// continue checking the remaining successors of the parent node.
|
|
|
|
} else {
|
|
|
|
node_type succ = successors.next();
|
|
|
|
if (graph.dfs_index(succ) < 0) {
|
|
|
|
// if the successor wasn't visted yet, the DFS recurses
|
|
|
|
// into the successor
|
|
|
|
|
|
|
|
// we save the state for this node and make the
|
|
|
|
// successor the current node.
|
|
|
|
dfs_stack.emplace_back(node, std::move(successors), lowlink);
|
|
|
|
node = succ;
|
|
|
|
|
|
|
|
// this break gets us to the section corresponding to
|
|
|
|
// the function entry in the recursive version
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
// the textbook version guards this update with a check
|
|
|
|
// whether the successor is still on the component
|
|
|
|
// stack. If the successor node was already visisted
|
|
|
|
// but is not on the component stack, it must be part
|
|
|
|
// of an already emitted SCC. We can avoid this check
|
|
|
|
// by setting the DFS index of all nodes in a SCC to
|
|
|
|
// INT_MAX when the SCC is emitted.
|
|
|
|
lowlink = min(lowlink, graph.dfs_index(succ));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-07-25 04:35:32 -05:00
|
|
|
};
|
2024-04-15 06:55:54 -05:00
|
|
|
|
|
|
|
YOSYS_NAMESPACE_END
|
|
|
|
|
|
|
|
#endif
|