Remove old timing analyser
This commit is contained in:
parent
b0820eeaaa
commit
d2a489d5e9
@ -99,11 +99,12 @@ struct Context : Arch, DeterministicRNG
|
|||||||
|
|
||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
// provided by report_json.cc
|
// provided by report.cc
|
||||||
void writeJsonReport(std::ostream &out) const;
|
void writeJsonReport(std::ostream &out) const;
|
||||||
|
|
||||||
// provided by timing_report.cc
|
// provided by timing_log.cc
|
||||||
void log_timing_results(bool print_histogram, bool print_path, bool warn_on_failure);
|
void log_timing_results(TimingResult &result, bool print_histogram, bool print_fmax, bool print_path,
|
||||||
|
bool warn_on_failure);
|
||||||
// --------------------------------------------------------------
|
// --------------------------------------------------------------
|
||||||
|
|
||||||
uint32_t checksum() const;
|
uint32_t checksum() const;
|
||||||
|
@ -365,7 +365,7 @@ struct TimingResult
|
|||||||
// clock to clock delays
|
// clock to clock delays
|
||||||
dict<std::pair<IdString, IdString>, delay_t> clock_delays;
|
dict<std::pair<IdString, IdString>, delay_t> clock_delays;
|
||||||
|
|
||||||
// Histogram of delays
|
// Histogram of slack
|
||||||
dict<int, unsigned> slack_histogram;
|
dict<int, unsigned> slack_histogram;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -238,10 +238,7 @@ void Context::writeJsonReport(std::ostream &out) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
Json::object jsonRoot{
|
Json::object jsonRoot{
|
||||||
{ "utilization", util_json },
|
{"utilization", util_json}, {"fmax", fmax_json}, {"critical_paths", json_report_critical_paths(this)}};
|
||||||
{ "fmax", fmax_json },
|
|
||||||
{ "critical_paths", json_report_critical_paths(this) }
|
|
||||||
};
|
|
||||||
|
|
||||||
if (detailed_timing_report) {
|
if (detailed_timing_report) {
|
||||||
jsonRoot["detailed_net_timings"] = json_report_detailed_net_timings(this);
|
jsonRoot["detailed_net_timings"] = json_report_detailed_net_timings(this);
|
@ -3,6 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright (C) 2018 gatecat <gatecat@ds0.me>
|
* Copyright (C) 2018 gatecat <gatecat@ds0.me>
|
||||||
* Copyright (C) 2018 Eddie Hung <eddieh@ece.ubc.ca>
|
* Copyright (C) 2018 Eddie Hung <eddieh@ece.ubc.ca>
|
||||||
|
* Copyright (C) 2023 rowanG077 <goemansrowan@gmail.com>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
@ -29,38 +30,22 @@
|
|||||||
|
|
||||||
NEXTPNR_NAMESPACE_BEGIN
|
NEXTPNR_NAMESPACE_BEGIN
|
||||||
|
|
||||||
namespace {
|
|
||||||
std::string clock_event_name(const Context *ctx, ClockDomainKey &dom)
|
|
||||||
{
|
|
||||||
std::string value;
|
|
||||||
if (dom.is_async())
|
|
||||||
value = "<async>";
|
|
||||||
else
|
|
||||||
value = (dom.edge == FALLING_EDGE ? "negedge " : "posedge ") + dom.clock.str(ctx);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TimingAnalyser::TimingAnalyser(Context *ctx) : ctx(ctx)
|
TimingAnalyser::TimingAnalyser(Context *ctx) : ctx(ctx)
|
||||||
{
|
{
|
||||||
ClockDomainKey key{IdString(), ClockEdge::RISING_EDGE};
|
ClockDomainKey key{IdString(), ClockEdge::RISING_EDGE};
|
||||||
domain_to_id.emplace(key, 0);
|
domain_to_id.emplace(key, 0);
|
||||||
domains.emplace_back(key);
|
domains.emplace_back(key);
|
||||||
async_clock_id = 0;
|
async_clock_id = 0;
|
||||||
|
|
||||||
domain_pair_id(async_clock_id, async_clock_id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void TimingAnalyser::setup(bool update_net_timings, bool update_histogram, bool update_crit_paths,
|
void TimingAnalyser::setup(bool update_net_timings, bool update_histogram, bool update_crit_paths)
|
||||||
bool update_route_delays)
|
|
||||||
{
|
{
|
||||||
init_ports();
|
init_ports();
|
||||||
get_cell_delays();
|
get_cell_delays();
|
||||||
topo_sort();
|
topo_sort();
|
||||||
setup_port_domains();
|
setup_port_domains();
|
||||||
identify_related_domains();
|
identify_related_domains();
|
||||||
run(update_net_timings, update_histogram, update_crit_paths, update_route_delays);
|
run(update_net_timings, update_histogram, update_crit_paths, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimingAnalyser::run(bool update_net_timings, bool update_histogram, bool update_crit_paths,
|
void TimingAnalyser::run(bool update_net_timings, bool update_histogram, bool update_crit_paths,
|
||||||
@ -78,7 +63,7 @@ void TimingAnalyser::run(bool update_net_timings, bool update_histogram, bool up
|
|||||||
// as to be updated. This is done so we ensure it's not possible to have
|
// as to be updated. This is done so we ensure it's not possible to have
|
||||||
// timing_result which contains mixed reports
|
// timing_result which contains mixed reports
|
||||||
if (update_net_timings || update_histogram || update_crit_paths) {
|
if (update_net_timings || update_histogram || update_crit_paths) {
|
||||||
ctx->timing_result = TimingResult();
|
result = TimingResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update_net_timings) {
|
if (update_net_timings) {
|
||||||
@ -666,6 +651,7 @@ void TimingAnalyser::walk_backward()
|
|||||||
dict<domain_id_t, delay_t> TimingAnalyser::max_delay_by_domain_pairs()
|
dict<domain_id_t, delay_t> TimingAnalyser::max_delay_by_domain_pairs()
|
||||||
{
|
{
|
||||||
dict<domain_id_t, delay_t> domain_delay;
|
dict<domain_id_t, delay_t> domain_delay;
|
||||||
|
|
||||||
for (auto p : topological_order) {
|
for (auto p : topological_order) {
|
||||||
auto &pd = ports.at(p);
|
auto &pd = ports.at(p);
|
||||||
for (auto &req : pd.required) {
|
for (auto &req : pd.required) {
|
||||||
@ -675,6 +661,9 @@ dict<domain_id_t, delay_t> TimingAnalyser::max_delay_by_domain_pairs()
|
|||||||
|
|
||||||
auto dp = domain_pair_id(launch, capture);
|
auto dp = domain_pair_id(launch, capture);
|
||||||
|
|
||||||
|
auto l = domains.at(launch);
|
||||||
|
auto c = domains.at(capture);
|
||||||
|
|
||||||
delay_t delay = arr.second.value.maxDelay() - req.second.value.minDelay();
|
delay_t delay = arr.second.value.maxDelay() - req.second.value.minDelay();
|
||||||
if (!domain_delay.count(dp) || domain_delay.at(dp) < delay)
|
if (!domain_delay.count(dp) || domain_delay.at(dp) < delay)
|
||||||
domain_delay[dp] = delay;
|
domain_delay[dp] = delay;
|
||||||
@ -728,7 +717,6 @@ void TimingAnalyser::compute_criticality()
|
|||||||
{
|
{
|
||||||
for (auto p : topological_order) {
|
for (auto p : topological_order) {
|
||||||
auto &pd = ports.at(p);
|
auto &pd = ports.at(p);
|
||||||
|
|
||||||
for (auto &pdp : pd.domain_pairs) {
|
for (auto &pdp : pd.domain_pairs) {
|
||||||
auto &dp = domain_pairs.at(pdp.first);
|
auto &dp = domain_pairs.at(pdp.first);
|
||||||
// Do not set criticality for asynchronous paths
|
// Do not set criticality for asynchronous paths
|
||||||
@ -747,7 +735,7 @@ void TimingAnalyser::compute_criticality()
|
|||||||
|
|
||||||
void TimingAnalyser::build_detailed_net_timing_report()
|
void TimingAnalyser::build_detailed_net_timing_report()
|
||||||
{
|
{
|
||||||
auto &net_timings = ctx->timing_result.detailed_net_timings;
|
auto &net_timings = result.detailed_net_timings;
|
||||||
|
|
||||||
for (domain_id_t dom_id = 0; dom_id < domain_id_t(domains.size()); ++dom_id) {
|
for (domain_id_t dom_id = 0; dom_id < domain_id_t(domains.size()); ++dom_id) {
|
||||||
auto &dom = domains.at(dom_id);
|
auto &dom = domains.at(dom_id);
|
||||||
@ -800,32 +788,6 @@ std::vector<CellPortKey> TimingAnalyser::get_worst_eps(domain_id_t domain_pair,
|
|||||||
return worst_eps;
|
return worst_eps;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string tgp_to_string(TimingPortClass c)
|
|
||||||
{
|
|
||||||
switch (c) {
|
|
||||||
case TMG_CLOCK_INPUT:
|
|
||||||
return "TMG_CLOCK_INPUT";
|
|
||||||
case TMG_GEN_CLOCK:
|
|
||||||
return "TMG_GEN_CLOCK";
|
|
||||||
case TMG_REGISTER_INPUT:
|
|
||||||
return "TMG_REGISTER_INPUT";
|
|
||||||
case TMG_REGISTER_OUTPUT:
|
|
||||||
return "TMG_REGISTER_OUTPUT";
|
|
||||||
case TMG_COMB_INPUT:
|
|
||||||
return "TMG_COMB_INPUT";
|
|
||||||
case TMG_COMB_OUTPUT:
|
|
||||||
return "TMG_COMB_OUTPUT";
|
|
||||||
case TMG_STARTPOINT:
|
|
||||||
return "TMG_STARTPOINT";
|
|
||||||
case TMG_ENDPOINT:
|
|
||||||
return "TMG_ENDPOINT";
|
|
||||||
case TMG_IGNORE:
|
|
||||||
return "TMG_IGNORE";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "UNKNOWN";
|
|
||||||
}
|
|
||||||
|
|
||||||
CriticalPath TimingAnalyser::build_critical_path_report(domain_id_t domain_pair, CellPortKey endpoint)
|
CriticalPath TimingAnalyser::build_critical_path_report(domain_id_t domain_pair, CellPortKey endpoint)
|
||||||
{
|
{
|
||||||
CriticalPath report;
|
CriticalPath report;
|
||||||
@ -855,7 +817,6 @@ CriticalPath TimingAnalyser::build_critical_path_report(domain_id_t domain_pair,
|
|||||||
std::vector<PortRef> crit_path_rev;
|
std::vector<PortRef> crit_path_rev;
|
||||||
auto cursor = endpoint;
|
auto cursor = endpoint;
|
||||||
|
|
||||||
log_info("Analyzing %s -> %s\n", clock_event_name(ctx, launch).c_str(), clock_event_name(ctx, capture).c_str());
|
|
||||||
while (cursor != CellPortKey()) {
|
while (cursor != CellPortKey()) {
|
||||||
auto cell = cell_info(cursor);
|
auto cell = cell_info(cursor);
|
||||||
auto &port = port_info(cursor);
|
auto &port = port_info(cursor);
|
||||||
@ -863,9 +824,6 @@ CriticalPath TimingAnalyser::build_critical_path_report(domain_id_t domain_pair,
|
|||||||
int port_clocks;
|
int port_clocks;
|
||||||
auto portClass = ctx->getPortTimingClass(cell, port.name, port_clocks);
|
auto portClass = ctx->getPortTimingClass(cell, port.name, port_clocks);
|
||||||
|
|
||||||
log_info("\tcursor at %s.%s tmg: %s, port dir: %s\n", cell->name.c_str(ctx), port.name.c_str(ctx),
|
|
||||||
tgp_to_string(portClass).c_str(), port.type == PortType::PORT_IN ? "PORT_IN" : "PORT_X");
|
|
||||||
|
|
||||||
if (portClass != TMG_CLOCK_INPUT && portClass != TMG_IGNORE && port.type == PortType::PORT_IN) {
|
if (portClass != TMG_CLOCK_INPUT && portClass != TMG_IGNORE && port.type == PortType::PORT_IN) {
|
||||||
crit_path_rev.emplace_back(PortRef{cell, port.name});
|
crit_path_rev.emplace_back(PortRef{cell, port.name});
|
||||||
}
|
}
|
||||||
@ -964,11 +922,11 @@ CriticalPath TimingAnalyser::build_critical_path_report(domain_id_t domain_pair,
|
|||||||
|
|
||||||
void TimingAnalyser::build_crit_path_reports()
|
void TimingAnalyser::build_crit_path_reports()
|
||||||
{
|
{
|
||||||
auto &clock_reports = ctx->timing_result.clock_paths;
|
auto &clock_reports = result.clock_paths;
|
||||||
auto &xclock_reports = ctx->timing_result.xclock_paths;
|
auto &xclock_reports = result.xclock_paths;
|
||||||
auto &clock_fmax = ctx->timing_result.clock_fmax;
|
auto &clock_fmax = result.clock_fmax;
|
||||||
auto &empty_clocks = ctx->timing_result.empty_paths;
|
auto &empty_clocks = result.empty_paths;
|
||||||
auto &clock_delays_ctx = ctx->timing_result.clock_delays;
|
auto &clock_delays_ctx = result.clock_delays;
|
||||||
|
|
||||||
auto delay_by_domain = max_delay_by_domain_pairs();
|
auto delay_by_domain = max_delay_by_domain_pairs();
|
||||||
|
|
||||||
@ -984,10 +942,9 @@ void TimingAnalyser::build_crit_path_reports()
|
|||||||
if (launch.clock != capture.clock || launch.is_async())
|
if (launch.clock != capture.clock || launch.is_async())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto path_delay = delay_by_domain.at(dp.key.launch);
|
auto path_delay = delay_by_domain.at(i);
|
||||||
|
|
||||||
double Fmax;
|
double Fmax;
|
||||||
empty_clocks.erase(launch.clock);
|
|
||||||
|
|
||||||
if (launch.edge == capture.edge)
|
if (launch.edge == capture.edge)
|
||||||
Fmax = 1000 / ctx->getDelayNS(path_delay);
|
Fmax = 1000 / ctx->getDelayNS(path_delay);
|
||||||
@ -999,10 +956,16 @@ void TimingAnalyser::build_crit_path_reports()
|
|||||||
if (ctx->nets.at(launch.clock)->clkconstr)
|
if (ctx->nets.at(launch.clock)->clkconstr)
|
||||||
target = 1000 / ctx->getDelayNS(ctx->nets.at(launch.clock)->clkconstr->period.minDelay());
|
target = 1000 / ctx->getDelayNS(ctx->nets.at(launch.clock)->clkconstr->period.minDelay());
|
||||||
|
|
||||||
|
auto worst_endpoint = get_worst_eps(i, 1);
|
||||||
|
if (worst_endpoint.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
clock_fmax[launch.clock].achieved = Fmax;
|
clock_fmax[launch.clock].achieved = Fmax;
|
||||||
clock_fmax[launch.clock].constraint = target;
|
clock_fmax[launch.clock].constraint = target;
|
||||||
auto worst_endpoint = get_worst_eps(i, 1).at(0);
|
|
||||||
clock_reports[launch.clock] = build_critical_path_report(i, worst_endpoint);
|
clock_reports[launch.clock] = build_critical_path_report(i, worst_endpoint.at(0));
|
||||||
|
|
||||||
|
empty_clocks.erase(launch.clock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1011,20 +974,17 @@ void TimingAnalyser::build_crit_path_reports()
|
|||||||
auto &launch = domains.at(dp.key.launch).key;
|
auto &launch = domains.at(dp.key.launch).key;
|
||||||
auto &capture = domains.at(dp.key.capture).key;
|
auto &capture = domains.at(dp.key.capture).key;
|
||||||
|
|
||||||
log_info("testin testing %s -> %s\n", clock_event_name(ctx, launch).c_str(),
|
|
||||||
clock_event_name(ctx, capture).c_str());
|
|
||||||
|
|
||||||
if (launch.clock == capture.clock && !launch.is_async())
|
if (launch.clock == capture.clock && !launch.is_async())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
log_info("testin testing2 %s -> %s\n", clock_event_name(ctx, launch).c_str(),
|
auto worst_endpoint = get_worst_eps(i, 1);
|
||||||
clock_event_name(ctx, capture).c_str());
|
if (worst_endpoint.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
auto worst_endpoint = get_worst_eps(i, 1).at(0);
|
xclock_reports.emplace_back(build_critical_path_report(i, worst_endpoint.at(0)));
|
||||||
xclock_reports.emplace_back(build_critical_path_report(i, worst_endpoint));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::sort(xclock_reports.begin(), xclock_reports.end(), [&](const CriticalPath &ra, const CriticalPath &rb) {
|
auto cmp_crit_path = [&](const CriticalPath &ra, const CriticalPath &rb) {
|
||||||
const auto &a = ra.clock_pair;
|
const auto &a = ra.clock_pair;
|
||||||
const auto &b = rb.clock_pair;
|
const auto &b = rb.clock_pair;
|
||||||
|
|
||||||
@ -1043,21 +1003,16 @@ void TimingAnalyser::build_crit_path_reports()
|
|||||||
if (a.end.edge < b.end.edge)
|
if (a.end.edge < b.end.edge)
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
});
|
};
|
||||||
|
|
||||||
for (auto &clock : clock_reports) {
|
std::sort(xclock_reports.begin(), xclock_reports.end(), cmp_crit_path);
|
||||||
float target = ctx->setting<float>("target_freq") / 1e6;
|
|
||||||
if (ctx->nets.at(clock.first)->clkconstr)
|
|
||||||
target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay());
|
|
||||||
clock_fmax[clock.first].constraint = target;
|
|
||||||
}
|
|
||||||
|
|
||||||
clock_delays_ctx = clock_delays;
|
clock_delays_ctx = clock_delays;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimingAnalyser::build_slack_histogram_report()
|
void TimingAnalyser::build_slack_histogram_report()
|
||||||
{
|
{
|
||||||
auto &slack_histogram = ctx->timing_result.slack_histogram;
|
auto &slack_histogram = result.slack_histogram;
|
||||||
|
|
||||||
for (domain_id_t dom_id = 0; dom_id < domain_id_t(domains.size()); ++dom_id) {
|
for (domain_id_t dom_id = 0; dom_id < domain_id_t(domains.size()); ++dom_id) {
|
||||||
for (auto &ep : domains.at(dom_id).endpoints) {
|
for (auto &ep : domains.at(dom_id).endpoints) {
|
||||||
@ -1069,7 +1024,7 @@ void TimingAnalyser::build_slack_histogram_report()
|
|||||||
auto &launch = domains.at(arr.first).key;
|
auto &launch = domains.at(arr.first).key;
|
||||||
|
|
||||||
// @gatecat: old timing analysis includes async path
|
// @gatecat: old timing analysis includes async path
|
||||||
// The slack doesn' make much sense so I filter it out.
|
// The slack doesn't make much sense so I filter it out.
|
||||||
if (launch.clock != capture.clock || launch.is_async())
|
if (launch.clock != capture.clock || launch.is_async())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -1129,4 +1084,17 @@ CellInfo *TimingAnalyser::cell_info(const CellPortKey &key) { return ctx->cells.
|
|||||||
|
|
||||||
PortInfo &TimingAnalyser::port_info(const CellPortKey &key) { return ctx->cells.at(key.cell)->ports.at(key.port); }
|
PortInfo &TimingAnalyser::port_info(const CellPortKey &key) { return ctx->cells.at(key.cell)->ports.at(key.port); }
|
||||||
|
|
||||||
|
void timing_analysis(Context *ctx, bool print_slack_histogram, bool print_fmax, bool print_path, bool warn_on_failure,
|
||||||
|
bool update_results)
|
||||||
|
{
|
||||||
|
TimingAnalyser tmg(ctx);
|
||||||
|
tmg.setup(ctx->detailed_timing_report, print_slack_histogram, print_path || print_fmax);
|
||||||
|
|
||||||
|
auto &result = tmg.get_timing_result();
|
||||||
|
ctx->log_timing_results(result, print_slack_histogram, print_fmax, print_path, warn_on_failure);
|
||||||
|
|
||||||
|
if (update_results)
|
||||||
|
ctx->timing_result = result;
|
||||||
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
* nextpnr -- Next Generation Place and Route
|
* nextpnr -- Next Generation Place and Route
|
||||||
*
|
*
|
||||||
* Copyright (C) 2018 gatecat <gatecat@ds0.me>
|
* Copyright (C) 2018 gatecat <gatecat@ds0.me>
|
||||||
|
* Copyright (C) 2023 rowanG077 <goemansrowan@gmail.com>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
@ -74,8 +75,10 @@ struct TimingAnalyser
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TimingAnalyser(Context *ctx);
|
TimingAnalyser(Context *ctx);
|
||||||
void setup(bool update_net_timings = false, bool update_histogram = false, bool update_crit_paths = false, bool update_route_delays = true);
|
|
||||||
void run(bool update_net_timings = false, bool update_histogram = false, bool update_crit_paths = false, bool update_route_delays = true);
|
void setup(bool update_net_timings = false, bool update_histogram = false, bool update_crit_paths = false);
|
||||||
|
void run(bool update_net_timings = false, bool update_histogram = false, bool update_crit_paths = false,
|
||||||
|
bool update_route_delays = true);
|
||||||
|
|
||||||
// This is used when routers etc are not actually binding detailed routing (due to congestion or an abstracted
|
// This is used when routers etc are not actually binding detailed routing (due to congestion or an abstracted
|
||||||
// model), but want to re-run STA with their own calculated delays
|
// model), but want to re-run STA with their own calculated delays
|
||||||
@ -91,7 +94,9 @@ struct TimingAnalyser
|
|||||||
return slack;
|
return slack;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto get_clock_delays() const { return clock_delays; }
|
dict<std::pair<IdString, IdString>, delay_t> get_clock_delays() const { return clock_delays; }
|
||||||
|
|
||||||
|
TimingResult &get_timing_result() { return result; }
|
||||||
|
|
||||||
bool setup_only = false;
|
bool setup_only = false;
|
||||||
bool verbose_mode = false;
|
bool verbose_mode = false;
|
||||||
@ -115,18 +120,14 @@ struct TimingAnalyser
|
|||||||
void compute_criticality();
|
void compute_criticality();
|
||||||
|
|
||||||
void build_detailed_net_timing_report();
|
void build_detailed_net_timing_report();
|
||||||
// Build up CriticalPathData
|
|
||||||
CriticalPath build_critical_path_report(domain_id_t domain_pair, CellPortKey endpoint);
|
CriticalPath build_critical_path_report(domain_id_t domain_pair, CellPortKey endpoint);
|
||||||
void build_crit_path_reports();
|
void build_crit_path_reports();
|
||||||
void build_slack_histogram_report();
|
void build_slack_histogram_report();
|
||||||
|
|
||||||
|
|
||||||
dict<domain_id_t, delay_t> max_delay_by_domain_pairs();
|
dict<domain_id_t, delay_t> max_delay_by_domain_pairs();
|
||||||
|
|
||||||
// get the N most failing endpoints for a given domain pair
|
// get the N worst endpoints for a given domain pair
|
||||||
std::vector<CellPortKey> get_worst_eps(domain_id_t domain_pair, int count);
|
std::vector<CellPortKey> get_worst_eps(domain_id_t domain_pair, int count);
|
||||||
// print the critical path for an endpoint and domain pair
|
|
||||||
void print_critical_path(CellPortKey endpoint, domain_id_t domain_pair);
|
|
||||||
|
|
||||||
const DelayPair init_delay{std::numeric_limits<delay_t>::max(), std::numeric_limits<delay_t>::lowest()};
|
const DelayPair init_delay{std::numeric_limits<delay_t>::max(), std::numeric_limits<delay_t>::lowest()};
|
||||||
|
|
||||||
@ -235,10 +236,11 @@ struct TimingAnalyser
|
|||||||
domain_id_t async_clock_id;
|
domain_id_t async_clock_id;
|
||||||
|
|
||||||
Context *ctx;
|
Context *ctx;
|
||||||
|
|
||||||
|
TimingResult result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Perform timing analysis and print out the fmax, and optionally the
|
// Perform timing analysis and optionaly print out slack histogram, fmax and critical paths
|
||||||
// critical path
|
|
||||||
void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false,
|
void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false,
|
||||||
bool warn_on_failure = false, bool update_results = false);
|
bool warn_on_failure = false, bool update_results = false);
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
/*
|
/*
|
||||||
* nextpnr -- Next Generation Place and Route
|
* nextpnr -- Next Generation Place and Route
|
||||||
*
|
*
|
||||||
|
* Copyright (C) 2018 gatecat <gatecat@ds0.me>
|
||||||
|
* Copyright (C) 2018 Eddie Hung <eddieh@ece.ubc.ca>
|
||||||
* Copyright (C) 2023 rowanG077 <goemansrowan@gmail.com>
|
* Copyright (C) 2023 rowanG077 <goemansrowan@gmail.com>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
@ -35,7 +37,7 @@ static std::string clock_event_name(const Context *ctx, const ClockEvent &e, int
|
|||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void log_crit_paths(const Context *ctx)
|
static void log_crit_paths(const Context *ctx, TimingResult &result)
|
||||||
{
|
{
|
||||||
static auto print_net_source = [ctx](const NetInfo *net) {
|
static auto print_net_source = [ctx](const NetInfo *net) {
|
||||||
// Check if this net is annotated with a source list
|
// Check if this net is annotated with a source list
|
||||||
@ -136,7 +138,7 @@ static void log_crit_paths(const Context *ctx)
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Single domain paths
|
// Single domain paths
|
||||||
for (auto &clock : ctx->timing_result.clock_paths) {
|
for (auto &clock : result.clock_paths) {
|
||||||
log_break();
|
log_break();
|
||||||
std::string start =
|
std::string start =
|
||||||
clock.second.clock_pair.start.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge");
|
clock.second.clock_pair.start.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge");
|
||||||
@ -149,7 +151,7 @@ static void log_crit_paths(const Context *ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cross-domain paths
|
// Cross-domain paths
|
||||||
for (auto &report : ctx->timing_result.xclock_paths) {
|
for (auto &report : result.xclock_paths) {
|
||||||
log_break();
|
log_break();
|
||||||
std::string start = clock_event_name(ctx, report.clock_pair.start);
|
std::string start = clock_event_name(ctx, report.clock_pair.start);
|
||||||
std::string end = clock_event_name(ctx, report.clock_pair.end);
|
std::string end = clock_event_name(ctx, report.clock_pair.end);
|
||||||
@ -158,20 +160,25 @@ static void log_crit_paths(const Context *ctx)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static void log_fmax(Context *ctx, bool warn_on_failure)
|
static void log_fmax(Context *ctx, TimingResult &result, bool warn_on_failure)
|
||||||
{
|
{
|
||||||
log_break();
|
log_break();
|
||||||
|
|
||||||
|
if (result.clock_paths.empty() && result.clock_paths.empty()) {
|
||||||
|
log_info("No Fmax available; no interior timing paths found in design.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned max_width = 0;
|
unsigned max_width = 0;
|
||||||
for (auto &clock : ctx->timing_result.clock_paths)
|
for (auto &clock : result.clock_paths)
|
||||||
max_width = std::max<unsigned>(max_width, clock.first.str(ctx).size());
|
max_width = std::max<unsigned>(max_width, clock.first.str(ctx).size());
|
||||||
|
|
||||||
for (auto &clock : ctx->timing_result.clock_paths) {
|
for (auto &clock : result.clock_paths) {
|
||||||
const auto &clock_name = clock.first.str(ctx);
|
const auto &clock_name = clock.first.str(ctx);
|
||||||
const int width = max_width - clock_name.size();
|
const int width = max_width - clock_name.size();
|
||||||
|
|
||||||
float fmax = ctx->timing_result.clock_fmax[clock.first].achieved;
|
float fmax = result.clock_fmax[clock.first].achieved;
|
||||||
float target = ctx->timing_result.clock_fmax[clock.first].constraint;
|
float target = result.clock_fmax[clock.first].constraint;
|
||||||
bool passed = target < fmax;
|
bool passed = target < fmax;
|
||||||
|
|
||||||
if (!warn_on_failure || passed)
|
if (!warn_on_failure || passed)
|
||||||
@ -188,31 +195,31 @@ static void log_fmax(Context *ctx, bool warn_on_failure)
|
|||||||
|
|
||||||
// Clock to clock delays for xpaths
|
// Clock to clock delays for xpaths
|
||||||
dict<ClockPair, delay_t> xclock_delays;
|
dict<ClockPair, delay_t> xclock_delays;
|
||||||
for (auto &report : ctx->timing_result.xclock_paths) {
|
for (auto &report : result.xclock_paths) {
|
||||||
const auto &clock1_name = report.clock_pair.start.clock;
|
const auto &clock1_name = report.clock_pair.start.clock;
|
||||||
const auto &clock2_name = report.clock_pair.end.clock;
|
const auto &clock2_name = report.clock_pair.end.clock;
|
||||||
|
|
||||||
const auto key = std::make_pair(clock1_name, clock2_name);
|
const auto key = std::make_pair(clock1_name, clock2_name);
|
||||||
if (ctx->timing_result.clock_delays.count(key)) {
|
if (result.clock_delays.count(key)) {
|
||||||
xclock_delays[report.clock_pair] = ctx->timing_result.clock_delays.at(key);
|
xclock_delays[report.clock_pair] = result.clock_delays.at(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned max_width_xca = 0;
|
unsigned max_width_xca = 0;
|
||||||
unsigned max_width_xcb = 0;
|
unsigned max_width_xcb = 0;
|
||||||
for (auto &report : ctx->timing_result.xclock_paths) {
|
for (auto &report : result.xclock_paths) {
|
||||||
max_width_xca = std::max((unsigned)clock_event_name(ctx, report.clock_pair.start).length(), max_width_xca);
|
max_width_xca = std::max((unsigned)clock_event_name(ctx, report.clock_pair.start).length(), max_width_xca);
|
||||||
max_width_xcb = std::max((unsigned)clock_event_name(ctx, report.clock_pair.end).length(), max_width_xcb);
|
max_width_xcb = std::max((unsigned)clock_event_name(ctx, report.clock_pair.end).length(), max_width_xcb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check and report xpath delays for related clocks
|
// Check and report xpath delays for related clocks
|
||||||
if (!ctx->timing_result.xclock_paths.empty()) {
|
if (!result.xclock_paths.empty()) {
|
||||||
for (auto &report : ctx->timing_result.xclock_paths) {
|
for (auto &report : result.xclock_paths) {
|
||||||
const auto &clock_a = report.clock_pair.start.clock;
|
const auto &clock_a = report.clock_pair.start.clock;
|
||||||
const auto &clock_b = report.clock_pair.end.clock;
|
const auto &clock_b = report.clock_pair.end.clock;
|
||||||
|
|
||||||
const auto key = std::make_pair(clock_a, clock_b);
|
const auto key = std::make_pair(clock_a, clock_b);
|
||||||
if (!ctx->timing_result.clock_delays.count(key)) {
|
if (!result.clock_delays.count(key)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +231,7 @@ static void log_fmax(Context *ctx, bool warn_on_failure)
|
|||||||
// Compensate path delay for clock-to-clock delay. If the
|
// Compensate path delay for clock-to-clock delay. If the
|
||||||
// result is negative then only the latter matters. Otherwise
|
// result is negative then only the latter matters. Otherwise
|
||||||
// the compensated path delay is taken.
|
// the compensated path delay is taken.
|
||||||
auto clock_delay = ctx->timing_result.clock_delays.at(key);
|
auto clock_delay = result.clock_delays.at(key);
|
||||||
path_delay -= clock_delay;
|
path_delay -= clock_delay;
|
||||||
|
|
||||||
float fmax = std::numeric_limits<float>::infinity();
|
float fmax = std::numeric_limits<float>::infinity();
|
||||||
@ -239,7 +246,7 @@ static void log_fmax(Context *ctx, bool warn_on_failure)
|
|||||||
// user input. In case of only one constraint preset take it,
|
// user input. In case of only one constraint preset take it,
|
||||||
// otherwise get the worst case (min.)
|
// otherwise get the worst case (min.)
|
||||||
float target;
|
float target;
|
||||||
auto &clock_fmax = ctx->timing_result.clock_fmax;
|
auto &clock_fmax = result.clock_fmax;
|
||||||
if (clock_fmax.count(clock_a) && !clock_fmax.count(clock_b)) {
|
if (clock_fmax.count(clock_a) && !clock_fmax.count(clock_b)) {
|
||||||
target = clock_fmax.at(clock_a).constraint;
|
target = clock_fmax.at(clock_a).constraint;
|
||||||
} else if (!clock_fmax.count(clock_a) && clock_fmax.count(clock_b)) {
|
} else if (!clock_fmax.count(clock_a) && clock_fmax.count(clock_b)) {
|
||||||
@ -268,7 +275,7 @@ static void log_fmax(Context *ctx, bool warn_on_failure)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Report clock delays for xpaths
|
// Report clock delays for xpaths
|
||||||
if (!ctx->timing_result.clock_delays.empty()) {
|
if (!result.clock_delays.empty()) {
|
||||||
for (auto &pair : xclock_delays) {
|
for (auto &pair : xclock_delays) {
|
||||||
auto ev_a = clock_event_name(ctx, pair.first.start, max_width_xca);
|
auto ev_a = clock_event_name(ctx, pair.first.start, max_width_xca);
|
||||||
auto ev_b = clock_event_name(ctx, pair.first.end, max_width_xcb);
|
auto ev_b = clock_event_name(ctx, pair.first.end, max_width_xcb);
|
||||||
@ -284,19 +291,19 @@ static void log_fmax(Context *ctx, bool warn_on_failure)
|
|||||||
log_break();
|
log_break();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &eclock : ctx->timing_result.empty_paths) {
|
for (auto &eclock : result.empty_paths) {
|
||||||
if (eclock != IdString())
|
if (eclock != IdString())
|
||||||
log_info("Clock '%s' has no interior paths\n", eclock.c_str(ctx));
|
log_info("Clock '%s' has no interior paths\n", eclock.c_str(ctx));
|
||||||
}
|
}
|
||||||
log_break();
|
log_break();
|
||||||
|
|
||||||
int start_field_width = 0, end_field_width = 0;
|
int start_field_width = 0, end_field_width = 0;
|
||||||
for (auto &report : ctx->timing_result.xclock_paths) {
|
for (auto &report : result.xclock_paths) {
|
||||||
start_field_width = std::max((int)clock_event_name(ctx, report.clock_pair.start).length(), start_field_width);
|
start_field_width = std::max((int)clock_event_name(ctx, report.clock_pair.start).length(), start_field_width);
|
||||||
end_field_width = std::max((int)clock_event_name(ctx, report.clock_pair.end).length(), end_field_width);
|
end_field_width = std::max((int)clock_event_name(ctx, report.clock_pair.end).length(), end_field_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &report : ctx->timing_result.xclock_paths) {
|
for (auto &report : result.xclock_paths) {
|
||||||
const ClockEvent &a = report.clock_pair.start;
|
const ClockEvent &a = report.clock_pair.start;
|
||||||
const ClockEvent &b = report.clock_pair.end;
|
const ClockEvent &b = report.clock_pair.end;
|
||||||
delay_t path_delay = 0;
|
delay_t path_delay = 0;
|
||||||
@ -309,23 +316,15 @@ static void log_fmax(Context *ctx, bool warn_on_failure)
|
|||||||
log_break();
|
log_break();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void log_histogram(Context *ctx)
|
static void log_histogram(Context *ctx, TimingResult &result)
|
||||||
{
|
{
|
||||||
log_break();
|
|
||||||
log_info("Slack histogram:\n");
|
|
||||||
|
|
||||||
if (ctx->timing_result.slack_histogram.empty()) {
|
|
||||||
log_info(" No slack figures available\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned num_bins = 20;
|
unsigned num_bins = 20;
|
||||||
unsigned bar_width = 60;
|
unsigned bar_width = 60;
|
||||||
|
|
||||||
int min_slack = INT_MAX;
|
int min_slack = INT_MAX;
|
||||||
int max_slack = INT_MIN;
|
int max_slack = INT_MIN;
|
||||||
|
|
||||||
for (const auto &i : ctx->timing_result.slack_histogram) {
|
for (const auto &i : result.slack_histogram) {
|
||||||
if (i.first < min_slack)
|
if (i.first < min_slack)
|
||||||
min_slack = i.first;
|
min_slack = i.first;
|
||||||
if (i.first > max_slack)
|
if (i.first > max_slack)
|
||||||
@ -335,7 +334,7 @@ static void log_histogram(Context *ctx)
|
|||||||
auto bin_size = std::max<unsigned>(1, ceil((max_slack - min_slack + 1) / float(num_bins)));
|
auto bin_size = std::max<unsigned>(1, ceil((max_slack - min_slack + 1) / float(num_bins)));
|
||||||
std::vector<unsigned> bins(num_bins);
|
std::vector<unsigned> bins(num_bins);
|
||||||
unsigned max_freq = 0;
|
unsigned max_freq = 0;
|
||||||
for (const auto &i : ctx->timing_result.slack_histogram) {
|
for (const auto &i : result.slack_histogram) {
|
||||||
int bin_idx = int((i.first - min_slack) / bin_size);
|
int bin_idx = int((i.first - min_slack) / bin_size);
|
||||||
if (bin_idx < 0)
|
if (bin_idx < 0)
|
||||||
bin_idx = 0;
|
bin_idx = 0;
|
||||||
@ -347,6 +346,8 @@ static void log_histogram(Context *ctx)
|
|||||||
}
|
}
|
||||||
bar_width = std::min(bar_width, max_freq);
|
bar_width = std::min(bar_width, max_freq);
|
||||||
|
|
||||||
|
log_break();
|
||||||
|
log_info("Slack histogram:\n");
|
||||||
log_info(" legend: * represents %d endpoint(s)\n", max_freq / bar_width);
|
log_info(" legend: * represents %d endpoint(s)\n", max_freq / bar_width);
|
||||||
log_info(" + represents [1,%d) endpoint(s)\n", max_freq / bar_width);
|
log_info(" + represents [1,%d) endpoint(s)\n", max_freq / bar_width);
|
||||||
for (unsigned i = 0; i < num_bins; ++i)
|
for (unsigned i = 0; i < num_bins; ++i)
|
||||||
@ -355,15 +356,17 @@ static void log_histogram(Context *ctx)
|
|||||||
(bins[i] * bar_width) % max_freq > 0 ? '+' : ' ');
|
(bins[i] * bar_width) % max_freq > 0 ? '+' : ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
void Context::log_timing_results(bool print_histogram, bool print_path, bool warn_on_failure)
|
void Context::log_timing_results(TimingResult &result, bool print_histogram, bool print_fmax, bool print_path,
|
||||||
|
bool warn_on_failure)
|
||||||
{
|
{
|
||||||
if (print_path)
|
if (print_path)
|
||||||
log_crit_paths(this);
|
log_crit_paths(this, result);
|
||||||
|
|
||||||
log_fmax(this, warn_on_failure);
|
if (print_fmax)
|
||||||
|
log_fmax(this, result, warn_on_failure);
|
||||||
|
|
||||||
if (print_histogram)
|
if (print_histogram && !timing_result.slack_histogram.empty())
|
||||||
log_histogram(this);
|
log_histogram(this, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
NEXTPNR_NAMESPACE_END
|
NEXTPNR_NAMESPACE_END
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user