blob: 77d11ab0c44bd60b99d32166f46b2b4a00b52476 [file]
#pragma once
/**
* The 'TimingGraph' class represents a timing graph.
*
* Logically the timing graph is a directed graph connecting Primary Inputs (nodes with no
* fan-in, e.g. circuit inputs Flip-Flop Q pins) to Primary Outputs (nodes with no fan-out,
* e.g. circuit outputs, Flip-Flop D pins), connecting through intermediate nodes (nodes with
* both fan-in and fan-out, e.g. combinational logic).
*
* To make performing the forward/backward traversals through the timing graph easier, we actually
* store all edges as bi-directional edges.
*
* NOTE: We store only the static connectivity and node information in the 'TimingGraph' class.
* Other dynamic information (edge delays, node arrival/required times) is stored seperately.
* This means that most actions opearting on the timing graph (e.g. TimingAnalyzers) only
* require read-only access to the timing graph.
*
* Accessing Graph Data
* ======================
* For performance reasons (see Implementation section for details) we store all graph data
* in the 'TimingGraph' class, and do not use separate edge/node objects. To facilitate this,
* each node and edge in the graph is given a unique identifier (e.g. NodeId, EdgeId). These
* ID's can then be used to access the required data through the appropriate member function.
*
* Implementation
* ================
* The 'TimingGraph' class represents the timing graph in a "Struct of Arrays (SoA)" manner,
* rather than the more typical "Array of Structs (AoS)" data layout.
*
* By using a SoA layout we keep all data for a particular field (e.g. node types) in contiguous
* memory. Using an AoS layout the various fields accross nodes would *not* be contiguous
* (although the different fields within each object (e.g. a TimingNode class) would be contiguous.
* Since we typically perform operations on particular fields accross nodes the SoA layout performs
* better (and enables memory ordering optimizations). The edges are also stored in a SOA format.
*
* The SoA layout also motivates the ID based approach, which allows direct indexing into the required
* vector to retrieve data.
*
* Memory Ordering Optimizations
* ===============================
* SoA also allows several additional memory layout optimizations. In particular, we know the
* order that a (serial) timing analyzer will walk the timing graph (i.e. level-by-level, from the
* start to end node in each level).
*
* Using this information we can re-arrange the node and edge data to match this traversal order.
* This greatly improves caching behaviour, since pulling in data for one node immediately pulls
* in data for the next node/edge to be processed. This exploits both spatial and temporal locality,
* and ensures that each cache line pulled into the cache will (likely) be accessed multiple times
* before being evicted.
*
* Note that performing these optimizations is currently done explicity by calling the optimize_edge_layout()
* and optimize_node_layout() member functions. In the future (particularily if incremental modification
* support is added), it may be a good idea apply these modifications automatically as needed.
*
*/
#include <vector>
#include <set>
#include <limits>
#include "tatum/util/tatum_range.hpp"
#include "tatum/util/tatum_linear_map.hpp"
#include "tatum/TimingGraphFwd.hpp"
namespace tatum {
class TimingGraph {
public: //Public types
//Iterators
typedef tatum::util::linear_map<EdgeId,EdgeId>::const_iterator edge_iterator;
typedef tatum::util::linear_map<NodeId,NodeId>::const_iterator node_iterator;
typedef tatum::util::linear_map<LevelId,LevelId>::const_iterator level_iterator;
typedef tatum::util::linear_map<LevelId,LevelId>::const_reverse_iterator reverse_level_iterator;
//Ranges
typedef tatum::util::Range<node_iterator> node_range;
typedef tatum::util::Range<edge_iterator> edge_range;
typedef tatum::util::Range<level_iterator> level_range;
typedef tatum::util::Range<reverse_level_iterator> reverse_level_range;
public: //Public accessors
/*
* Node data accessors
*/
///\param id The id of a node
///\returns The type of the node
NodeType node_type(const NodeId id) const { return node_types_[id]; }
///\param id The node id
///\returns A range of all out-going edges the node drives
edge_range node_out_edges(const NodeId id) const { return tatum::util::make_range(node_out_edges_[id].begin(), node_out_edges_[id].end()); }
///\param id The node id
///\returns A range of all in-coming edges the node drives
edge_range node_in_edges(const NodeId id) const { return tatum::util::make_range(node_in_edges_[id].begin(), node_in_edges_[id].end()); }
///\param id The Node id
///\returns The number of active (undisabled) edges terminating at the node
size_t node_num_active_in_edges(const NodeId id) const;
///\param id The node id
///\returns The edge id corresponding to the incoming clock capture edge, or EdgeId::INVALID() if none
EdgeId node_clock_capture_edge(const NodeId id) const;
///\param id The node id
///\returns The edge id corresponding to the incoming clock launch edge, or EdgeId::INVALID() if none
EdgeId node_clock_launch_edge(const NodeId id) const;
/*
* Edge accessors
*/
///\param edge The id of an edge
///\returns The type of the edge
EdgeType edge_type(const EdgeId id) const;
///\param id The id of an edge
///\returns The node id of the edge's sink
NodeId edge_sink_node(const EdgeId id) const { return edge_sink_nodes_[id]; }
///\param id The id of an edge
///\returns The node id of the edge's source (driver)
NodeId edge_src_node(const EdgeId id) const {
return edge_src_nodes_[id];
}
///\param edge The id of an edge
///\returns Whether the edge is disabled (i.e. ignored during timing analysis)
bool edge_disabled(const EdgeId id) const { return edges_disabled_[id]; }
///\param src_node the edge's source node
///\param sink_node the edge's sink node
///\returns The edge betwen these the source and sink nodes, or EdgeId::INVALID() if none exists
EdgeId find_edge(const tatum::NodeId src_node, const tatum::NodeId sink_node) const;
/*
* Level accessors
*/
///\param level_id The level index in the graph
///\pre The graph must be levelized.
///\returns A range containing the nodes in the level
///\see levelize()
node_range level_nodes(const LevelId level_id) const {
TATUM_ASSERT_MSG(is_levelized_, "Timing graph must be levelized");
return tatum::util::make_range(level_nodes_[level_id].begin(),
level_nodes_[level_id].end());
}
///\pre The graph must be levelized.
///\returns A range containing the nodes which are primary inputs (i.e. SOURCE's with no fanin, corresponding to top level design inputs pins)
///\warning Not all SOURCE nodes in the graph are primary inputs (e.g. FF Q pins are SOURCE's but have incomming edges from the clock network)
///\see levelize()
node_range primary_inputs() const {
TATUM_ASSERT_MSG(is_levelized_, "Timing graph must be levelized");
return tatum::util::make_range(primary_inputs_.begin(), primary_inputs_.end());
}
///\pre The graph must be levelized.
///\returns A range containing the nodes which are logical outputs (i.e. nodes with no fan-out
// corresponding to: top level design output pins and FF D pins)
///\warning The logical outputs may be on different levels of the graph
///\see levelize()
node_range logical_outputs() const {
TATUM_ASSERT_MSG(is_levelized_, "Timing graph must be levelized");
return tatum::util::make_range(logical_outputs_.begin(), logical_outputs_.end());
}
/*
* Graph aggregate accessors
*/
//\returns A range containing all nodes in the graph
node_range nodes() const { return tatum::util::make_range(node_ids_.begin(), node_ids_.end()); }
//\returns A range containing all edges in the graph
edge_range edges() const { return tatum::util::make_range(edge_ids_.begin(), edge_ids_.end()); }
//\returns A range containing all levels in the graph
level_range levels() const {
TATUM_ASSERT_MSG(is_levelized_, "Timing graph must be levelized");
return tatum::util::make_range(level_ids_.begin(), level_ids_.end());
}
//\returns A range containing all levels in the graph in *reverse* order
reverse_level_range reversed_levels() const {
TATUM_ASSERT_MSG(is_levelized_, "Timing graph must be levelized");
return tatum::util::make_range(level_ids_.rbegin(), level_ids_.rend());
}
//\returns true if the timing graph is internally consistent, throws an exception if not
bool validate() const;
public: //Mutators
/*
* Graph modifiers
*/
///Adds a node to the timing graph
///\param type The type of the node to be added
///\warning Graph will likely need to be re-levelized after modification
NodeId add_node(const NodeType type);
///Adds an edge to the timing graph
///\param type The edge's type
///\param src_node The node id of the edge's driving node
///\param sink_node The node id of the edge's sink node
///\pre The src_node and sink_node must have been already added to the graph
///\warning Graph will likely need to be re-levelized after modification
EdgeId add_edge(const EdgeType type, const NodeId src_node, const NodeId sink_node);
///Removes a node (and it's associated edges) from the timing graph
///\param node_id The node to remove
///\warning This will leave invalid ID references in the timing graph until compress() is called
///\see add_node(), compress()
void remove_node(const NodeId node_id);
///Removes an edge from the timing graph
///\param edge_id The edge to remove
///\warning This will leave invalid ID references in the timing graph until compress() is called
///\see add_edge(), compress()
void remove_edge(const EdgeId edge_id);
///Disables an edge in the timing graph (e.g. to break a combinational loop)
///\param edge_id The edge to disable
///\see identify_combinational_loops()
void disable_edge(const EdgeId edge_id, bool disable=true);
///Compresses the Edge and Node ID spaces to eliminate invalid entries
///\returns A structure containing mappings from old to new IDs
GraphIdMaps compress();
/*
* Graph-level modification operations
*/
///Levelizes the graph.
///\post The graph topologically ordered (i.e. the level of each node is known)
///\post The primary outputs have been identified
void levelize();
/*
* Memory layout optimization operations
*/
///Optimizes the graph's internal memory layout for better performance
///\warning Old IDs will be invalidated
///\returns The mapping from old to new IDs
GraphIdMaps optimize_layout();
///Sets whether dangling combinational nodes is an error (if true) or not
void set_allow_dangling_combinational_nodes(bool value) {
allow_dangling_combinational_nodes_ = value;
}
private: //Internal helper functions
///\returns A mapping from old to new edge ids which is optimized for performance
// (i.e. cache locality)
tatum::util::linear_map<EdgeId,EdgeId> optimize_edge_layout() const;
///\returns A mapping from old to new edge ids which is optimized for performance
// (i.e. cache locality)
tatum::util::linear_map<NodeId,NodeId> optimize_node_layout() const;
void remap_nodes(const tatum::util::linear_map<NodeId,NodeId>& node_id_map);
void remap_edges(const tatum::util::linear_map<EdgeId,EdgeId>& edge_id_map);
void force_levelize();
bool valid_node_id(const NodeId node_id) const;
bool valid_edge_id(const EdgeId edge_id) const;
bool valid_level_id(const LevelId level_id) const;
bool validate_sizes() const;
bool validate_values() const;
bool validate_structure() const;
size_t count_active_edges(edge_range) const;
private: //Data
/*
* For improved memory locality, we use a Struct of Arrays (SoA)
* data layout, rather than Array of Structs (AoS)
*/
//Node data
tatum::util::linear_map<NodeId,NodeId> node_ids_; //The node IDs in the graph
tatum::util::linear_map<NodeId,NodeType> node_types_; //Type of node
tatum::util::linear_map<NodeId,std::vector<EdgeId>> node_in_edges_; //Incomiing edge IDs for node
tatum::util::linear_map<NodeId,std::vector<EdgeId>> node_out_edges_; //Out going edge IDs for node
//Edge data
tatum::util::linear_map<EdgeId,EdgeId> edge_ids_; //The edge IDs in the graph
tatum::util::linear_map<EdgeId,EdgeType> edge_types_; //Type of edge
tatum::util::linear_map<EdgeId,NodeId> edge_sink_nodes_; //Sink node for each edge
tatum::util::linear_map<EdgeId,NodeId> edge_src_nodes_; //Source node for each edge
tatum::util::linear_map<EdgeId,bool> edges_disabled_;
//Auxilary graph-level info, filled in by levelize()
tatum::util::linear_map<LevelId,LevelId> level_ids_; //The level IDs in the graph
tatum::util::linear_map<LevelId,std::vector<NodeId>> level_nodes_; //Nodes in each level
std::vector<NodeId> primary_inputs_; //Primary input nodes of the timing graph.
std::vector<NodeId> logical_outputs_; //Logical output nodes of the timing graph.
bool is_levelized_ = false; //Inidcates if the current levelization is valid
bool allow_dangling_combinational_nodes_ = false;
};
//Returns the set of nodes (Strongly Connected Components) that form loops in the timing graph
std::vector<std::vector<NodeId>> identify_combinational_loops(const TimingGraph& tg);
//Returns the set of nodes transitively connected (either fanin or fanout) to nodes in through_nodes
//up to max_depth (default infinite) hops away
std::vector<NodeId> find_transitively_connected_nodes(const TimingGraph& tg,
const std::vector<NodeId> through_nodes,
size_t max_depth=std::numeric_limits<size_t>::max());
//Returns the set of nodes in the transitive fanin of nodes in sinks up to max_depth (default infinite) hops away
std::vector<NodeId> find_transitive_fanin_nodes(const TimingGraph& tg,
const std::vector<NodeId> sinks,
size_t max_depth=std::numeric_limits<size_t>::max());
//Returns the set of nodes in the transitive fanout of nodes in sources up to max_depth (default infinite) hops away
std::vector<NodeId> find_transitive_fanout_nodes(const TimingGraph& tg,
const std::vector<NodeId> sources,
size_t max_depth=std::numeric_limits<size_t>::max());
EdgeType infer_edge_type(const TimingGraph& tg, EdgeId edge);
//Mappings from old to new IDs
struct GraphIdMaps {
GraphIdMaps(tatum::util::linear_map<NodeId,NodeId> node_map,
tatum::util::linear_map<EdgeId,EdgeId> edge_map)
: node_id_map(node_map), edge_id_map(edge_map) {}
tatum::util::linear_map<NodeId,NodeId> node_id_map;
tatum::util::linear_map<EdgeId,EdgeId> edge_id_map;
};
} //namepsace