| #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 |
| |