711 lines
29 KiB
C++
711 lines
29 KiB
C++
#include <map>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <vector>
|
|
|
|
#include "tatum/util/tatum_math.hpp"
|
|
#include "tatum/util/OsFormatGuard.hpp"
|
|
#include "tatum/error.hpp"
|
|
#include "tatum/TimingReporter.hpp"
|
|
#include "tatum/TimingGraph.hpp"
|
|
#include "tatum/TimingConstraints.hpp"
|
|
|
|
namespace tatum { namespace detail {
|
|
|
|
float convert_to_printable_units(float value, float unit_scale) {
|
|
return value / unit_scale;
|
|
}
|
|
|
|
std::string to_printable_string(tatum::Time val, float unit_scale, size_t precision) {
|
|
std::stringstream ss;
|
|
if(!std::signbit(val.value())) ss << " "; //Pad possitive values so they align with negative prefixed with -
|
|
ss << std::fixed << std::setprecision(precision) << convert_to_printable_units(val.value(), unit_scale);
|
|
|
|
return ss.str();
|
|
}
|
|
|
|
void ReportTimingPathHelper::update_print_path(std::ostream& os, std::string point, tatum::Time path) {
|
|
|
|
tatum::Time incr = path - prev_path_;
|
|
|
|
print_path_line(os, point, to_printable_string(incr, unit_scale_, precision_), to_printable_string(path, unit_scale_, precision_));
|
|
|
|
prev_path_ = path;
|
|
}
|
|
|
|
void ReportTimingPathHelper::update_print_path_no_incr(std::ostream& os, std::string point, tatum::Time path) {
|
|
print_path_line(os, point, "", to_printable_string(path, unit_scale_, precision_));
|
|
|
|
prev_path_ = path;
|
|
}
|
|
|
|
void ReportTimingPathHelper::reset_path() {
|
|
prev_path_ = tatum::Time(0.);
|
|
}
|
|
|
|
void ReportTimingPathHelper::print_path_line_no_incr(std::ostream& os, std::string point, tatum::Time path) const {
|
|
print_path_line(os, point, "", to_printable_string(path, unit_scale_, precision_));
|
|
}
|
|
|
|
void ReportTimingPathHelper::print_path_line(std::ostream& os, std::string point, std::string incr, std::string path) const {
|
|
os << std::setw(point_width_) << std::left << point;
|
|
os << std::setw(incr_width_) << std::right << incr;
|
|
os << std::setw(path_width_) << std::right << path;
|
|
os << "\n";
|
|
}
|
|
|
|
void ReportTimingPathHelper::print_divider(std::ostream& os) const {
|
|
size_t cnt = point_width_ + incr_width_ + path_width_;
|
|
for(size_t i = 0; i < cnt; ++i) {
|
|
os << "-";
|
|
}
|
|
os << "\n";
|
|
}
|
|
|
|
}} //namespace
|
|
|
|
namespace tatum {
|
|
|
|
/*
|
|
* Public member functions
|
|
*/
|
|
|
|
TimingReporter::TimingReporter(const TimingGraphNameResolver& name_resolver,
|
|
const TimingGraph& timing_graph,
|
|
const TimingConstraints& timing_constraints,
|
|
float unit_scale,
|
|
size_t precision)
|
|
: name_resolver_(name_resolver)
|
|
, timing_graph_(timing_graph)
|
|
, timing_constraints_(timing_constraints)
|
|
, unit_scale_(unit_scale)
|
|
, precision_(precision) {
|
|
//pass
|
|
}
|
|
|
|
void TimingReporter::report_timing_setup(std::string filename,
|
|
const SetupTimingAnalyzer& setup_analyzer,
|
|
size_t npaths) const {
|
|
std::ofstream os(filename);
|
|
report_timing_setup(os, setup_analyzer, npaths);
|
|
}
|
|
|
|
void TimingReporter::report_timing_setup(std::ostream& os,
|
|
const SetupTimingAnalyzer& setup_analyzer,
|
|
size_t npaths) const {
|
|
auto paths = path_collector_.collect_worst_setup_timing_paths(timing_graph_, setup_analyzer, npaths);
|
|
|
|
report_timing(os, paths);
|
|
}
|
|
|
|
void TimingReporter::report_timing_hold(std::string filename,
|
|
const HoldTimingAnalyzer& hold_analyzer,
|
|
size_t npaths) const {
|
|
std::ofstream os(filename);
|
|
report_timing_hold(os, hold_analyzer, npaths);
|
|
}
|
|
|
|
void TimingReporter::report_timing_hold(std::ostream& os,
|
|
const HoldTimingAnalyzer& hold_analyzer,
|
|
size_t npaths) const {
|
|
auto paths = path_collector_.collect_worst_hold_timing_paths(timing_graph_, hold_analyzer, npaths);
|
|
|
|
report_timing(os, paths);
|
|
}
|
|
|
|
void TimingReporter::report_skew_setup(std::string filename,
|
|
const SetupTimingAnalyzer& setup_analyzer,
|
|
size_t nworst) const {
|
|
std::ofstream os(filename);
|
|
report_skew_setup(os, setup_analyzer, nworst);
|
|
}
|
|
|
|
void TimingReporter::report_skew_setup(std::ostream& os,
|
|
const SetupTimingAnalyzer& setup_analyzer,
|
|
size_t nworst) const {
|
|
auto paths = path_collector_.collect_worst_setup_skew_paths(timing_graph_, timing_constraints_, setup_analyzer, nworst);
|
|
|
|
os << "#Clock skew for setup timing startpoint/endpoint\n";
|
|
os << "\n";
|
|
report_skew(os, paths, TimingType::SETUP);
|
|
os << "#End of clock skew for setup timing startpoint/endpoint report\n";
|
|
}
|
|
|
|
void TimingReporter::report_skew_hold(std::string filename,
|
|
const HoldTimingAnalyzer& hold_analyzer,
|
|
size_t nworst) const {
|
|
std::ofstream os(filename);
|
|
report_skew_hold(os, hold_analyzer, nworst);
|
|
}
|
|
|
|
void TimingReporter::report_skew_hold(std::ostream& os,
|
|
const HoldTimingAnalyzer& hold_analyzer,
|
|
size_t nworst) const {
|
|
auto paths = path_collector_.collect_worst_hold_skew_paths(timing_graph_, timing_constraints_, hold_analyzer, nworst);
|
|
|
|
os << "#Clock skew for hold timing startpoint/endpoint\n";
|
|
os << "\n";
|
|
report_skew(os, paths, TimingType::HOLD);
|
|
os << "#End of clock skew for hold timing startpoint/endpoint report\n";
|
|
}
|
|
|
|
void TimingReporter::report_unconstrained_setup(std::string filename,
|
|
const tatum::SetupTimingAnalyzer& setup_analyzer) const {
|
|
std::ofstream os(filename);
|
|
report_unconstrained_setup(os, setup_analyzer);
|
|
}
|
|
|
|
void TimingReporter::report_unconstrained_setup(std::ostream& os,
|
|
const tatum::SetupTimingAnalyzer& setup_analyzer) const {
|
|
detail::SetupTagRetriever tag_retriever(setup_analyzer);
|
|
|
|
os << "#Unconstrained setup timing startpoint/endpoint\n";
|
|
os << "\n";
|
|
os << "timing_node_id node_type node_name\n";
|
|
os << "-------------- --------- ---------\n";
|
|
report_unconstrained(os, NodeType::SOURCE, tag_retriever);
|
|
report_unconstrained(os, NodeType::SINK, tag_retriever);
|
|
os << "\n";
|
|
os << "#End of unconstrained setup startpoint/endpoint report\n";
|
|
}
|
|
|
|
void TimingReporter::report_unconstrained_hold(std::string filename,
|
|
const tatum::HoldTimingAnalyzer& hold_analyzer) const {
|
|
std::ofstream os(filename);
|
|
report_unconstrained_hold(os, hold_analyzer);
|
|
}
|
|
|
|
void TimingReporter::report_unconstrained_hold(std::ostream& os,
|
|
const tatum::HoldTimingAnalyzer& hold_analyzer) const {
|
|
detail::HoldTagRetriever tag_retriever(hold_analyzer);
|
|
|
|
os << "#Unconstrained hold timing startpoint/endpoint\n";
|
|
os << "\n";
|
|
os << "timing_node_id node_type node_name\n";
|
|
os << "-------------- --------- ---------\n";
|
|
report_unconstrained(os, NodeType::SOURCE, tag_retriever);
|
|
report_unconstrained(os, NodeType::SINK, tag_retriever);
|
|
os << "\n";
|
|
os << "#End of unconstrained hold startpoint/endpoint report\n";
|
|
}
|
|
|
|
/*
|
|
* Private member functions
|
|
*/
|
|
|
|
void TimingReporter::report_timing(std::ostream& os,
|
|
const std::vector<TimingPath>& paths) const {
|
|
tatum::OsFormatGuard flag_guard(os);
|
|
|
|
os << "#Timing report of worst " << paths.size() << " path(s)\n";
|
|
os << "# Unit scale: " << std::setprecision(0) << std::scientific << unit_scale_ << " seconds\n";
|
|
os << "# Output precision: " << precision_ << "\n";
|
|
os << "\n";
|
|
|
|
size_t i = 0;
|
|
for(const auto& path : paths) {
|
|
os << "#Path " << ++i << "\n";
|
|
report_timing_path(os, path);
|
|
os << "\n";
|
|
}
|
|
|
|
os << "#End of timing report\n";
|
|
}
|
|
|
|
void TimingReporter::report_timing_path(std::ostream& os, const TimingPath& timing_path) const {
|
|
std::string divider = "--------------------------------------------------------------------------------";
|
|
|
|
TimingPathInfo path_info = timing_path.path_info();
|
|
|
|
os << "Startpoint: " << name_resolver_.node_name(path_info.startpoint())
|
|
<< " (" << name_resolver_.node_type_name(path_info.startpoint())
|
|
<< " clocked by " << timing_constraints_.clock_domain_name(path_info.launch_domain())
|
|
<< ")\n";
|
|
os << "Endpoint : " << name_resolver_.node_name(path_info.endpoint())
|
|
<< " (" << name_resolver_.node_type_name(path_info.endpoint())
|
|
<< " clocked by " << timing_constraints_.clock_domain_name(path_info.capture_domain())
|
|
<< ")\n";
|
|
|
|
if(path_info.type() == TimingType::SETUP) {
|
|
os << "Path Type : setup" << "\n";
|
|
} else {
|
|
TATUM_ASSERT_MSG(path_info.type() == TimingType::HOLD, "Expected path type SETUP or HOLD");
|
|
os << "Path Type : hold" << "\n";
|
|
}
|
|
|
|
os << "\n";
|
|
|
|
size_t point_print_width = estimate_point_print_width(timing_path);
|
|
|
|
//Helper to track path state and output formatting
|
|
detail::ReportTimingPathHelper path_helper(unit_scale_, precision_, point_print_width);
|
|
path_helper.print_path_line(os, "Point", " Incr", " Path");
|
|
path_helper.print_divider(os);
|
|
|
|
|
|
//Launch path
|
|
Time arr_time;
|
|
Time arr_path;
|
|
{
|
|
path_helper.reset_path();
|
|
|
|
arr_path = report_timing_clock_launch_subpath(os, path_helper, timing_path.clock_launch_path(), path_info.launch_domain(), path_info.type());
|
|
|
|
arr_path = report_timing_data_arrival_subpath(os, path_helper, timing_path.data_arrival_path(), path_info.launch_domain(), path_info.type(), arr_path);
|
|
|
|
{
|
|
//Final arrival time
|
|
const TimingPathElem& path_elem = *(--timing_path.data_arrival_path().elements().end());
|
|
|
|
TATUM_ASSERT(timing_graph_.node_type(path_elem.node()) == NodeType::SINK);
|
|
|
|
TATUM_ASSERT(path_elem.tag().type() == TagType::DATA_ARRIVAL);
|
|
arr_time = path_elem.tag().time();
|
|
path_helper.update_print_path_no_incr(os, "data arrival time", arr_time);
|
|
os << "\n";
|
|
}
|
|
|
|
//Sanity check the arrival time calculated by this timing report (i.e. path) and the one calculated by
|
|
//the analyzer (i.e. arr_time) agree
|
|
if(!nearly_equal(arr_time, arr_path)) {
|
|
os.flush();
|
|
std::stringstream ss;
|
|
ss << "Internal Error: analyzer arrival time (" << arr_time.value() << ")"
|
|
<< " differs from timing report path arrival time (" << arr_path.value() << ")"
|
|
<< " beyond tolerance";
|
|
throw tatum::Error(ss.str());
|
|
}
|
|
}
|
|
|
|
|
|
//Capture path (required time)
|
|
Time req_time;
|
|
Time req_path;
|
|
{
|
|
path_helper.reset_path();
|
|
|
|
req_path = report_timing_clock_capture_subpath(os, path_helper, timing_path.clock_capture_path(),
|
|
path_info.launch_domain(), path_info.capture_domain(),
|
|
path_info.endpoint(),
|
|
path_info.type());
|
|
|
|
const TimingPathElem& path_elem = timing_path.data_required_element();
|
|
|
|
req_path = report_timing_data_required_element(os, path_helper, path_elem,
|
|
path_info.capture_domain(), path_info.type(),
|
|
req_path);
|
|
//Final arrival time
|
|
req_time = path_elem.tag().time();
|
|
path_helper.update_print_path_no_incr(os, "data required time", req_time);
|
|
|
|
|
|
//Sanity check required time
|
|
if(!nearly_equal(req_time, req_path)) {
|
|
os.flush();
|
|
std::stringstream ss;
|
|
ss << "Internal Error: analyzer required time (" << req_time.value() << ")"
|
|
<< " differs from report_timing path required time (" << req_path.value() << ")"
|
|
<< " beyond tolerance";
|
|
throw tatum::Error(ss.str());
|
|
}
|
|
}
|
|
|
|
//Summary and slack
|
|
path_helper.print_divider(os);
|
|
if (path_info.type() == TimingType::SETUP) {
|
|
path_helper.print_path_line_no_incr(os, "data required time", req_time);
|
|
path_helper.print_path_line_no_incr(os, "data arrival time", -arr_time);
|
|
} else {
|
|
TATUM_ASSERT(path_info.type() == TimingType::HOLD);
|
|
path_helper.print_path_line_no_incr(os, "data required time", -req_time);
|
|
path_helper.print_path_line_no_incr(os, "data arrival time", arr_time);
|
|
}
|
|
path_helper.print_divider(os);
|
|
Time slack = timing_path.slack_tag().time();
|
|
if(slack.value() < 0. || std::signbit(slack.value())) {
|
|
path_helper.print_path_line_no_incr(os, "slack (VIOLATED)", slack);
|
|
} else {
|
|
path_helper.print_path_line_no_incr(os, "slack (MET)", slack);
|
|
}
|
|
os << "\n";
|
|
os.flush();
|
|
|
|
//Sanity check slack
|
|
Time path_slack;
|
|
if (path_info.type() == TimingType::SETUP) {
|
|
path_slack = req_path - arr_path;
|
|
} else {
|
|
TATUM_ASSERT(path_info.type() == TimingType::HOLD);
|
|
path_slack = arr_path - req_path;
|
|
}
|
|
if(!nearly_equal(slack, path_slack)) {
|
|
os.flush();
|
|
std::stringstream ss;
|
|
ss << "Internal Error: analyzer slack (" << slack << ")"
|
|
<< " differs from report_timing path slack (" << path_slack << ")"
|
|
<< " beyond tolerance";
|
|
throw tatum::Error(ss.str());
|
|
}
|
|
}
|
|
|
|
void TimingReporter::report_unconstrained(std::ostream& os, const NodeType type, const detail::TagRetriever& tag_retriever) const {
|
|
for(NodeId node : timing_graph_.nodes()) {
|
|
NodeType node_type = timing_graph_.node_type(node);
|
|
if(node_type == type) {
|
|
auto tags = tag_retriever.tags(node);
|
|
if(!is_constrained(node_type, tags)) {
|
|
os << size_t(node)
|
|
<< " " << name_resolver_.node_type_name(node)
|
|
<< " " << name_resolver_.node_name(node)
|
|
<< "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimingReporter::report_skew(std::ostream& os, const std::vector<SkewPath>& skew_paths, TimingType timing_type) const {
|
|
tatum::OsFormatGuard flag_guard(os);
|
|
|
|
int i = 1;
|
|
for (const auto& skew_path : skew_paths) {
|
|
os << "#Skew Path " << i << "\n";
|
|
report_skew_path(os, skew_path, timing_type);
|
|
os << "\n";
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void TimingReporter::report_skew_path(std::ostream& os, const SkewPath& skew_path, TimingType timing_type) const {
|
|
|
|
auto& launch_path = skew_path.clock_launch_path;
|
|
auto& capture_path = skew_path.clock_capture_path;
|
|
|
|
NodeId launch_node = skew_path.data_launch_node;
|
|
NodeId capture_node = skew_path.data_capture_node;
|
|
|
|
os << "Startpoint: " << name_resolver_.node_name(launch_node)
|
|
<< " (" << name_resolver_.node_type_name(launch_node)
|
|
<< " clocked by " << timing_constraints_.clock_domain_name(skew_path.launch_domain)
|
|
<< ")\n";
|
|
os << "Endpoint : " << name_resolver_.node_name(capture_node)
|
|
<< " (" << name_resolver_.node_type_name(capture_node)
|
|
<< " clocked by " << timing_constraints_.clock_domain_name(skew_path.capture_domain)
|
|
<< ")\n";
|
|
|
|
if(timing_type == TimingType::SETUP) {
|
|
os << "Path Type : setup" << "\n";
|
|
} else {
|
|
TATUM_ASSERT_MSG(timing_type == TimingType::HOLD, "Expected path type SETUP or HOLD");
|
|
os << "Path Type : hold" << "\n";
|
|
}
|
|
os << "\n";
|
|
|
|
std::string launch_name = name_resolver_.node_name(launch_node)
|
|
+ " ("
|
|
+ name_resolver_.node_type_name(launch_node)
|
|
+ ")";
|
|
std::string capture_name = name_resolver_.node_name(capture_node)
|
|
+ " ("
|
|
+ name_resolver_.node_type_name(capture_node)
|
|
+ ")";
|
|
|
|
size_t point_print_width = std::max(launch_name.size(), capture_name.size());
|
|
point_print_width = std::max(point_print_width, std::string("clock data capture (normalized)").size());
|
|
|
|
detail::ReportTimingPathHelper path_helper(unit_scale_, precision_, point_print_width);
|
|
|
|
|
|
path_helper.print_path_line(os, "Point", " Incr", " Path");
|
|
path_helper.print_divider(os);
|
|
|
|
Time data_launch_time = report_timing_clock_launch_subpath(os, path_helper, launch_path, skew_path.launch_domain, timing_type);
|
|
TATUM_ASSERT(nearly_equal(data_launch_time, skew_path.clock_launch_arrival));
|
|
|
|
path_helper.update_print_path_no_incr(os, "data launch", data_launch_time);
|
|
os << "\n";
|
|
|
|
os << "\n";
|
|
|
|
path_helper.reset_path();
|
|
|
|
Time data_capture_time = report_timing_clock_capture_subpath(os, path_helper, capture_path, skew_path.launch_domain, skew_path.capture_domain, skew_path.data_capture_node, timing_type);
|
|
TATUM_ASSERT(nearly_equal(data_capture_time, skew_path.clock_capture_arrival));
|
|
|
|
path_helper.update_print_path_no_incr(os, "data capture", data_capture_time);
|
|
path_helper.print_divider(os);
|
|
|
|
Time clock_constraint;
|
|
if (timing_type == TimingType::SETUP) {
|
|
clock_constraint = timing_constraints_.setup_constraint(skew_path.launch_domain, skew_path.capture_domain);
|
|
} else {
|
|
clock_constraint = timing_constraints_.hold_constraint(skew_path.launch_domain, skew_path.capture_domain);
|
|
}
|
|
path_helper.print_path_line_no_incr(os, "data capture", data_capture_time);
|
|
path_helper.print_path_line_no_incr(os, "clock constraint", -clock_constraint);
|
|
path_helper.print_path_line_no_incr(os, "data launch", -data_launch_time);
|
|
path_helper.print_divider(os);
|
|
|
|
Time skew = data_capture_time - clock_constraint - data_launch_time;
|
|
TATUM_ASSERT(nearly_equal(skew, skew_path.clock_skew));
|
|
path_helper.print_path_line_no_incr(os, "skew", skew);
|
|
}
|
|
|
|
Time TimingReporter::report_timing_clock_launch_subpath(std::ostream& os,
|
|
detail::ReportTimingPathHelper& path_helper,
|
|
const TimingSubPath& subpath,
|
|
DomainId domain,
|
|
TimingType timing_type) const {
|
|
Time path(0.);
|
|
|
|
{
|
|
//Launch clock origin
|
|
path += Time(0.);
|
|
std::string point = "clock " + timing_constraints_.clock_domain_name(domain) + " (rise edge)";
|
|
path_helper.update_print_path(os, point, path);
|
|
}
|
|
|
|
return report_timing_clock_subpath(os, path_helper, subpath, domain, timing_type, path, Time(0.));
|
|
}
|
|
|
|
Time TimingReporter::report_timing_clock_capture_subpath(std::ostream& os,
|
|
detail::ReportTimingPathHelper& path_helper,
|
|
const TimingSubPath& subpath,
|
|
DomainId launch_domain,
|
|
DomainId capture_domain,
|
|
NodeId capture_node,
|
|
TimingType timing_type) const {
|
|
Time path(0.);
|
|
Time offset(0.); //Offset required since the clock launch tag doesn't have the actual (potentially
|
|
//capture node dependent) constraint annotated. We determine this up front by
|
|
//looking up the actual constraint and add it into the subpath printed as an offset.
|
|
//Note that the required times 'path' values are correct it is only the increments
|
|
//which need to be offset when being reported (with the exception of the original
|
|
//rising edge).
|
|
{
|
|
//Launch clock origin
|
|
if (timing_type == TimingType::SETUP) {
|
|
path = timing_constraints_.setup_constraint(launch_domain, capture_domain, capture_node);
|
|
offset = path - timing_constraints_.setup_constraint(launch_domain, capture_domain);
|
|
} else {
|
|
TATUM_ASSERT(timing_type == TimingType::HOLD);
|
|
path = timing_constraints_.hold_constraint(launch_domain, capture_domain, capture_node);
|
|
offset = path - timing_constraints_.hold_constraint(launch_domain, capture_domain);
|
|
}
|
|
|
|
//os << "[offset]" << tatum::detail::to_printable_string(offset, 1e-9,3) << "\n";
|
|
std::string point = "clock " + timing_constraints_.clock_domain_name(capture_domain) + " (rise edge)";
|
|
path_helper.update_print_path(os, point, path);
|
|
}
|
|
|
|
path = report_timing_clock_subpath(os, path_helper, subpath, capture_domain, timing_type, path, offset);
|
|
|
|
{
|
|
//Uncertainty
|
|
Time uncertainty;
|
|
if(timing_type == TimingType::SETUP) {
|
|
uncertainty = -Time(timing_constraints_.setup_clock_uncertainty(launch_domain, capture_domain));
|
|
} else {
|
|
TATUM_ASSERT(timing_type == TimingType::HOLD);
|
|
uncertainty = Time(timing_constraints_.hold_clock_uncertainty(launch_domain, capture_domain));
|
|
}
|
|
path += uncertainty;
|
|
path_helper.update_print_path(os, "clock uncertainty", path + offset);
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
Time TimingReporter::report_timing_clock_subpath(std::ostream& os,
|
|
detail::ReportTimingPathHelper& path_helper,
|
|
const TimingSubPath& subpath,
|
|
DomainId domain,
|
|
TimingType timing_type,
|
|
Time path,
|
|
Time offset) const {
|
|
{
|
|
//Launch clock latency
|
|
Time latency;
|
|
if (timing_type == TimingType::SETUP) {
|
|
//Setup clock launches late
|
|
latency = Time(timing_constraints_.source_latency(domain, ArrivalType::LATE));
|
|
} else {
|
|
TATUM_ASSERT(timing_type == TimingType::HOLD);
|
|
//Hold clock launches early
|
|
latency = Time(timing_constraints_.source_latency(domain, ArrivalType::EARLY));
|
|
}
|
|
path += latency;
|
|
std::string point = "clock source latency";
|
|
path_helper.update_print_path(os, point, path);
|
|
}
|
|
|
|
|
|
DelayType delay_type;
|
|
if (timing_type == TimingType::SETUP) {
|
|
delay_type = DelayType::MAX;
|
|
} else {
|
|
delay_type = DelayType::MIN;
|
|
}
|
|
|
|
//Launch clock path
|
|
for(const TimingPathElem& path_elem : subpath.elements()) {
|
|
|
|
//Ask the application for a detailed breakdown of the edge delays
|
|
auto delay_breakdown = name_resolver_.edge_delay_breakdown(path_elem.incomming_edge(), delay_type);
|
|
if (!delay_breakdown.components.empty()) {
|
|
//Application provided detailed delay breakdown of edge delay, report it
|
|
for (auto& delay_component : delay_breakdown.components) {
|
|
std::string point = "|";
|
|
if (!delay_component.inst_name.empty()) {
|
|
point += " " + delay_component.inst_name;
|
|
}
|
|
if (!delay_component.type_name.empty()) {
|
|
point += " (" + delay_component.type_name + ")";
|
|
}
|
|
path += delay_component.delay;
|
|
path_helper.update_print_path(os, point, path);
|
|
}
|
|
TATUM_ASSERT_MSG(nearly_equal(path, path_elem.tag().time()), "Delay breakdown must match calculated delay");
|
|
}
|
|
|
|
|
|
std::string point = name_resolver_.node_name(path_elem.node()) + " (" + name_resolver_.node_type_name(path_elem.node()) + ")";
|
|
|
|
path = path_elem.tag().time();
|
|
|
|
path_helper.update_print_path(os, point, path + offset);
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
Time TimingReporter::report_timing_data_arrival_subpath(std::ostream& os,
|
|
detail::ReportTimingPathHelper& path_helper,
|
|
const TimingSubPath& subpath,
|
|
DomainId domain,
|
|
TimingType timing_type,
|
|
Time path) const {
|
|
|
|
{
|
|
//Input constraint
|
|
TATUM_ASSERT(subpath.elements().size() > 0);
|
|
const TimingPathElem& path_elem = *(subpath.elements().begin());
|
|
|
|
Time input_constraint;
|
|
if (timing_type == TimingType::SETUP) {
|
|
input_constraint = timing_constraints_.input_constraint(path_elem.node(), domain, DelayType::MAX);
|
|
} else {
|
|
TATUM_ASSERT(timing_type == TimingType::HOLD);
|
|
input_constraint = timing_constraints_.input_constraint(path_elem.node(), domain, DelayType::MIN);
|
|
}
|
|
|
|
if(input_constraint.valid()) {
|
|
path += Time(input_constraint);
|
|
|
|
path_helper.update_print_path(os, "input external delay", path);
|
|
}
|
|
}
|
|
|
|
DelayType delay_type;
|
|
if (timing_type == TimingType::SETUP) {
|
|
delay_type = DelayType::MAX;
|
|
} else {
|
|
delay_type = DelayType::MIN;
|
|
}
|
|
|
|
//Launch data
|
|
for(const TimingPathElem& path_elem : subpath.elements()) {
|
|
|
|
//Ask the application for a detailed breakdown of the edge delays
|
|
auto delay_breakdown = name_resolver_.edge_delay_breakdown(path_elem.incomming_edge(), delay_type);
|
|
if (!delay_breakdown.components.empty()) {
|
|
//Application provided detailed delay breakdown of edge delay, report it
|
|
for (auto& delay_component : delay_breakdown.components) {
|
|
std::string point = "|";
|
|
if (!delay_component.inst_name.empty()) {
|
|
point += " " + delay_component.inst_name;
|
|
}
|
|
if (!delay_component.type_name.empty()) {
|
|
point += " (" + delay_component.type_name + ")";
|
|
}
|
|
path += delay_component.delay;
|
|
path_helper.update_print_path(os, point, path);
|
|
}
|
|
TATUM_ASSERT_MSG(nearly_equal(path, path_elem.tag().time()), "Delay breakdown must match calculated delay");
|
|
}
|
|
|
|
std::string point = name_resolver_.node_name(path_elem.node()) + " (" + name_resolver_.node_type_name(path_elem.node()) + ")";
|
|
|
|
EdgeId in_edge = path_elem.incomming_edge();
|
|
if(in_edge && timing_graph_.edge_type(in_edge) == EdgeType::PRIMITIVE_CLOCK_LAUNCH) {
|
|
point += " [clock-to-output]";
|
|
}
|
|
|
|
path = path_elem.tag().time();
|
|
|
|
path_helper.update_print_path(os, point, path);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
Time TimingReporter::report_timing_data_required_element(std::ostream& os,
|
|
detail::ReportTimingPathHelper& path_helper,
|
|
const TimingPathElem& data_required_elem,
|
|
DomainId capture_domain,
|
|
TimingType timing_type,
|
|
Time path) const {
|
|
{
|
|
TATUM_ASSERT(timing_graph_.node_type(data_required_elem.node()) == NodeType::SINK);
|
|
|
|
//Setup/hold time
|
|
EdgeId in_edge = data_required_elem.incomming_edge();
|
|
if(in_edge && timing_graph_.edge_type(in_edge) == EdgeType::PRIMITIVE_CLOCK_CAPTURE) {
|
|
std::string point;
|
|
if(timing_type == TimingType::SETUP) {
|
|
point = "cell setup time";
|
|
} else {
|
|
TATUM_ASSERT_MSG(timing_type == TimingType::HOLD, "Expected path type SETUP or HOLD");
|
|
point = "cell hold time";
|
|
}
|
|
path = data_required_elem.tag().time();
|
|
path_helper.update_print_path(os, point, path);
|
|
}
|
|
|
|
//Output constraint
|
|
Time output_constraint;
|
|
if (timing_type == TimingType::SETUP) {
|
|
output_constraint = timing_constraints_.output_constraint(data_required_elem.node(), capture_domain, DelayType::MAX);
|
|
} else {
|
|
TATUM_ASSERT(timing_type == TimingType::HOLD);
|
|
output_constraint = timing_constraints_.output_constraint(data_required_elem.node(), capture_domain, DelayType::MIN);
|
|
}
|
|
if(output_constraint.valid()) {
|
|
path += -Time(output_constraint);
|
|
path_helper.update_print_path(os, "output external delay", path);
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
bool TimingReporter::nearly_equal(const Time& lhs, const Time& rhs) const {
|
|
return tatum::util::nearly_equal(lhs.value(), rhs.value(), absolute_error_tolerance_, relative_error_tolerance_);
|
|
}
|
|
|
|
size_t TimingReporter::estimate_point_print_width(const TimingPath& path) const {
|
|
size_t width = 60; //default
|
|
for(auto subpath : {path.clock_launch_path(), path.data_arrival_path(), path.clock_capture_path()}) {
|
|
for(auto elem : subpath.elements()) {
|
|
//Take the longest typical point name
|
|
std::string point = name_resolver_.node_name(elem.node()) + " (" + name_resolver_.node_type_name(elem.node()) + ")";
|
|
point += " [clock-to-output]";
|
|
|
|
//Keep the max over all points
|
|
width = std::max(width, point.size());
|
|
}
|
|
}
|
|
return width;
|
|
}
|
|
|
|
} //namespace tatum
|