OpenFPGA/libs/EXTERNAL/libtatum/libtatum/tatum/TimingReporter.cpp

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