blob: 4d4ac33be99773cdcee59ade49c93c3ee233b717 [file] [log] [blame]
#include <set>
#include "vtr_log.h"
#include "vtr_linear_map.h"
#include "timing_graph_builder.h"
#include "vpr_error.h"
#include "vpr_utils.h"
#include "atom_netlist.h"
#include "atom_netlist_utils.h"
#include "tatum/TimingGraph.hpp"
using tatum::EdgeId;
using tatum::NodeId;
using tatum::NodeType;
using tatum::TimingGraph;
template<class K, class V>
tatum::util::linear_map<K, V> remap_valid(const tatum::util::linear_map<K, V>& data, const tatum::util::linear_map<K, K>& id_map) {
tatum::util::linear_map<K, V> new_data;
for (size_t i = 0; i < data.size(); ++i) {
tatum::EdgeId old_edge(i);
tatum::EdgeId new_edge = id_map[old_edge];
if (new_edge) {
new_data.insert(new_edge, data[old_edge]);
}
}
return new_data;
}
TimingGraphBuilder::TimingGraphBuilder(const AtomNetlist& netlist,
AtomLookup& netlist_lookup)
: netlist_(netlist)
, netlist_lookup_(netlist_lookup)
, netlist_clock_drivers_(find_netlist_logical_clock_drivers(netlist_)) {
//pass
}
std::unique_ptr<TimingGraph> TimingGraphBuilder::timing_graph(bool allow_dangling_combinational_nodes) {
build(allow_dangling_combinational_nodes);
opt_memory_layout();
VTR_ASSERT(tg_);
tg_->validate();
return std::move(tg_);
}
void TimingGraphBuilder::build(bool allow_dangling_combinational_nodes) {
tg_ = std::make_unique<tatum::TimingGraph>();
// Optionally allow dangling combinational nodes.
// Set by `--allow_dangling_combinational_nodes on`. Default value is false
tg_->set_allow_dangling_combinational_nodes(allow_dangling_combinational_nodes);
for (AtomBlockId blk : netlist_.blocks()) {
AtomBlockType blk_type = netlist_.block_type(blk);
if (blk_type == AtomBlockType::INPAD || blk_type == AtomBlockType::OUTPAD) {
add_io_to_timing_graph(blk);
} else if (blk_type == AtomBlockType::BLOCK) {
add_block_to_timing_graph(blk);
} else {
VPR_FATAL_ERROR(VPR_ERROR_TIMING, "Unrecognized atom block type while constructing timing graph");
}
}
for (AtomNetId net : netlist_.nets()) {
add_net_to_timing_graph(net);
}
fix_comb_loops();
tg_->levelize();
}
void TimingGraphBuilder::opt_memory_layout() {
auto id_map = tg_->optimize_layout();
remap_ids(id_map);
}
void TimingGraphBuilder::add_io_to_timing_graph(const AtomBlockId blk) {
NodeType node_type;
AtomPinId pin;
if (netlist_.block_type(blk) == AtomBlockType::INPAD) {
node_type = NodeType::SOURCE;
if (netlist_.block_pins(blk).size() == 1) {
pin = *netlist_.block_pins(blk).begin();
} else {
//Un-swept disconnected input
VTR_ASSERT(netlist_.block_pins(blk).size() == 0);
return;
}
} else {
VTR_ASSERT(netlist_.block_type(blk) == AtomBlockType::OUTPAD);
node_type = NodeType::SINK;
if (netlist_.block_pins(blk).size() == 1) {
pin = *netlist_.block_pins(blk).begin();
} else {
//Un-swept disconnected output
VTR_ASSERT(netlist_.block_pins(blk).size() == 0);
return;
}
}
NodeId tnode = tg_->add_node(node_type);
netlist_lookup_.set_atom_pin_tnode(pin, tnode);
}
void TimingGraphBuilder::add_block_to_timing_graph(const AtomBlockId blk) {
std::set<std::string> output_ports_used_as_combinational_sinks;
//Create the input pins
for (AtomPinId input_pin : netlist_.block_input_pins(blk)) {
AtomPortId input_port = netlist_.pin_port(input_pin);
//Inspect the port model to determine pin type
const t_model_ports* model_port = netlist_.port_model(input_port);
NodeId tnode;
VTR_ASSERT(!model_port->is_clock);
if (model_port->clock.empty()) {
//No clock => combinational input
tnode = tg_->add_node(NodeType::IPIN);
//A combinational pin is really both internal and external, mark it internal here
//and external in the default case below
netlist_lookup_.set_atom_pin_tnode(input_pin, tnode, BlockTnode::INTERNAL);
} else {
tnode = tg_->add_node(NodeType::SINK);
if (!model_port->combinational_sink_ports.empty()) {
//There is an internal combinational connection starting at this sequential input
//Create the internal source
NodeId internal_tnode = tg_->add_node(NodeType::SOURCE);
netlist_lookup_.set_atom_pin_tnode(input_pin, internal_tnode, BlockTnode::INTERNAL);
}
}
//Record any output port which will be used as a combinational sink
output_ports_used_as_combinational_sinks.insert(model_port->combinational_sink_ports.begin(),
model_port->combinational_sink_ports.end());
//Save the pin to external tnode mapping
netlist_lookup_.set_atom_pin_tnode(input_pin, tnode);
}
//Create the clock pins
for (AtomPinId clock_pin : netlist_.block_clock_pins(blk)) {
AtomPortId clock_port = netlist_.pin_port(clock_pin);
//Inspect the port model to determine pin type
const t_model_ports* model_port = netlist_.port_model(clock_port);
VTR_ASSERT(model_port->is_clock);
VTR_ASSERT(model_port->clock.empty());
NodeId tnode = tg_->add_node(NodeType::CPIN);
netlist_lookup_.set_atom_pin_tnode(clock_pin, tnode);
}
//Create the output pins
std::set<tatum::NodeId> clock_generator_tnodes;
for (AtomPinId output_pin : netlist_.block_output_pins(blk)) {
AtomPortId output_port = netlist_.pin_port(output_pin);
//Inspect the port model to determine pin type
const t_model_ports* model_port = netlist_.port_model(output_port);
NodeId tnode;
if (is_netlist_clock_source(output_pin)) {
//A generated clock source
tnode = tg_->add_node(NodeType::SOURCE);
clock_generator_tnodes.insert(tnode);
if (!model_port->is_clock) {
//An implicit clock source, possibly clock derived from data
AtomNetId clock_net = netlist_.pin_net(output_pin);
VTR_LOG_WARN("Inferred implicit clock source %s for netlist clock %s (possibly data used as clock)\n",
netlist_.pin_name(output_pin).c_str(), netlist_.net_name(clock_net).c_str());
//This type of situation often requires cutting paths between the implicit clock source and
//it's inputs which can cause dangling combinational nodes. Do not error if this occurs.
tg_->set_allow_dangling_combinational_nodes(true);
}
} else {
VTR_ASSERT_MSG(!model_port->is_clock, "Primitive data output can not be a clock generator");
if (model_port->clock.empty()) {
//No clock => combinational output
tnode = tg_->add_node(NodeType::OPIN);
//A combinational pin is really both internal and external, mark it internal here
//and external in the default case below
netlist_lookup_.set_atom_pin_tnode(output_pin, tnode, BlockTnode::INTERNAL);
} else {
VTR_ASSERT(!model_port->clock.empty());
//Has an associated clock => sequential output
tnode = tg_->add_node(NodeType::SOURCE);
if (output_ports_used_as_combinational_sinks.count(model_port->name)) {
//There is a combinational path within the primitive terminating at this sequential output
//Create the internal sink node
NodeId internal_tnode = tg_->add_node(NodeType::SINK);
netlist_lookup_.set_atom_pin_tnode(output_pin, internal_tnode, BlockTnode::INTERNAL);
}
}
}
netlist_lookup_.set_atom_pin_tnode(output_pin, tnode);
}
//Connect the clock pins to the sources and sinks
for (AtomPinId pin : netlist_.block_pins(blk)) {
for (auto blk_tnode_type : {BlockTnode::EXTERNAL, BlockTnode::INTERNAL}) {
NodeId tnode = netlist_lookup_.atom_pin_tnode(pin, blk_tnode_type);
if (!tnode) continue;
if (clock_generator_tnodes.count(tnode)) continue; //Clock sources don't have incomming clock pin connections
auto node_type = tg_->node_type(tnode);
if (node_type == NodeType::SOURCE || node_type == NodeType::SINK) {
//Look-up the clock name on the port model
AtomPortId port = netlist_.pin_port(pin);
const t_model_ports* model_port = netlist_.port_model(port);
VTR_ASSERT_MSG(!model_port->clock.empty(), "Sequential pins must have a clock");
//Find the clock pin in the netlist
AtomPortId clk_port = netlist_.find_port(blk, model_port->clock);
VTR_ASSERT(clk_port);
VTR_ASSERT_MSG(netlist_.port_width(clk_port) == 1, "Primitive clock ports can only contain one pin");
AtomPinId clk_pin = netlist_.port_pin(clk_port, 0);
VTR_ASSERT(clk_pin);
//Convert the pin to it's tnode
NodeId clk_tnode = netlist_lookup_.atom_pin_tnode(clk_pin);
tatum::EdgeType type;
if (node_type == NodeType::SOURCE) {
type = tatum::EdgeType::PRIMITIVE_CLOCK_LAUNCH;
} else {
VTR_ASSERT(node_type == NodeType::SINK);
type = tatum::EdgeType::PRIMITIVE_CLOCK_CAPTURE;
}
//Add the edge from the clock to the source/sink
tg_->add_edge(type, clk_tnode, tnode);
}
}
}
//Connect the combinational edges from input pins
for (AtomPinId src_pin : netlist_.block_input_pins(blk)) {
//Combinational edges go between IPINs and OPINs for combinational blocks
//and between the internal SOURCEs and SINKS for sequential blocks
//
//We have already marked all internal SOURCE/SINK nodes internal, and all IPIN/OPIN nodes as internal
//so we only need to look at tnode's of that class
NodeId src_tnode = netlist_lookup_.atom_pin_tnode(src_pin, BlockTnode::INTERNAL);
if (src_tnode) {
auto src_type = tg_->node_type(src_tnode);
//Look-up the combinationally connected sink ports name on the port model
AtomPortId src_port = netlist_.pin_port(src_pin);
const t_model_ports* model_port = netlist_.port_model(src_port);
for (const std::string& sink_port_name : model_port->combinational_sink_ports) {
AtomPortId sink_port = netlist_.find_port(blk, sink_port_name);
if (!sink_port) continue; //Port may not be connected
//We now need to create edges between the source pin, and all the pins in the
//output port
for (AtomPinId sink_pin : netlist_.port_pins(sink_port)) {
//Get the tnode of the sink
NodeId sink_tnode = netlist_lookup_.atom_pin_tnode(sink_pin, BlockTnode::INTERNAL);
if (!sink_tnode) {
//No tnode found, either a combinational clock generator or an error
//Try again looking for an external tnode
sink_tnode = netlist_lookup_.atom_pin_tnode(sink_pin, BlockTnode::EXTERNAL);
//Is the sink a clock generator?
if (sink_tnode && clock_generator_tnodes.count(sink_tnode)) {
//Do not create the edge
VTR_LOG_WARN("Timing edge from %s to %s will not be created since %s has been identified as a clock generator\n",
netlist_.pin_name(src_pin).c_str(), netlist_.pin_name(sink_pin).c_str(), netlist_.pin_name(sink_pin).c_str());
} else {
//Unknown
VPR_FATAL_ERROR(VPR_ERROR_TIMING, "Unable to find matching sink tnode for timing edge from %s to %s",
netlist_.pin_name(src_pin).c_str(), netlist_.pin_name(src_pin).c_str());
}
} else {
//Valid tnode create the edge
auto sink_type = tg_->node_type(sink_tnode);
VTR_ASSERT_MSG((src_type == NodeType::IPIN && sink_type == NodeType::OPIN)
|| (src_type == NodeType::SOURCE && sink_type == NodeType::SINK)
|| (src_type == NodeType::SOURCE && sink_type == NodeType::OPIN)
|| (src_type == NodeType::IPIN && sink_type == NodeType::SINK),
"Internal primitive combinational edges must be between {IPIN, SOURCE} and {OPIN, SINK}");
//Add the edge between the pins
tg_->add_edge(tatum::EdgeType::PRIMITIVE_COMBINATIONAL, src_tnode, sink_tnode);
}
}
}
}
}
//Connect the combinational edges from clock pins
//
//These are typically used to represent clock buffers
for (AtomPinId src_clock_pin : netlist_.block_clock_pins(blk)) {
NodeId src_tnode = netlist_lookup_.atom_pin_tnode(src_clock_pin, BlockTnode::EXTERNAL);
if (!src_tnode) continue;
//Look-up the combinationally connected sink ports name on the port model
AtomPortId src_port = netlist_.pin_port(src_clock_pin);
const t_model_ports* model_port = netlist_.port_model(src_port);
for (const std::string& sink_port_name : model_port->combinational_sink_ports) {
AtomPortId sink_port = netlist_.find_port(blk, sink_port_name);
if (!sink_port) continue; //Port may not be connected
//We now need to create edges between the source pin, and all the pins in the
//output port
for (AtomPinId sink_pin : netlist_.port_pins(sink_port)) {
//Get the tnode of the sink
NodeId sink_tnode = netlist_lookup_.atom_pin_tnode(sink_pin, BlockTnode::EXTERNAL);
tg_->add_edge(tatum::EdgeType::PRIMITIVE_COMBINATIONAL, src_tnode, sink_tnode);
VTR_LOG("Adding edge from '%s' (%zu) -> '%s' (%zu)\n", netlist_.pin_name(src_clock_pin).c_str(), size_t(src_tnode), netlist_.pin_name(sink_pin).c_str(), size_t(sink_tnode));
}
}
}
}
void TimingGraphBuilder::add_net_to_timing_graph(const AtomNetId net) {
//Create edges from the driver to sink tnodes
AtomPinId driver_pin = netlist_.net_driver(net);
if (!driver_pin) {
//Undriven nets have no timing dependencies, and hence no edges
VTR_LOG_WARN("Net %s has no driver and will be ignored for timing purposes\n", netlist_.net_name(net).c_str());
return;
}
NodeId driver_tnode = netlist_lookup_.atom_pin_tnode(driver_pin);
VTR_ASSERT(driver_tnode);
for (AtomPinId sink_pin : netlist_.net_sinks(net)) {
NodeId sink_tnode = netlist_lookup_.atom_pin_tnode(sink_pin);
VTR_ASSERT(sink_tnode);
tg_->add_edge(tatum::EdgeType::INTERCONNECT, driver_tnode, sink_tnode);
}
}
void TimingGraphBuilder::fix_comb_loops() {
auto sccs = tatum::identify_combinational_loops(*tg_);
//For non-simple loops (i.e. SCCs with multiple loops) we may need to break
//multiple edges, so repeatedly break edges until there are no SCCs left
while (!sccs.empty()) {
VTR_LOG_WARN("Detected %zu strongly connected component(s) forming combinational loop(s) in timing graph\n", sccs.size());
for (const auto& scc : sccs) {
EdgeId edge_to_break = find_scc_edge_to_break(scc);
VTR_ASSERT(edge_to_break);
tg_->disable_edge(edge_to_break);
}
sccs = tatum::identify_combinational_loops(*tg_);
}
}
tatum::EdgeId TimingGraphBuilder::find_scc_edge_to_break(std::vector<tatum::NodeId> scc) {
//Find an edge which is part of the SCC and remove it from the graph,
//in the hope of breaking the SCC
std::set<tatum::NodeId> scc_set(scc.begin(), scc.end());
for (tatum::NodeId src_node : scc) {
AtomPinId src_pin = netlist_lookup_.tnode_atom_pin(src_node);
for (tatum::EdgeId edge : tg_->node_out_edges(src_node)) {
if (tg_->edge_disabled(edge)) continue;
tatum::NodeId sink_node = tg_->edge_sink_node(edge);
AtomPinId sink_pin = netlist_lookup_.tnode_atom_pin(sink_node);
if (scc_set.count(sink_node)) {
VTR_LOG_WARN("Arbitrarily disabling timing graph edge %zu (%s -> %s) to break combinational loop\n",
edge, netlist_.pin_name(src_pin).c_str(), netlist_.pin_name(sink_pin).c_str());
return edge;
}
}
}
return tatum::EdgeId::INVALID();
}
void TimingGraphBuilder::remap_ids(const tatum::GraphIdMaps& id_mapping) {
//Update the pin-tnode mapping
vtr::linear_map<tatum::NodeId, AtomPinId> new_tnode_atom_pin;
for (auto kv : netlist_lookup_.tnode_atom_pins()) {
tatum::NodeId old_tnode = kv.first;
AtomPinId pin = kv.second;
tatum::NodeId new_tnode = id_mapping.node_id_map[old_tnode];
new_tnode_atom_pin.emplace(new_tnode, pin);
}
for (auto kv : new_tnode_atom_pin) {
tatum::NodeId tnode = kv.first;
AtomPinId pin = kv.second;
netlist_lookup_.set_atom_pin_tnode(pin, tnode);
}
}
bool TimingGraphBuilder::is_netlist_clock_source(const AtomPinId pin) const {
return netlist_clock_drivers_.count(pin);
}