aboutsummaryrefslogtreecommitdiffstats
path: root/common/timing.cc
diff options
context:
space:
mode:
Diffstat (limited to 'common/timing.cc')
-rw-r--r--common/timing.cc544
1 files changed, 411 insertions, 133 deletions
diff --git a/common/timing.cc b/common/timing.cc
index c5a99f54..40e4d344 100644
--- a/common/timing.cc
+++ b/common/timing.cc
@@ -22,6 +22,7 @@
#include <algorithm>
#include <boost/range/adaptor/reversed.hpp>
#include <deque>
+#include <map>
#include <unordered_map>
#include <utility>
#include "log.h"
@@ -29,17 +30,72 @@
NEXTPNR_NAMESPACE_BEGIN
+namespace {
+struct ClockEvent
+{
+ IdString clock;
+ ClockEdge edge;
+
+ bool operator==(const ClockEvent &other) const { return clock == other.clock && edge == other.edge; }
+};
+
+struct ClockPair
+{
+ ClockEvent start, end;
+
+ bool operator==(const ClockPair &other) const { return start == other.start && end == other.end; }
+};
+} // namespace
+
+NEXTPNR_NAMESPACE_END
+namespace std {
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockEvent &obj) const noexcept
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(obj.clock));
+ boost::hash_combine(seed, hash<int>()(int(obj.edge)));
+ return seed;
+ }
+};
+
+template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockPair>
+{
+ std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockPair &obj) const noexcept
+ {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>()(obj.start));
+ boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>()(obj.start));
+ return seed;
+ }
+};
+
+} // namespace std
+NEXTPNR_NAMESPACE_BEGIN
+
typedef std::vector<const PortRef *> PortRefVector;
typedef std::map<int, unsigned> DelayFrequency;
+struct CriticalPath
+{
+ PortRefVector ports;
+ delay_t path_delay;
+ delay_t path_period;
+};
+
+typedef std::unordered_map<ClockPair, CriticalPath> CriticalPathMap;
+
struct Timing
{
Context *ctx;
bool net_delays;
bool update;
delay_t min_slack;
- PortRefVector *crit_path;
+ CriticalPathMap *crit_path;
DelayFrequency *slack_histogram;
+ IdString async_clock;
struct TimingData
{
@@ -49,23 +105,24 @@ struct Timing
unsigned max_path_length = 0;
delay_t min_remaining_budget;
bool false_startpoint = false;
+ std::unordered_map<ClockEvent, delay_t> arrival_time;
};
- Timing(Context *ctx, bool net_delays, bool update, PortRefVector *crit_path = nullptr,
+ Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr,
DelayFrequency *slack_histogram = nullptr)
: ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq),
- crit_path(crit_path), slack_histogram(slack_histogram)
+ crit_path(crit_path), slack_histogram(slack_histogram), async_clock(ctx->id("$async$"))
{
}
delay_t walk_paths()
{
- const auto clk_period = delay_t(1.0e12 / ctx->target_freq);
+ const auto clk_period = ctx->getDelayFromNS(1.0e9 / ctx->target_freq).maxDelay();
// First, compute the topographical order of nets to walk through the circuit, assuming it is a _acyclic_ graph
// TODO(eddieh): Handle the case where it is cyclic, e.g. combinatorial loops
std::vector<NetInfo *> topographical_order;
- std::unordered_map<const NetInfo *, TimingData> net_data;
+ std::unordered_map<const NetInfo *, std::unordered_map<ClockEvent, TimingData>> net_data;
// In lieu of deleting edges from the graph, simply count the number of fanins to each output port
std::unordered_map<const PortInfo *, unsigned> port_fanin;
@@ -84,22 +141,34 @@ struct Timing
}
for (auto o : output_ports) {
- IdString clockPort;
- TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clockPort);
+ int clocks = 0;
+ TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clocks);
// If output port is influenced by a clock (e.g. FF output) then add it to the ordering as a timing
// start-point
if (portClass == TMG_REGISTER_OUTPUT) {
- DelayInfo clkToQ;
- ctx->getCellDelay(cell.second.get(), clockPort, o->name, clkToQ);
topographical_order.emplace_back(o->net);
- net_data.emplace(o->net, TimingData{clkToQ.maxDelay()});
+ for (int i = 0; i < clocks; i++) {
+ TimingClockingInfo clkInfo = ctx->getPortClockingInfo(cell.second.get(), o->name, i);
+ const NetInfo *clknet = get_net_or_empty(cell.second.get(), clkInfo.clock_port);
+ IdString clksig = clknet ? clknet->name : async_clock;
+ net_data[o->net][ClockEvent{clksig, clknet ? clkInfo.edge : RISING_EDGE}] =
+ TimingData{clkInfo.clockToQ.maxDelay()};
+ }
+
} else {
if (portClass == TMG_STARTPOINT || portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE) {
topographical_order.emplace_back(o->net);
TimingData td;
td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE);
- net_data.emplace(o->net, std::move(td));
+ td.max_arrival = 0;
+ net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td;
}
+
+ // Don't analyse paths from a clock input to other pins - they will be considered by the
+ // special-case handling register input/output class ports
+ if (portClass == TMG_CLOCK_INPUT)
+ continue;
+
// Otherwise, for all driven input ports on this cell, if a timing arc exists between the input and
// the current output port, increment fanin counter
for (auto i : input_ports) {
@@ -120,14 +189,15 @@ struct Timing
queue.pop_front();
for (auto &usr : net->users) {
- IdString clockPort;
- TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort);
+ int user_clocks;
+ TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, user_clocks);
if (usrClass == TMG_IGNORE || usrClass == TMG_CLOCK_INPUT)
continue;
for (auto &port : usr.cell->ports) {
if (port.second.type != PORT_OUT || !port.second.net)
continue;
- TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, clockPort);
+ int port_clocks;
+ TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, port.first, port_clocks);
// Skip if this is a clocked output (but allow non-clocked ones)
if (portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT || portClass == TMG_IGNORE ||
@@ -174,137 +244,213 @@ struct Timing
// Go forwards topographically to find the maximum arrival time and max path length for each net
for (auto net : topographical_order) {
- auto &nd = net_data.at(net);
- const auto net_arrival = nd.max_arrival;
- const auto net_length_plus_one = nd.max_path_length + 1;
- nd.min_remaining_budget = clk_period;
- for (auto &usr : net->users) {
- IdString clockPort;
- TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort);
- if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) {
- } else {
+ if (!net_data.count(net))
+ continue;
+ auto &nd_map = net_data.at(net);
+ for (auto &startdomain : nd_map) {
+ ClockEvent start_clk = startdomain.first;
+ auto &nd = startdomain.second;
+ if (nd.false_startpoint)
+ continue;
+ const auto net_arrival = nd.max_arrival;
+ const auto net_length_plus_one = nd.max_path_length + 1;
+ nd.min_remaining_budget = clk_period;
+ for (auto &usr : net->users) {
+ int port_clocks;
+ TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
- auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
auto usr_arrival = net_arrival + net_delay;
- // Iterate over all output ports on the same cell as the sink
- for (auto port : usr.cell->ports) {
- if (port.second.type != PORT_OUT || !port.second.net)
- continue;
- DelayInfo comb_delay;
- // Look up delay through this path
- bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
- if (!is_path)
- continue;
- auto &data = net_data[port.second.net];
- auto &arrival = data.max_arrival;
- arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay());
- if (!budget_override) { // Do not increment path length if budget overriden since it doesn't
- // require a share of the slack
- auto &path_length = data.max_path_length;
- path_length = std::max(path_length, net_length_plus_one);
+
+ if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE ||
+ portClass == TMG_CLOCK_INPUT) {
+ // Skip
+ } else {
+ auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
+ // Iterate over all output ports on the same cell as the sink
+ for (auto port : usr.cell->ports) {
+ if (port.second.type != PORT_OUT || !port.second.net)
+ continue;
+ DelayInfo comb_delay;
+ // Look up delay through this path
+ bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
+ if (!is_path)
+ continue;
+ auto &data = net_data[port.second.net][start_clk];
+ auto &arrival = data.max_arrival;
+ arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay());
+ if (!budget_override) { // Do not increment path length if budget overriden since it doesn't
+ // require a share of the slack
+ auto &path_length = data.max_path_length;
+ path_length = std::max(path_length, net_length_plus_one);
+ }
}
}
}
}
}
- const NetInfo *crit_net = nullptr;
+ std::unordered_map<ClockPair, std::pair<delay_t, NetInfo *>> crit_nets;
// Now go backwards topographically to determine the minimum path slack, and to distribute all path slack evenly
// between all nets on the path
for (auto net : boost::adaptors::reverse(topographical_order)) {
- auto &nd = net_data.at(net);
- // Ignore false startpoints
- if (nd.false_startpoint) continue;
- const delay_t net_length_plus_one = nd.max_path_length + 1;
- auto &net_min_remaining_budget = nd.min_remaining_budget;
- for (auto &usr : net->users) {
- auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
- auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
- IdString associatedClock;
- TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, associatedClock);
- if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) {
- const auto net_arrival = nd.max_arrival;
- auto path_budget = clk_period - (net_arrival + net_delay);
- if (update) {
- auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
- usr.budget = std::min(usr.budget, net_delay + budget_share);
- net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share);
- }
+ if (!net_data.count(net))
+ continue;
+ auto &nd_map = net_data.at(net);
+ for (auto &startdomain : nd_map) {
+ auto &nd = startdomain.second;
+ // Ignore false startpoints
+ if (nd.false_startpoint)
+ continue;
+ const delay_t net_length_plus_one = nd.max_path_length + 1;
+ auto &net_min_remaining_budget = nd.min_remaining_budget;
+ for (auto &usr : net->users) {
+ auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
+ auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
+ int port_clocks;
+ TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
+ if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) {
+ auto process_endpoint = [&](IdString clksig, ClockEdge edge, delay_t setup) {
+ const auto net_arrival = nd.max_arrival;
+ const auto endpoint_arrival = net_arrival + net_delay + setup;
+ delay_t period;
+ // Set default period
+ if (edge == startdomain.first.edge) {
+ period = clk_period;
+ } else {
+ period = clk_period / 2;
+ }
+ if (clksig != async_clock) {
+ if (ctx->nets.at(clksig)->clkconstr) {
+ if (edge == startdomain.first.edge) {
+ // same edge
+ period = ctx->nets.at(clksig)->clkconstr->period.minDelay();
+ } else if (edge == RISING_EDGE) {
+ // falling -> rising
+ period = ctx->nets.at(clksig)->clkconstr->low.minDelay();
+ } else if (edge == FALLING_EDGE) {
+ // rising -> falling
+ period = ctx->nets.at(clksig)->clkconstr->high.minDelay();
+ }
+ }
+ }
+ auto path_budget = period - endpoint_arrival;
+
+ if (update) {
+ auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
+ usr.budget = std::min(usr.budget, net_delay + budget_share);
+ net_min_remaining_budget =
+ std::min(net_min_remaining_budget, path_budget - budget_share);
+ }
+
+ if (path_budget < min_slack)
+ min_slack = path_budget;
+
+ if (slack_histogram) {
+ int slack_ps = ctx->getDelayNS(path_budget) * 1000;
+ (*slack_histogram)[slack_ps]++;
+ }
+ ClockEvent dest_ev{clksig, edge};
+ ClockPair clockPair{startdomain.first, dest_ev};
+ nd.arrival_time[dest_ev] = std::max(nd.arrival_time[dest_ev], endpoint_arrival);
+
+ if (crit_path) {
+ if (!crit_nets.count(clockPair) || crit_nets.at(clockPair).first < endpoint_arrival) {
+ crit_nets[clockPair] = std::make_pair(endpoint_arrival, net);
+ (*crit_path)[clockPair].path_delay = endpoint_arrival;
+ (*crit_path)[clockPair].path_period = period;
+ (*crit_path)[clockPair].ports.clear();
+ (*crit_path)[clockPair].ports.push_back(&usr);
+ }
+ }
+ };
+ if (portClass == TMG_REGISTER_INPUT) {
+ for (int i = 0; i < port_clocks; i++) {
+ TimingClockingInfo clkInfo = ctx->getPortClockingInfo(usr.cell, usr.port, i);
+ const NetInfo *clknet = get_net_or_empty(usr.cell, clkInfo.clock_port);
+ IdString clksig = clknet ? clknet->name : async_clock;
+ process_endpoint(clksig, clknet ? clkInfo.edge : RISING_EDGE, clkInfo.setup.maxDelay());
+ }
+ } else {
+ process_endpoint(async_clock, RISING_EDGE, 0);
+ }
- if (path_budget < min_slack) {
- min_slack = path_budget;
- if (crit_path) {
- crit_path->clear();
- crit_path->push_back(&usr);
- crit_net = net;
+ } else if (update) {
+
+ // Iterate over all output ports on the same cell as the sink
+ for (const auto &port : usr.cell->ports) {
+ if (port.second.type != PORT_OUT || !port.second.net)
+ continue;
+ DelayInfo comb_delay;
+ bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
+ if (!is_path)
+ continue;
+ if (net_data.count(port.second.net) &&
+ net_data.at(port.second.net).count(startdomain.first)) {
+ auto path_budget =
+ net_data.at(port.second.net).at(startdomain.first).min_remaining_budget;
+ auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
+ usr.budget = std::min(usr.budget, net_delay + budget_share);
+ net_min_remaining_budget =
+ std::min(net_min_remaining_budget, path_budget - budget_share);
+ }
}
}
- if (slack_histogram) {
- int slack_ps = ctx->getDelayNS(path_budget) * 1000;
- (*slack_histogram)[slack_ps]++;
- }
- } else if (update) {
- // Iterate over all output ports on the same cell as the sink
- for (const auto &port : usr.cell->ports) {
- if (port.second.type != PORT_OUT || !port.second.net)
- continue;
- DelayInfo comb_delay;
- bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
- if (!is_path)
- continue;
- auto path_budget = net_data.at(port.second.net).min_remaining_budget;
- auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
- usr.budget = std::min(usr.budget, net_delay + budget_share);
- net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share);
- }
}
}
}
if (crit_path) {
// Walk backwards from the most critical net
- while (crit_net) {
- const PortInfo *crit_ipin = nullptr;
- delay_t max_arrival = std::numeric_limits<delay_t>::min();
-
- // Look at all input ports on its driving cell
- for (const auto &port : crit_net->driver.cell->ports) {
- if (port.second.type != PORT_IN || !port.second.net)
- continue;
- DelayInfo comb_delay;
- bool is_path =
- ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay);
- if (!is_path)
- continue;
- // If input port is influenced by a clock, skip
- IdString portClock;
- TimingPortClass portClass = ctx->getPortTimingClass(crit_net->driver.cell, port.first, portClock);
- if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT ||
- portClass == TMG_IGNORE)
- continue;
+ for (auto crit_pair : crit_nets) {
+ NetInfo *crit_net = crit_pair.second.second;
+ auto &cp_ports = (*crit_path)[crit_pair.first].ports;
+ while (crit_net) {
+ const PortInfo *crit_ipin = nullptr;
+ delay_t max_arrival = std::numeric_limits<delay_t>::min();
+
+ // Look at all input ports on its driving cell
+ for (const auto &port : crit_net->driver.cell->ports) {
+ if (port.second.type != PORT_IN || !port.second.net)
+ continue;
+ DelayInfo comb_delay;
+ bool is_path =
+ ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay);
+ if (!is_path)
+ continue;
+ // If input port is influenced by a clock, skip
+ int port_clocks;
+ TimingPortClass portClass =
+ ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks);
+ if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT ||
+ portClass == TMG_ENDPOINT || portClass == TMG_IGNORE)
+ continue;
- // And find the fanin net with the latest arrival time
- const auto net_arrival = net_data.at(port.second.net).max_arrival;
- if (net_arrival > max_arrival) {
- max_arrival = net_arrival;
- crit_ipin = &port.second;
+ // And find the fanin net with the latest arrival time
+ if (net_data.count(port.second.net) &&
+ net_data.at(port.second.net).count(crit_pair.first.start)) {
+ const auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival;
+ if (net_arrival > max_arrival) {
+ max_arrival = net_arrival;
+ crit_ipin = &port.second;
+ }
+ }
}
- }
- if (!crit_ipin)
- break;
-
- // Now convert PortInfo* into a PortRef*
- for (auto &usr : crit_ipin->net->users) {
- if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) {
- crit_path->push_back(&usr);
+ if (!crit_ipin)
break;
+
+ // Now convert PortInfo* into a PortRef*
+ for (auto &usr : crit_ipin->net->users) {
+ if (usr.cell->name == crit_net->driver.cell->name && usr.port == crit_ipin->name) {
+ cp_ports.push_back(&usr);
+ break;
+ }
}
+ crit_net = crit_ipin->net;
}
- crit_net = crit_ipin->net;
+ std::reverse(cp_ports.begin(), cp_ports.end());
}
- std::reverse(crit_path->begin(), crit_path->end());
}
return min_slack;
}
@@ -365,30 +511,106 @@ void assign_budget(Context *ctx, bool quiet)
log_info("Checksum: 0x%08x\n", ctx->checksum());
}
-void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
+void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool print_path)
{
- PortRefVector crit_path;
+ auto format_event = [ctx](const ClockEvent &e, int field_width = 0) {
+ std::string value;
+ if (e.clock == ctx->id("$async$"))
+ value = std::string("<async>");
+ else
+ value = (e.edge == FALLING_EDGE ? std::string("negedge ") : std::string("posedge ")) + e.clock.str(ctx);
+ if (int(value.length()) < field_width)
+ value.insert(value.length(), field_width - int(value.length()), ' ');
+ return value;
+ };
+
+ CriticalPathMap crit_paths;
DelayFrequency slack_histogram;
- Timing timing(ctx, true /* net_delays */, false /* update */, print_path ? &crit_path : nullptr,
+ Timing timing(ctx, true /* net_delays */, false /* update */, (print_path || print_fmax) ? &crit_paths : nullptr,
print_histogram ? &slack_histogram : nullptr);
- auto min_slack = timing.walk_paths();
+ timing.walk_paths();
+ std::map<IdString, std::pair<ClockPair, CriticalPath>> clock_reports;
+ std::map<IdString, double> clock_fmax;
+ std::vector<ClockPair> xclock_paths;
+ std::set<IdString> empty_clocks; // set of clocks with no interior paths
+ if (print_path || print_fmax) {
+ for (auto path : crit_paths) {
+ const ClockEvent &a = path.first.start;
+ const ClockEvent &b = path.first.end;
+ empty_clocks.insert(a.clock);
+ empty_clocks.insert(b.clock);
+ }
+ for (auto path : crit_paths) {
+ const ClockEvent &a = path.first.start;
+ const ClockEvent &b = path.first.end;
+ if (a.clock != b.clock || a.clock == ctx->id("$async$"))
+ continue;
+ double Fmax;
+ empty_clocks.erase(a.clock);
+ if (a.edge == b.edge)
+ Fmax = 1000 / ctx->getDelayNS(path.second.path_delay);
+ else
+ Fmax = 500 / ctx->getDelayNS(path.second.path_delay);
+ if (!clock_fmax.count(a.clock) || Fmax < clock_fmax.at(a.clock)) {
+ clock_reports[a.clock] = path;
+ clock_fmax[a.clock] = Fmax;
+ }
+ }
+
+ for (auto &path : crit_paths) {
+ const ClockEvent &a = path.first.start;
+ const ClockEvent &b = path.first.end;
+ if (a.clock == b.clock && a.clock != ctx->id("$async$"))
+ continue;
+ xclock_paths.push_back(path.first);
+ }
+
+ if (clock_reports.empty()) {
+ log_warning("No clocks found in design");
+ }
+
+ std::sort(xclock_paths.begin(), xclock_paths.end(), [ctx](const ClockPair &a, const ClockPair &b) {
+ if (a.start.clock.str(ctx) < b.start.clock.str(ctx))
+ return true;
+ if (a.start.clock.str(ctx) > b.start.clock.str(ctx))
+ return false;
+ if (a.start.edge < b.start.edge)
+ return true;
+ if (a.start.edge > b.start.edge)
+ return false;
+ if (a.end.clock.str(ctx) < b.end.clock.str(ctx))
+ return true;
+ if (a.end.clock.str(ctx) > b.end.clock.str(ctx))
+ return false;
+ if (a.end.edge < b.end.edge)
+ return true;
+ return false;
+ });
+ }
if (print_path) {
- if (crit_path.empty()) {
- log_info("Design contains no timing paths\n");
- } else {
+ auto print_path_report = [ctx](ClockPair &clocks, PortRefVector &crit_path) {
delay_t total = 0;
- log_break();
- log_info("Critical path report:\n");
- log_info("curr total\n");
-
auto &front = crit_path.front();
auto &front_port = front->cell->ports.at(front->port);
auto &front_driver = front_port.net->driver;
- IdString last_port;
- ctx->getPortTimingClass(front_driver.cell, front_driver.port, last_port);
+ int port_clocks;
+ auto portClass = ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks);
+ IdString last_port = front_driver.port;
+ if (portClass == TMG_REGISTER_OUTPUT) {
+ for (int i = 0; i < port_clocks; i++) {
+ TimingClockingInfo clockInfo = ctx->getPortClockingInfo(front_driver.cell, front_driver.port, i);
+ const NetInfo *clknet = get_net_or_empty(front_driver.cell, clockInfo.clock_port);
+ if (clknet != nullptr && clknet->name == clocks.start.clock &&
+ clockInfo.edge == clocks.start.edge) {
+ last_port = clockInfo.clock_port;
+ }
+ }
+ }
+
+ log_info("curr total\n");
for (auto sink : crit_path) {
auto sink_cell = sink->cell;
auto &port = sink_cell->ports.at(sink->port);
@@ -396,7 +618,12 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
auto &driver = net->driver;
auto driver_cell = driver.cell;
DelayInfo comb_delay;
- ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
+ if (last_port == driver.port) {
+ // Case where we start with a STARTPOINT etc
+ comb_delay = ctx->getDelayFromNS(0);
+ } else {
+ ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
+ }
total += comb_delay.maxDelay();
log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()), ctx->getDelayNS(total),
driver_cell->name.c_str(ctx), driver.port.c_str(ctx));
@@ -427,12 +654,63 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
}
last_port = sink->port;
}
+ };
+
+ for (auto &clock : clock_reports) {
log_break();
+ std::string start = clock.second.first.start.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge");
+ std::string end = clock.second.first.end.edge == FALLING_EDGE ? std::string("negedge") : std::string("posedge");
+ log_info("Critical path report for clock '%s' (%s -> %s):\n", clock.first.c_str(ctx), start.c_str(), end.c_str());
+ auto &crit_path = clock.second.second.ports;
+ print_path_report(clock.second.first, crit_path);
+ }
+
+ for (auto &xclock : xclock_paths) {
+ log_break();
+ std::string start = format_event(xclock.start);
+ std::string end = format_event(xclock.end);
+ log_info("Critical path report for cross-domain path '%s' -> '%s':\n", start.c_str(), end.c_str());
+ auto &crit_path = crit_paths.at(xclock).ports;
+ print_path_report(xclock, crit_path);
}
}
+ if (print_fmax) {
+ log_break();
+ unsigned max_width = 0;
+ for (auto &clock : clock_reports)
+ max_width = std::max<unsigned>(max_width, clock.first.str(ctx).size());
+ for (auto &clock : clock_reports) {
+ const auto &clock_name = clock.first.str(ctx);
+ const int width = max_width - clock_name.size();
+ if (ctx->nets.at(clock.first)->clkconstr) {
+ float target = 1000 / ctx->getDelayNS(ctx->nets.at(clock.first)->clkconstr->period.minDelay());
+ log_info("Max frequency for clock %*s'%s': %.02f MHz (%s at %.02f MHz)\n", width, "", clock_name.c_str(),
+ clock_fmax[clock.first], (target < clock_fmax[clock.first]) ? "PASS" : "FAIL", target);
+ } else {
+ log_info("Max frequency for clock %*s'%s': %.02f MHz\n", width, "", clock_name.c_str(), clock_fmax[clock.first]);
+ }
+ }
+ for (auto &eclock : empty_clocks) {
+ if (eclock != ctx->id("$async$"))
+ log_info("Clock '%s' has no interior paths\n", eclock.c_str(ctx));
+ }
+ log_break();
+
+ int start_field_width = 0, end_field_width = 0;
+ for (auto &xclock : xclock_paths) {
+ start_field_width = std::max((int)format_event(xclock.start).length(), start_field_width);
+ end_field_width = std::max((int)format_event(xclock.end).length(), end_field_width);
+ }
- delay_t default_slack = delay_t((1.0e9 / ctx->getDelayNS(1)) / ctx->target_freq);
- log_info("estimated Fmax = %.2f MHz\n", 1e3 / ctx->getDelayNS(default_slack - min_slack));
+ for (auto &xclock : xclock_paths) {
+ const ClockEvent &a = xclock.start;
+ const ClockEvent &b = xclock.end;
+ auto &path = crit_paths.at(xclock);
+ auto ev_a = format_event(a, start_field_width), ev_b = format_event(b, end_field_width);
+ log_info("Max delay %s -> %s: %0.02f ns\n", ev_a.c_str(), ev_b.c_str(), ctx->getDelayNS(path.path_delay));
+ }
+ log_break();
+ }
if (print_histogram && slack_histogram.size() > 0) {
unsigned num_bins = 20;