blob: ab46e91ab8eb61b2c7fbe9ae4554b8ec75965c7f [file] [log] [blame]
#include <algorithm>
#include <iostream>
#include <sstream>
#include <map>
#include "tatum/util/tatum_assert.hpp"
#include "tatum/base/loop_detect.hpp"
#include "tatum/error.hpp"
#include "tatum/TimingGraph.hpp"
namespace tatum {
//Builds a mapping from old to new ids by skipping values marked invalid
template<typename Id>
tatum::util::linear_map<Id,Id> compress_ids(const tatum::util::linear_map<Id,Id>& ids) {
tatum::util::linear_map<Id,Id> id_map(ids.size());
size_t i = 0;
for(auto id : ids) {
if(id) {
//Valid
id_map.insert(id, Id(i));
++i;
}
}
return id_map;
}
//Returns a vector based on 'values', which has had entries dropped and
//re-ordered according according to 'id_map'.
//
// Each entry in id_map corresponds to the assoicated element in 'values'.
// The value of the id_map entry is the new ID of the entry in values.
//
// If it is an invalid ID, the element in values is dropped.
// Otherwise the element is moved to the new ID location.
template<typename Id, typename T>
tatum::util::linear_map<Id,T> clean_and_reorder_values(const tatum::util::linear_map<Id,T>& values, const tatum::util::linear_map<Id,Id>& id_map) {
TATUM_ASSERT(values.size() == id_map.size());
//Allocate space for the values that will not be dropped
tatum::util::linear_map<Id,T> result;
//Move over the valid entries to their new locations
for(size_t cur_idx = 0; cur_idx < values.size(); ++cur_idx) {
Id old_id = Id(cur_idx);
Id new_id = id_map[old_id];
if (new_id) {
//There is a valid mapping
result.insert(new_id, std::move(values[old_id]));
}
}
return result;
}
//Returns the set of new valid Ids defined by 'id_map'
//TOOD: merge with clean_and_reorder_values
template<typename Id>
tatum::util::linear_map<Id,Id> clean_and_reorder_ids(const tatum::util::linear_map<Id,Id>& id_map) {
//For IDs, the values are the new id's stored in the map
//Allocate a new vector to store the values that have been not dropped
tatum::util::linear_map<Id,Id> result;
//Move over the valid entries to their new locations
for(size_t cur_idx = 0; cur_idx < id_map.size(); ++cur_idx) {
Id old_id = Id(cur_idx);
Id new_id = id_map[old_id];
if (new_id) {
result.insert(new_id, new_id);
}
}
return result;
}
template<typename Container, typename ValId>
Container update_valid_refs(const Container& values, const tatum::util::linear_map<ValId,ValId>& id_map) {
Container updated;
for(ValId orig_val : values) {
if(orig_val) {
//Original item valid
ValId new_val = id_map[orig_val];
if(new_val) {
//The original item exists in the new mapping
updated.emplace_back(new_val);
}
}
}
return updated;
}
//Updates the Ids in 'values' based on id_map, even if the original or new mapping is not valid
template<typename Container, typename ValId>
Container update_all_refs(const Container& values, const tatum::util::linear_map<ValId,ValId>& id_map) {
Container updated;
for(ValId orig_val : values) {
//The original item was valid
ValId new_val = id_map[orig_val];
//The original item exists in the new mapping
updated.emplace_back(new_val);
}
return updated;
}
//Recursive helper functions for collecting transitively connected nodes
void find_transitive_fanout_nodes_recurr(const TimingGraph& tg,
std::vector<NodeId>& nodes,
const NodeId node,
size_t max_depth=std::numeric_limits<size_t>::max(),
size_t depth=0);
void find_transitive_fanin_nodes_recurr(const TimingGraph& tg,
std::vector<NodeId>& nodes,
const NodeId node,
size_t max_depth=std::numeric_limits<size_t>::max(),
size_t depth=0);
size_t TimingGraph::node_num_active_in_edges(const NodeId node) const {
size_t active_edges = 0;
for (EdgeId edge : node_in_edges(node)) {
if (!edge_disabled(edge)) {
++active_edges;
}
}
return active_edges;
}
EdgeId TimingGraph::node_clock_capture_edge(const NodeId node) const {
if(node_type(node) == NodeType::SINK) {
//Only sinks can have clock capture edges
//Look through the edges for the incoming clock edge
for(EdgeId edge : node_in_edges(node)) {
if(edge_type(edge) == EdgeType::PRIMITIVE_CLOCK_CAPTURE) {
return edge;
}
}
}
return EdgeId::INVALID();
}
EdgeId TimingGraph::node_clock_launch_edge(const NodeId node) const {
if(node_type(node) == NodeType::SOURCE) {
//Only sources can have clock capture edges
//Look through the edges for the incoming clock edge
for(EdgeId edge : node_in_edges(node)) {
if(edge_type(edge) == EdgeType::PRIMITIVE_CLOCK_LAUNCH) {
return edge;
}
}
}
return EdgeId::INVALID();
}
NodeId TimingGraph::add_node(const NodeType type) {
//Invalidate the levelization
is_levelized_ = false;
//Reserve an ID
NodeId node_id = NodeId(node_ids_.size());
node_ids_.push_back(node_id);
//Type
node_types_.push_back(type);
//Edges
node_out_edges_.push_back(std::vector<EdgeId>());
node_in_edges_.push_back(std::vector<EdgeId>());
//Verify sizes
TATUM_ASSERT(node_types_.size() == node_out_edges_.size());
TATUM_ASSERT(node_types_.size() == node_in_edges_.size());
//Return the ID of the added node
return node_id;
}
EdgeId TimingGraph::add_edge(const EdgeType type, const NodeId src_node, const NodeId sink_node) {
//We require that the source/sink node must already be in the graph,
// so we can update them with thier edge references
TATUM_ASSERT(valid_node_id(src_node));
TATUM_ASSERT(valid_node_id(sink_node));
//Invalidate the levelization
is_levelized_ = false;
//Reserve an edge ID
EdgeId edge_id = EdgeId(edge_ids_.size());
edge_ids_.push_back(edge_id);
//Create the edgge
edge_types_.push_back(type);
edge_src_nodes_.push_back(src_node);
edge_sink_nodes_.push_back(sink_node);
edges_disabled_.push_back(false);
//Verify
TATUM_ASSERT(edge_sink_nodes_.size() == edge_src_nodes_.size());
//Update the nodes the edge references
node_out_edges_[src_node].push_back(edge_id);
node_in_edges_[sink_node].push_back(edge_id);
TATUM_ASSERT(edge_type(edge_id) == type);
TATUM_ASSERT(edge_src_node(edge_id) == src_node);
TATUM_ASSERT(edge_sink_node(edge_id) == sink_node);
//Return the edge id of the added edge
return edge_id;
}
void TimingGraph::remove_node(const NodeId node_id) {
TATUM_ASSERT(valid_node_id(node_id));
//Invalidate the levelization
is_levelized_ = false;
//Invalidate all the references
for(EdgeId in_edge : node_in_edges(node_id)) {
if(!in_edge) continue;
remove_edge(in_edge);
}
for(EdgeId out_edge : node_out_edges(node_id)) {
if(!out_edge) continue;
remove_edge(out_edge);
}
//Mark the node as invalid
node_ids_[node_id] = NodeId::INVALID();
}
void TimingGraph::remove_edge(const EdgeId edge_id) {
TATUM_ASSERT(valid_edge_id(edge_id));
//Invalidate the levelization
is_levelized_ = false;
//Invalidate the upstream node to edge references
NodeId src_node = edge_src_node(edge_id);
auto iter_out = std::find(node_out_edges_[src_node].begin(), node_out_edges_[src_node].end(), edge_id);
TATUM_ASSERT(iter_out != node_out_edges_[src_node].end());
*iter_out = EdgeId::INVALID();
//Invalidate the downstream node to edge references
NodeId sink_node = edge_sink_node(edge_id);
auto iter_in = std::find(node_in_edges_[sink_node].begin(), node_in_edges_[sink_node].end(), edge_id);
TATUM_ASSERT(iter_in != node_in_edges_[sink_node].end());
*iter_in = EdgeId::INVALID();
//Mark the edge invalid
edge_ids_[edge_id] = EdgeId::INVALID();
}
void TimingGraph::disable_edge(const EdgeId edge, bool disable) {
TATUM_ASSERT(valid_edge_id(edge));
if(edges_disabled_[edge] != disable) {
//If we are changing edges the levelization is no longer valid
is_levelized_ = false;
}
//Update the edge's disabled flag
edges_disabled_[edge] = disable;
}
EdgeType TimingGraph::edge_type(const EdgeId edge) const {
TATUM_ASSERT(valid_edge_id(edge));
return edge_types_[edge];
}
EdgeId TimingGraph::find_edge(const tatum::NodeId src_node, const tatum::NodeId sink_node) const {
TATUM_ASSERT(valid_node_id(src_node));
TATUM_ASSERT(valid_node_id(sink_node));
for(EdgeId edge : node_out_edges(src_node)) {
if(edge_sink_node(edge) == sink_node) {
return edge;
}
}
return EdgeId::INVALID();
}
GraphIdMaps TimingGraph::compress() {
auto node_id_map = compress_ids(node_ids_);
auto edge_id_map = compress_ids(edge_ids_);
remap_nodes(node_id_map);
remap_edges(edge_id_map);
levelize();
validate();
return {node_id_map, edge_id_map};
}
void TimingGraph::levelize() {
if(!is_levelized_) {
force_levelize();
}
}
void TimingGraph::force_levelize() {
//Levelizes the timing graph
//This over-writes any previous levelization if it exists.
//
//Also records primary outputs
//Clear any previous levelization
level_nodes_.clear();
level_ids_.clear();
primary_inputs_.clear();
logical_outputs_.clear();
//Allocate space for the first level
level_nodes_.resize(1);
//Copy the number of input edges per-node
//These will be decremented to know when all a node's upstream parents have been
//placed in a previous level (indicating that the node goes in the current level)
//
//Also initialize the first level (nodes with no fanin)
std::vector<int> node_fanin_remaining(nodes().size());
for(NodeId node_id : nodes()) {
size_t node_fanin = 0;
for(EdgeId edge : node_in_edges(node_id)) {
if(edge_disabled(edge)) continue;
++node_fanin;
}
node_fanin_remaining[size_t(node_id)] = node_fanin;
//Initialize the first level
if(node_fanin == 0) {
level_nodes_[LevelId(0)].push_back(node_id);
if (node_type(node_id) == NodeType::SOURCE) {
//We require that all primary inputs (i.e. top-level circuit inputs) to
//be SOURCEs. Due to disconnected nodes we may have non-SOURCEs which
//appear in the first level.
primary_inputs_.push_back(node_id);
}
}
}
//Walk the graph from primary inputs (no fanin) to generate a topological sort
//
//We inspect the output edges of each node and decrement the fanin count of the
//target node. Once the fanin count for a node reaches zero it can be added
//to the current level.
int level_idx = 0;
level_ids_.emplace_back(level_idx);
bool inserted_node_in_level = true;
while(inserted_node_in_level) { //If nothing was inserted we are finished
inserted_node_in_level = false;
for(const NodeId node_id : level_nodes_[LevelId(level_idx)]) {
//Inspect the fanout
for(EdgeId edge_id : node_out_edges(node_id)) {
if(edge_disabled(edge_id)) continue;
NodeId sink_node = edge_sink_node(edge_id);
//Decrement the fanin count
TATUM_ASSERT(node_fanin_remaining[size_t(sink_node)] > 0);
node_fanin_remaining[size_t(sink_node)]--;
//Add to the next level if all fanin has been seen
if(node_fanin_remaining[size_t(sink_node)] == 0) {
//Ensure there is space by allocating the next level if required
level_nodes_.resize(level_idx+2);
//Add the node
level_nodes_[LevelId(level_idx+1)].push_back(sink_node);
inserted_node_in_level = true;
}
}
//Also track the primary outputs (those with fan-in AND no fan-out)
//
// There may be some node with neither any fan-in or fan-out.
// We will treat them as primary inputs, so they should not be to
// the primary outputs
if( node_out_edges(node_id).size() == 0
&& node_in_edges(node_id).size() != 0
&& node_type(node_id) == NodeType::SINK) {
logical_outputs_.push_back(node_id);
}
}
if(inserted_node_in_level) {
level_idx++;
level_ids_.emplace_back(level_idx);
}
}
//Mark the levelization as valid
is_levelized_ = true;
}
bool TimingGraph::validate() const {
bool valid = true;
valid &= validate_sizes();
valid &= validate_values();
valid &= validate_structure();
return valid;
}
GraphIdMaps TimingGraph::optimize_layout() {
auto node_id_map = optimize_node_layout();
remap_nodes(node_id_map);
levelize();
auto edge_id_map = optimize_edge_layout();
remap_edges(edge_id_map);
levelize();
return {node_id_map, edge_id_map};
}
tatum::util::linear_map<EdgeId,EdgeId> TimingGraph::optimize_edge_layout() const {
//Make all edges in a level be contiguous in memory
//Determine the edges driven by each level of the graph
std::vector<std::vector<EdgeId>> edge_levels;
for(LevelId level_id : levels()) {
edge_levels.push_back(std::vector<EdgeId>());
for(auto node_id : level_nodes(level_id)) {
//We walk the nodes according to the input-edge order.
//This is the same order used by the arrival-time traversal (which is responsible
//for most of the analyzer run-time), so matching it's order exactly results in
//better cache locality
for(EdgeId edge_id : node_in_edges(node_id)) {
//edge_id is driven by nodes in level level_idx
edge_levels[size_t(level_id)].push_back(edge_id);
}
}
}
//Maps from from original to new edge id, used to update node to edge refs
tatum::util::linear_map<EdgeId,EdgeId> orig_to_new_edge_id(edges().size());
//Determine the new order
size_t iedge = 0;
for(auto& edge_level : edge_levels) {
for(const EdgeId orig_edge_id : edge_level) {
//Save the new edge id to update nodes
orig_to_new_edge_id[orig_edge_id] = EdgeId(iedge);
++iedge;
}
}
for(auto new_id : orig_to_new_edge_id) {
TATUM_ASSERT(new_id);
}
TATUM_ASSERT(iedge == edges().size());
return orig_to_new_edge_id;
}
tatum::util::linear_map<NodeId,NodeId> TimingGraph::optimize_node_layout() const {
//Make all nodes in a level be contiguous in memory
/*
* Keep a map of the old and new node ids to update edges
* and node levels later
*/
tatum::util::linear_map<NodeId,NodeId> orig_to_new_node_id(nodes().size());
//Determine the new order
size_t inode = 0;
for(const LevelId level_id : levels()) {
for(const NodeId old_node_id : level_nodes(level_id)) {
//Record the new node id
orig_to_new_node_id[old_node_id] = NodeId(inode);
++inode;
}
}
for(auto new_id : orig_to_new_node_id) {
TATUM_ASSERT(new_id);
}
TATUM_ASSERT(inode == nodes().size());
return orig_to_new_node_id;
}
void TimingGraph::remap_nodes(const tatum::util::linear_map<NodeId,NodeId>& node_id_map) {
is_levelized_ = false;
//Update values
node_ids_ = clean_and_reorder_ids(node_id_map);
node_types_ = clean_and_reorder_values(node_types_, node_id_map);
node_in_edges_ = clean_and_reorder_values(node_in_edges_, node_id_map);
node_out_edges_ = clean_and_reorder_values(node_out_edges_, node_id_map);
//Update references
edge_src_nodes_ = update_all_refs(edge_src_nodes_, node_id_map);
edge_sink_nodes_ = update_all_refs(edge_sink_nodes_, node_id_map);
}
void TimingGraph::remap_edges(const tatum::util::linear_map<EdgeId,EdgeId>& edge_id_map) {
is_levelized_ = false;
//Update values
edge_ids_ = clean_and_reorder_ids(edge_id_map);
edge_types_ = clean_and_reorder_values(edge_types_, edge_id_map);
edge_sink_nodes_ = clean_and_reorder_values(edge_sink_nodes_, edge_id_map);
edge_src_nodes_ = clean_and_reorder_values(edge_src_nodes_, edge_id_map);
edges_disabled_ = clean_and_reorder_values(edges_disabled_, edge_id_map);
//Update cross-references
for(auto& edges_ref : node_in_edges_) {
edges_ref = update_valid_refs(edges_ref, edge_id_map);
}
for(auto& edges_ref : node_out_edges_) {
edges_ref = update_valid_refs(edges_ref, edge_id_map);
}
}
bool TimingGraph::valid_node_id(const NodeId node_id) const {
return size_t(node_id) < node_ids_.size();
}
bool TimingGraph::valid_edge_id(const EdgeId edge_id) const {
return size_t(edge_id) < edge_ids_.size();
}
bool TimingGraph::valid_level_id(const LevelId level_id) const {
return size_t(level_id) < level_ids_.size();
}
bool TimingGraph::validate_sizes() const {
if ( node_ids_.size() != node_types_.size()
|| node_ids_.size() != node_in_edges_.size()
|| node_ids_.size() != node_out_edges_.size()) {
throw tatum::Error("Inconsistent node attribute sizes");
}
if ( edge_ids_.size() != edge_types_.size()
|| edge_ids_.size() != edge_sink_nodes_.size()
|| edge_ids_.size() != edge_src_nodes_.size()
|| edge_ids_.size() != edges_disabled_.size()) {
throw tatum::Error("Inconsistent edge attribute sizes");
}
if (level_ids_.size() != level_nodes_.size()) {
throw tatum::Error("Inconsistent level attribute sizes");
}
return true;
}
bool TimingGraph::validate_values() const {
for(NodeId node_id : nodes()) {
if(!valid_node_id(node_id)) {
throw tatum::Error("Invalid node id", node_id);
}
for(EdgeId edge_id : node_in_edges_[node_id]) {
if(!valid_edge_id(edge_id)) {
throw tatum::Error("Invalid node-in-edge reference", node_id, edge_id);
}
//Check that the references are consistent
if(edge_sink_nodes_[edge_id] != node_id) {
throw tatum::Error("Mismatched edge-sink/node-in-edge reference", node_id, edge_id);
}
}
for(EdgeId edge_id : node_out_edges_[node_id]) {
if(!valid_edge_id(edge_id)) {
throw tatum::Error("Invalid node-out-edge reference", node_id, edge_id);
}
//Check that the references are consistent
if(edge_src_nodes_[edge_id] != node_id) {
throw tatum::Error("Mismatched edge-src/node-out-edge reference", node_id, edge_id);
}
}
}
for(EdgeId edge_id : edges()) {
if(!valid_edge_id(edge_id)) {
throw tatum::Error("Invalid edge id", edge_id);
}
NodeId src_node = edge_src_nodes_[edge_id];
if(!valid_node_id(src_node)) {
throw tatum::Error("Invalid edge source node", src_node, edge_id);
}
NodeId sink_node = edge_sink_nodes_[edge_id];
if(!valid_node_id(sink_node)) {
throw tatum::Error("Invalid edge sink node", sink_node, edge_id);
}
}
//TODO: more checking
return true;
}
bool TimingGraph::validate_structure() const {
//Verify that the timing graph connectivity is as expected
if (!is_levelized_) {
throw tatum::Error("Timing graph must be levelized for structural validation");
}
for(NodeId src_node : nodes()) {
NodeType src_type = node_type(src_node);
auto out_edges = node_out_edges(src_node);
auto in_edges = node_in_edges(src_node);
//Check expected number of fan-in/fan-out edges
if(src_type == NodeType::SOURCE) {
if(in_edges.size() > 1) {
throw tatum::Error("SOURCE node has more than one active incoming edge (expected 0 if primary input, or 1 if clocked)", src_node);
}
} else if (src_type == NodeType::SINK) {
if(out_edges.size() > 0) {
throw tatum::Error("SINK node has out-going edges", src_node);
}
} else if (src_type == NodeType::IPIN) {
if(in_edges.size() == 0 && !allow_dangling_combinational_nodes_) {
throw tatum::Error("IPIN has no in-coming edges", src_node);
}
if(out_edges.size() == 0 && !allow_dangling_combinational_nodes_) {
throw tatum::Error("IPIN has no out-going edges", src_node);
}
} else if (src_type == NodeType::OPIN) {
//May have no incoming edges if a constant generator, so don't check that case
if(out_edges.size() == 0 && !allow_dangling_combinational_nodes_) {
throw tatum::Error("OPIN has no out-going edges", src_node);
}
} else {
TATUM_ASSERT(src_type == NodeType::CPIN);
if(in_edges.size() == 0) {
throw tatum::Error("CPIN has no in-coming edges", src_node);
}
//We do not check for out-going cpin edges, since there is no reason that
//a clock pin must be used
}
//Check node-type edge connectivity
for(EdgeId out_edge : node_out_edges(src_node)) {
NodeId sink_node = edge_sink_node(out_edge);
NodeType sink_type = node_type(sink_node);
EdgeType out_edge_type = edge_type(out_edge);
//Check type connectivity
if (src_type == NodeType::SOURCE) {
if( sink_type != NodeType::IPIN
&& sink_type != NodeType::OPIN
&& sink_type != NodeType::CPIN
&& sink_type != NodeType::SINK) {
throw tatum::Error("SOURCE nodes should only drive IPIN, OPIN, CPIN or SINK nodes", src_node, out_edge);
}
if(sink_type == NodeType::SINK) {
if( out_edge_type != EdgeType::INTERCONNECT
&& out_edge_type != EdgeType::PRIMITIVE_COMBINATIONAL) {
throw tatum::Error("SOURCE to SINK edges should always be either INTERCONNECT or PRIMTIIVE_COMBINATIONAL type edges", src_node, out_edge);
}
} else if (sink_type == NodeType::OPIN) {
if(out_edge_type != EdgeType::PRIMITIVE_COMBINATIONAL) {
throw tatum::Error("SOURCE to OPIN edges should always be PRIMITIVE_COMBINATIONAL type edges", src_node, out_edge);
}
} else {
TATUM_ASSERT(sink_type == NodeType::IPIN || sink_type == NodeType::CPIN);
if(out_edge_type != EdgeType::INTERCONNECT) {
throw tatum::Error("SOURCE to IPIN/CPIN edges should always be INTERCONNECT type edges", src_node, out_edge);
}
}
} else if (src_type == NodeType::SINK) {
throw tatum::Error("SINK nodes should not have out-going edges", sink_node);
} else if (src_type == NodeType::IPIN) {
if(sink_type != NodeType::OPIN && sink_type != NodeType::SINK) {
throw tatum::Error("IPIN nodes should only drive OPIN or SINK nodes", src_node, out_edge);
}
if(out_edge_type != EdgeType::PRIMITIVE_COMBINATIONAL) {
throw tatum::Error("IPIN to OPIN/SINK edges should always be PRIMITIVE_COMBINATIONAL type edges", src_node, out_edge);
}
} else if (src_type == NodeType::OPIN) {
if( sink_type != NodeType::IPIN
&& sink_type != NodeType::CPIN
&& sink_type != NodeType::SINK) {
throw tatum::Error("OPIN nodes should only drive IPIN, CPIN or SINK nodes", src_node, out_edge);
}
if(out_edge_type != EdgeType::INTERCONNECT) {
throw tatum::Error("OPIN out edges should always be INTERCONNECT type edges", src_node, out_edge);
}
} else if (src_type == NodeType::CPIN) {
if( sink_type != NodeType::SOURCE
&& sink_type != NodeType::SINK
&& sink_type != NodeType::OPIN) {
throw tatum::Error("CPIN nodes should only drive SOURCE, OPIN or SINK nodes", src_node, out_edge);
}
if(sink_type == NodeType::SOURCE && out_edge_type != EdgeType::PRIMITIVE_CLOCK_LAUNCH) {
throw tatum::Error("CPIN to SOURCE edges should always be PRIMITIVE_CLOCK_LAUNCH type edges", src_node, out_edge);
} else if (sink_type == NodeType::SINK && out_edge_type != EdgeType::PRIMITIVE_CLOCK_CAPTURE) {
throw tatum::Error("CPIN to SINK edges should always be PRIMITIVE_CLOCK_CAPTURE type edges", src_node, out_edge);
}
} else {
throw tatum::Error("Unrecognized node type", src_node, out_edge);
}
}
}
//Record the nodes assoicated with each edge
std::map<std::pair<NodeId,NodeId>,std::vector<EdgeId>> edge_nodes;
for(EdgeId edge : edges()) {
NodeId src_node = edge_src_node(edge);
NodeId sink_node = edge_sink_node(edge);
edge_nodes[{src_node,sink_node}].push_back(edge);
}
//Check for duplicate edges between pairs of nodes
for(const auto& kv : edge_nodes) {
const auto& edge_ids = kv.second;
TATUM_ASSERT_MSG(edge_ids.size() > 0, "Node pair must have at least one edge");
if(edge_ids.size() > 1) {
NodeId src_node = kv.first.first;
NodeId sink_node = kv.first.second;
std::stringstream ss;
ss << "Dulplicate timing edges found " << src_node << " -> " << sink_node
<< ", duplicate edges: ";
for(EdgeId edge : edge_ids) {
ss << edge << " ";
}
throw tatum::Error(ss.str(), src_node);
}
}
for(NodeId node : primary_inputs()) {
if(!node_in_edges(node).empty()) {
throw tatum::Error("Primary input nodes should have no incoming edges", node);
}
if(node_type(node) != NodeType::SOURCE) {
throw tatum::Error("Primary inputs should be only SOURCE nodes", node);
}
}
for(NodeId node : logical_outputs()) {
if(!node_out_edges(node).empty()) {
throw tatum::Error("Logical output node should have no outgoing edges", node);
}
if(node_type(node) != NodeType::SINK) {
throw tatum::Error("Logical outputs should be only SINK nodes", node);
}
}
auto sccs = identify_combinational_loops(*this);
if(!sccs.empty()) {
throw Error("Timing graph contains active combinational loops. "
"Either disable timing edges (to break the loops) or restructure the graph.");
//Future work:
//
// We could handle this internally by identifying the incoming and outgoing edges of the SCC,
// and estimating a 'max' delay through the SCC from each incoming to each outgoing edge.
// The SCC could then be replaced with a clique between SCC input and output edges.
//
// One possible estimate is to trace the longest path through the SCC without visiting a node
// more than once (although this is not gaurenteed to be conservative).
}
return true;
}
size_t TimingGraph::count_active_edges(edge_range edges_to_check) const {
size_t active_cnt = 0;
for(EdgeId edge : edges_to_check) {
if(!edge_disabled(edge)) {
++active_cnt;
}
}
return active_cnt;
}
//Returns sets of nodes involved in combinational loops
std::vector<std::vector<NodeId>> identify_combinational_loops(const TimingGraph& tg) {
constexpr size_t MIN_LOOP_SCC_SIZE = 2; //Any SCC of size >= 2 is a loop in the timing graph
return identify_strongly_connected_components(tg, MIN_LOOP_SCC_SIZE);
}
std::vector<NodeId> find_transitively_connected_nodes(const TimingGraph& tg,
const std::vector<NodeId> through_nodes,
size_t max_depth) {
std::vector<NodeId> nodes;
for(NodeId through_node : through_nodes) {
find_transitive_fanin_nodes_recurr(tg, nodes, through_node, max_depth);
find_transitive_fanout_nodes_recurr(tg, nodes, through_node, max_depth);
}
std::sort(nodes.begin(), nodes.end());
nodes.erase(std::unique(nodes.begin(), nodes.end()), nodes.end());
return nodes;
}
std::vector<NodeId> find_transitive_fanin_nodes(const TimingGraph& tg,
const std::vector<NodeId> sinks,
size_t max_depth) {
std::vector<NodeId> nodes;
for(NodeId sink : sinks) {
find_transitive_fanin_nodes_recurr(tg, nodes, sink, max_depth);
}
std::sort(nodes.begin(), nodes.end());
nodes.erase(std::unique(nodes.begin(), nodes.end()), nodes.end());
return nodes;
}
std::vector<NodeId> find_transitive_fanout_nodes(const TimingGraph& tg,
const std::vector<NodeId> sources,
size_t max_depth) {
std::vector<NodeId> nodes;
for(NodeId source : sources) {
find_transitive_fanout_nodes_recurr(tg, nodes, source, max_depth);
}
std::sort(nodes.begin(), nodes.end());
nodes.erase(std::unique(nodes.begin(), nodes.end()), nodes.end());
return nodes;
}
void find_transitive_fanin_nodes_recurr(const TimingGraph& tg,
std::vector<NodeId>& nodes,
const NodeId node,
size_t max_depth,
size_t depth) {
if(depth > max_depth) return;
nodes.push_back(node);
for(EdgeId in_edge : tg.node_in_edges(node)) {
if(tg.edge_disabled(in_edge)) continue;
NodeId src_node = tg.edge_src_node(in_edge);
find_transitive_fanin_nodes_recurr(tg, nodes, src_node, max_depth, depth + 1);
}
}
void find_transitive_fanout_nodes_recurr(const TimingGraph& tg,
std::vector<NodeId>& nodes,
const NodeId node,
size_t max_depth,
size_t depth) {
if(depth > max_depth) return;
nodes.push_back(node);
for(EdgeId out_edge : tg.node_out_edges(node)) {
if(tg.edge_disabled(out_edge)) continue;
NodeId sink_node = tg.edge_sink_node(out_edge);
find_transitive_fanout_nodes_recurr(tg, nodes, sink_node, max_depth, depth+1);
}
}
EdgeType infer_edge_type(const TimingGraph& tg, EdgeId edge) {
NodeId src_node = tg.edge_src_node(edge);
NodeId sink_node = tg.edge_sink_node(edge);
NodeType src_node_type = tg.node_type(src_node);
NodeType sink_node_type = tg.node_type(sink_node);
if(src_node_type == NodeType::IPIN && sink_node_type == NodeType::OPIN) {
return EdgeType::PRIMITIVE_COMBINATIONAL;
} else if ( (src_node_type == NodeType::OPIN || src_node_type == NodeType::SOURCE)
&& (sink_node_type == NodeType::IPIN || sink_node_type == NodeType::SINK || sink_node_type == NodeType::CPIN)) {
return EdgeType::INTERCONNECT;
} else if (src_node_type == NodeType::CPIN) {
if (sink_node_type == NodeType::SOURCE) {
return EdgeType::PRIMITIVE_CLOCK_LAUNCH;
} else if(sink_node_type == NodeType::SINK) {
return EdgeType::PRIMITIVE_CLOCK_CAPTURE;
} else {
throw tatum::Error("Invalid edge sink node (CPIN source node must connect to SOURCE or SINK sink node)", sink_node, edge);
}
} else {
throw tatum::Error("Invalid edge sink/source nodes", edge);
}
}
//Stream output for NodeType
std::ostream& operator<<(std::ostream& os, const NodeType type) {
if (type == NodeType::SOURCE) os << "SOURCE";
else if (type == NodeType::SINK) os << "SINK";
else if (type == NodeType::IPIN) os << "IPIN";
else if (type == NodeType::OPIN) os << "OPIN";
else if (type == NodeType::CPIN) os << "CPIN";
else throw std::domain_error("Unrecognized NodeType");
return os;
}
//Stream output for EdgeType
std::ostream& operator<<(std::ostream& os, const EdgeType type) {
if (type == EdgeType::PRIMITIVE_COMBINATIONAL) os << "PRIMITIVE_COMBINATIONAL";
else if (type == EdgeType::PRIMITIVE_CLOCK_LAUNCH) os << "PRIMITIVE_CLOCK_LAUNCH";
else if (type == EdgeType::PRIMITIVE_CLOCK_CAPTURE) os << "PRIMITIVE_CLOCK_CAPTURE";
else if (type == EdgeType::INTERCONNECT) os << "INTERCONNECT";
else throw std::domain_error("Unrecognized EdgeType");
return os;
}
std::ostream& operator<<(std::ostream& os, NodeId node_id) {
if(node_id == NodeId::INVALID()) {
return os << "Node(INVALID)";
} else {
return os << "Node(" << size_t(node_id) << ")";
}
}
std::ostream& operator<<(std::ostream& os, EdgeId edge_id) {
if(edge_id == EdgeId::INVALID()) {
return os << "Edge(INVALID)";
} else {
return os << "Edge(" << size_t(edge_id) << ")";
}
}
std::ostream& operator<<(std::ostream& os, DomainId domain_id) {
if(domain_id == DomainId::INVALID()) {
return os << "Domain(INVALID)";
} else {
return os << "Domain(" << size_t(domain_id) << ")";
}
}
std::ostream& operator<<(std::ostream& os, LevelId level_id) {
if(level_id == LevelId::INVALID()) {
return os << "Level(INVALID)";
} else {
return os << "Level(" << size_t(level_id) << ")";
}
}
} //namepsace