text: #include "slack_evaluation.h"
  
  #include "tatum/TimingGraph.hpp"
  #include "timing_util.h"
  #include "vpr_error.h"
  #include "atom_netlist.h"
  #include "vtr_log.h"
  
  #if defined(VPR_USE_TBB)
  #    include <tbb/task_group.h>
  #    include <tbb/parallel_for_each.h>
  #endif
  /*
   * SetupSlackCrit
   */
  
  SetupSlackCrit::SetupSlackCrit(const AtomNetlist& netlist, const AtomLookup& netlist_lookup)
      : netlist_(netlist)
      , netlist_lookup_(netlist_lookup)
      , pin_slacks_(netlist_.pins().size(), NAN)
      , pin_criticalities_(netlist_.pins().size(), NAN) {
      //pass
  }
  
  //Returns the worst (least) slack of connections through the specified pin
  float SetupSlackCrit::setup_pin_slack(AtomPinId pin) const { return pin_slacks_[pin]; }
  
  //Returns the worst (maximum) criticality of connections through the specified pin.
  //  Criticality (in [0., 1.]) represents how timing-critical something is,
  //  0. is non-critical and 1. is most-critical.
  float SetupSlackCrit::setup_pin_criticality(AtomPinId pin) const { return pin_criticalities_[pin]; }
  
  void SetupSlackCrit::update_slacks_and_criticalities(const tatum::TimingGraph& timing_graph, const tatum::SetupTimingAnalyzer& analyzer) {
  #if defined(VPR_USE_TBB)
      tbb::task_group g;
      g.run([&] { update_slacks(analyzer); });
      g.run([&] { update_criticalities(timing_graph, analyzer); });
      g.wait();
  #else
      update_slacks(analyzer);
      update_criticalities(timing_graph, analyzer);
  #endif
  }
  
  void SetupSlackCrit::update_slacks(const tatum::SetupTimingAnalyzer& analyzer) {
      auto pins = netlist_.pins();
  #if defined(VPR_USE_TBB)
      tbb::parallel_for_each(pins.begin(), pins.end(), [&, this](auto pin) {
          this->update_pin_slack(pin, analyzer);
      });
  #else
      for (auto pin : pins) {
          update_pin_slack(pin, analyzer);
      }
  #endif
  }
  
  void SetupSlackCrit::update_pin_slack(const AtomPinId pin, const tatum::SetupTimingAnalyzer& analyzer) {
      //Find the timing node associated with the pin
      tatum::NodeId node = netlist_lookup_.atom_pin_tnode(pin);
      VTR_ASSERT(node);
  
      //Find the worst (least) slack at this node
      auto tags = analyzer.setup_slacks(node);
      auto min_tag_iter = find_minimum_tag(tags);
      if (min_tag_iter != tags.end()) {
          pin_slacks_[pin] = min_tag_iter->time().value();
      } else {
          //No tags (e.g. driven by constant generator)
          pin_slacks_[pin] = std::numeric_limits<float>::infinity();
      }
  }
  
  void SetupSlackCrit::update_criticalities(const tatum::TimingGraph& timing_graph, const tatum::SetupTimingAnalyzer& analyzer) {
      //Record the maximum required time, and wost slack per domain pair
      std::map<DomainPair, float> max_req;
      std::map<DomainPair, float> worst_slack;
      for (tatum::NodeId node : timing_graph.logical_outputs()) {
          for (auto& tag : analyzer.setup_tags(node, tatum::TagType::DATA_REQUIRED)) {
              auto domain_pair = DomainPair(tag.launch_clock_domain(), tag.capture_clock_domain());
  
              float req = tag.time().value();
              if (!max_req.count(domain_pair) || max_req[domain_pair] < req) {
                  max_req[domain_pair] = req;
              }
          }
  
          for (auto& tag : analyzer.setup_slacks(node)) {
              auto domain_pair = DomainPair(tag.launch_clock_domain(), tag.capture_clock_domain());
  
              float slack = tag.time().value();
              if (!worst_slack.count(domain_pair) || slack < worst_slack[domain_pair]) {
                  worst_slack[domain_pair] = slack;
              }
          }
      }
  
      //Update the criticalities of each pin
      auto pins = netlist_.pins();
  #if defined(VPR_USE_TBB)
      tbb::parallel_for_each(pins.begin(), pins.end(), [&, this](auto pin) {
          this->pin_criticalities_[pin] = this->calc_pin_criticality(pin, analyzer, max_req, worst_slack);
      });
  #else
      for (auto pin : pins) {
          pin_criticalities_[pin] = calc_pin_criticality(pin, analyzer, max_req, worst_slack);
      }
  #endif
  }
  
  float SetupSlackCrit::calc_pin_criticality(AtomPinId pin,
                                             const tatum::SetupTimingAnalyzer& analyzer,
                                             const std::map<DomainPair, float>& max_req,
                                             const std::map<DomainPair, float>& worst_slack) {
      tatum::NodeId node = netlist_lookup_.atom_pin_tnode(pin);
      VTR_ASSERT(node);
  
      //Calculate maximum criticality over all domains
      return calc_relaxed_criticality(max_req, worst_slack, analyzer.setup_slacks(node));
  }
  
  /*
   * HoldSlackCrit
   */
  
  HoldSlackCrit::HoldSlackCrit(const AtomNetlist& netlist, const AtomLookup& netlist_lookup)
      : netlist_(netlist)
      , netlist_lookup_(netlist_lookup)
      , pin_slacks_(netlist_.pins().size(), NAN)
      , pin_criticalities_(netlist_.pins().size(), NAN) {
      //pass
  }
  
  //Returns the worst (least) slack of connections through the specified pin
  float HoldSlackCrit::hold_pin_slack(AtomPinId pin) const { return pin_slacks_[pin]; }
  
  //Returns the worst (maximum) criticality of connections through the specified pin.
  //  Criticality (in [0., 1.]) represents how timing-critical something is,
  //  0. is non-critical and 1. is most-critical.
  float HoldSlackCrit::hold_pin_criticality(AtomPinId pin) const { return pin_criticalities_[pin]; }
  
  void HoldSlackCrit::update_slacks_and_criticalities(const tatum::TimingGraph& timing_graph, const tatum::HoldTimingAnalyzer& analyzer) {
  #if defined(VPR_USE_TBB)
      tbb::task_group g;
      g.run([&] { update_slacks(analyzer); });
      g.run([&] { update_criticalities(timing_graph, analyzer); });
      g.wait();
  #else
      update_slacks(analyzer);
      update_criticalities(timing_graph, analyzer);
  #endif
  }
  
  void HoldSlackCrit::update_slacks(const tatum::HoldTimingAnalyzer& analyzer) {
      for (AtomPinId pin : netlist_.pins()) {
          update_pin_slack(pin, analyzer);
      }
  }
  
  void HoldSlackCrit::update_pin_slack(const AtomPinId pin, const tatum::HoldTimingAnalyzer& analyzer) {
      //Find the timing node associated with the pin
      tatum::NodeId node = netlist_lookup_.atom_pin_tnode(pin);
      VTR_ASSERT(node);
  
      //Find the worst (least) slack at this node
      auto tags = analyzer.hold_slacks(node);
      auto min_tag_iter = find_minimum_tag(tags);
      if (min_tag_iter != tags.end()) {
          pin_slacks_[pin] = min_tag_iter->time().value();
      } else {
          //No tags (e.g. driven by constant generator)
          pin_slacks_[pin] = std::numeric_limits<float>::infinity();
      }
  }
  
  void HoldSlackCrit::update_criticalities(const tatum::TimingGraph& timing_graph, const tatum::HoldTimingAnalyzer& analyzer) {
      //TODO: this calculates a simple shifted and scaled criticality, not clear if this is the
      //right approach (e.g. should we use a more intellegent method like the one used by setup slack?)
      float worst_slack = std::numeric_limits<float>::infinity();
      float best_slack = -std::numeric_limits<float>::infinity();
      for (tatum::NodeId node : timing_graph.nodes()) {
          for (auto& tag : analyzer.hold_slacks(node)) {
              float slack = tag.time().value();
              worst_slack = std::min(worst_slack, slack);
              best_slack = std::max(best_slack, slack);
          }
      }
  
      //Calculate the transformation from slack to criticality,
      //we scale and shift the result so the worst_slack takes on criticalty 1.0
      //while the best slack takes on criticality 0.0
      float scale = 1. / std::abs(best_slack - worst_slack);
      float shift = -worst_slack;
  
      //Update the criticalities of each pin
      auto pins = netlist_.pins();
  #if defined(VPR_USE_TBB)
      tbb::parallel_for_each(pins.begin(), pins.end(), [&, this](auto pin) {
          this->pin_criticalities_[pin] = this->calc_pin_criticality(pin, analyzer, scale, shift);
      });
  #else
      for (auto pin : pins) {
          pin_criticalities_[pin] = calc_pin_criticality(pin, analyzer, scale, shift);
      }
  #endif
  }
  
  float HoldSlackCrit::calc_pin_criticality(AtomPinId pin,
                                            const tatum::HoldTimingAnalyzer& analyzer,
                                            const float scale,
                                            const float shift) {
      tatum::NodeId node = netlist_lookup_.atom_pin_tnode(pin);
      VTR_ASSERT(node);
  
      float criticality = 0.;
  
      for (auto tag : analyzer.hold_slacks(node)) {
          float slack = tag.time().value();
  
          float tag_criticality = 1. - scale * (slack + shift);
  
          criticality = std::max(criticality, tag_criticality);
      }
  
      VTR_ASSERT(criticality >= 0.);
      VTR_ASSERT(criticality <= 1.);
  
      return criticality;
  }
  