aboutsummaryrefslogtreecommitdiffstats
path: root/common/timing.cc
diff options
context:
space:
mode:
Diffstat (limited to 'common/timing.cc')
-rw-r--r--common/timing.cc706
1 files changed, 543 insertions, 163 deletions
diff --git a/common/timing.cc b/common/timing.cc
index a61c0beb..8229f143 100644
--- a/common/timing.cc
+++ b/common/timing.cc
@@ -30,6 +30,547 @@
NEXTPNR_NAMESPACE_BEGIN
+void TimingAnalyser::setup()
+{
+ init_ports();
+ get_cell_delays();
+ topo_sort();
+ setup_port_domains();
+ run();
+}
+
+void TimingAnalyser::run()
+{
+ reset_times();
+ get_route_delays();
+ walk_forward();
+ walk_backward();
+ compute_slack();
+ compute_criticality();
+}
+
+void TimingAnalyser::init_ports()
+{
+ // Per cell port structures
+ for (auto cell : sorted(ctx->cells)) {
+ CellInfo *ci = cell.second;
+ for (auto port : sorted_ref(ci->ports)) {
+ auto &data = ports[CellPortKey(ci->name, port.first)];
+ data.type = port.second.type;
+ data.cell_port = CellPortKey(ci->name, port.first);
+ }
+ }
+ // Cell port to net port mapping
+ for (auto net : sorted(ctx->nets)) {
+ NetInfo *ni = net.second;
+ if (ni->driver.cell != nullptr)
+ ports[CellPortKey(ni->driver)].net_port = NetPortKey(ni->name);
+ for (size_t i = 0; i < ni->users.size(); i++)
+ ports[CellPortKey(ni->users.at(i))].net_port = NetPortKey(ni->name, i);
+ }
+}
+
+void TimingAnalyser::get_cell_delays()
+{
+ for (auto &port : ports) {
+ CellInfo *ci = cell_info(port.first);
+ auto &pi = port_info(port.first);
+ auto &pd = port.second;
+
+ IdString name = port.first.port;
+ // Ignore dangling ports altogether for timing purposes
+ if (pd.net_port.net == IdString())
+ continue;
+ pd.cell_arcs.clear();
+ int clkInfoCount = 0;
+ TimingPortClass cls = ctx->getPortTimingClass(ci, name, clkInfoCount);
+ if (cls == TMG_STARTPOINT || cls == TMG_ENDPOINT || cls == TMG_CLOCK_INPUT || cls == TMG_GEN_CLOCK ||
+ cls == TMG_IGNORE)
+ continue;
+ if (pi.type == PORT_IN) {
+ // Input ports might have setup/hold relationships
+ if (cls == TMG_REGISTER_INPUT) {
+ for (int i = 0; i < clkInfoCount; i++) {
+ auto info = ctx->getPortClockingInfo(ci, name, i);
+ if (!ci->ports.count(info.clock_port) || ci->ports.at(info.clock_port).net == nullptr)
+ continue;
+ pd.cell_arcs.emplace_back(CellArc::SETUP, info.clock_port, DelayQuad(info.setup, info.setup),
+ info.edge);
+ pd.cell_arcs.emplace_back(CellArc::HOLD, info.clock_port, DelayQuad(info.hold, info.hold),
+ info.edge);
+ }
+ }
+ // Combinational delays through cell
+ for (auto &other_port : ci->ports) {
+ auto &op = other_port.second;
+ // ignore dangling ports and non-outputs
+ if (op.net == nullptr || op.type != PORT_OUT)
+ continue;
+ DelayQuad delay;
+ bool is_path = ctx->getCellDelay(ci, name, other_port.first, delay);
+ if (is_path)
+ pd.cell_arcs.emplace_back(CellArc::COMBINATIONAL, other_port.first, delay);
+ }
+ } else if (pi.type == PORT_OUT) {
+ // Output ports might have clk-to-q relationships
+ if (cls == TMG_REGISTER_OUTPUT) {
+ for (int i = 0; i < clkInfoCount; i++) {
+ auto info = ctx->getPortClockingInfo(ci, name, i);
+ if (!ci->ports.count(info.clock_port) || ci->ports.at(info.clock_port).net == nullptr)
+ continue;
+ pd.cell_arcs.emplace_back(CellArc::CLK_TO_Q, info.clock_port, info.clockToQ, info.edge);
+ }
+ }
+ // Combinational delays through cell
+ for (auto &other_port : ci->ports) {
+ auto &op = other_port.second;
+ // ignore dangling ports and non-inputs
+ if (op.net == nullptr || op.type != PORT_IN)
+ continue;
+ DelayQuad delay;
+ bool is_path = ctx->getCellDelay(ci, other_port.first, name, delay);
+ if (is_path)
+ pd.cell_arcs.emplace_back(CellArc::COMBINATIONAL, other_port.first, delay);
+ }
+ }
+ }
+}
+
+void TimingAnalyser::get_route_delays()
+{
+ for (auto net : sorted(ctx->nets)) {
+ NetInfo *ni = net.second;
+ if (ni->driver.cell == nullptr || ni->driver.cell->bel == BelId())
+ continue;
+ for (auto &usr : ni->users) {
+ if (usr.cell->bel == BelId())
+ continue;
+ ports.at(CellPortKey(usr)).route_delay = DelayPair(ctx->getNetinfoRouteDelay(ni, usr));
+ }
+ }
+}
+
+void TimingAnalyser::topo_sort()
+{
+ TopoSort<CellPortKey> topo;
+ for (auto &port : ports) {
+ auto &pd = port.second;
+ // All ports are nodes
+ topo.node(port.first);
+ if (pd.type == PORT_IN) {
+ // inputs: combinational arcs through the cell are edges
+ for (auto &arc : pd.cell_arcs) {
+ if (arc.type != CellArc::COMBINATIONAL)
+ continue;
+ topo.edge(port.first, CellPortKey(port.first.cell, arc.other_port));
+ }
+ } else if (pd.type == PORT_OUT) {
+ // output: routing arcs are edges
+ const NetInfo *pn = port_info(port.first).net;
+ if (pn != nullptr) {
+ for (auto &usr : pn->users)
+ topo.edge(port.first, CellPortKey(usr));
+ }
+ }
+ }
+ bool no_loops = topo.sort();
+ if (!no_loops && verbose_mode) {
+ log_info("Found %d combinational loops:\n", int(topo.loops.size()));
+ int i = 0;
+ for (auto &loop : topo.loops) {
+ log_info(" loop %d:\n", ++i);
+ for (auto &port : loop) {
+ log_info(" %s.%s (%s)\n", ctx->nameOf(port.cell), ctx->nameOf(port.port),
+ ctx->nameOf(port_info(port).net));
+ }
+ }
+ }
+ std::swap(topological_order, topo.sorted);
+}
+
+void TimingAnalyser::setup_port_domains()
+{
+ for (auto &d : domains) {
+ d.startpoints.clear();
+ d.endpoints.clear();
+ }
+ // Go forward through the topological order (domains from the PoV of arrival time)
+ for (auto port : topological_order) {
+ auto &pd = ports.at(port);
+ auto &pi = port_info(port);
+ if (pi.type == PORT_OUT) {
+ for (auto &fanin : pd.cell_arcs) {
+ if (fanin.type != CellArc::CLK_TO_Q)
+ continue;
+ // registered outputs are startpoints
+ auto dom = domain_id(port.cell, fanin.other_port, fanin.edge);
+ // create per-domain data
+ pd.arrival[dom];
+ domains.at(dom).startpoints.emplace_back(port, fanin.other_port);
+ }
+ // copy domains across routing
+ if (pi.net != nullptr)
+ for (auto &usr : pi.net->users)
+ copy_domains(port, CellPortKey(usr), false);
+ } else {
+ // copy domains from input to output
+ for (auto &fanout : pd.cell_arcs) {
+ if (fanout.type != CellArc::COMBINATIONAL)
+ continue;
+ copy_domains(port, CellPortKey(port.cell, fanout.other_port), false);
+ }
+ }
+ }
+ // Go backward through the topological order (domains from the PoV of required time)
+ for (auto port : reversed_range(topological_order)) {
+ auto &pd = ports.at(port);
+ auto &pi = port_info(port);
+ if (pi.type == PORT_OUT) {
+ // copy domains from output to input
+ for (auto &fanin : pd.cell_arcs) {
+ if (fanin.type != CellArc::COMBINATIONAL)
+ continue;
+ copy_domains(port, CellPortKey(port.cell, fanin.other_port), true);
+ }
+ } else {
+ for (auto &fanout : pd.cell_arcs) {
+ if (fanout.type != CellArc::SETUP)
+ continue;
+ // registered inputs are startpoints
+ auto dom = domain_id(port.cell, fanout.other_port, fanout.edge);
+ // create per-domain data
+ pd.required[dom];
+ domains.at(dom).endpoints.emplace_back(port, fanout.other_port);
+ }
+ // copy port to driver
+ if (pi.net != nullptr && pi.net->driver.cell != nullptr)
+ copy_domains(port, CellPortKey(pi.net->driver), true);
+ }
+ }
+ // Iterate over ports and find domain paris
+ for (auto port : topological_order) {
+ auto &pd = ports.at(port);
+ for (auto &arr : pd.arrival)
+ for (auto &req : pd.required) {
+ pd.domain_pairs[domain_pair_id(arr.first, req.first)];
+ }
+ }
+}
+
+void TimingAnalyser::reset_times()
+{
+ for (auto &port : ports) {
+ auto do_reset = [&](std::unordered_map<domain_id_t, ArrivReqTime> &times) {
+ for (auto &t : times) {
+ t.second.value = init_delay;
+ t.second.path_length = 0;
+ t.second.bwd_min = CellPortKey();
+ t.second.bwd_max = CellPortKey();
+ }
+ };
+ do_reset(port.second.arrival);
+ do_reset(port.second.required);
+ for (auto &dp : port.second.domain_pairs) {
+ dp.second.setup_slack = std::numeric_limits<delay_t>::max();
+ dp.second.hold_slack = std::numeric_limits<delay_t>::max();
+ dp.second.max_path_length = 0;
+ dp.second.criticality = 0;
+ dp.second.budget = 0;
+ }
+ port.second.worst_crit = 0;
+ port.second.worst_setup_slack = std::numeric_limits<delay_t>::max();
+ port.second.worst_hold_slack = std::numeric_limits<delay_t>::max();
+ }
+}
+
+void TimingAnalyser::set_arrival_time(CellPortKey target, domain_id_t domain, DelayPair arrival, int path_length,
+ CellPortKey prev)
+{
+ auto &arr = ports.at(target).arrival.at(domain);
+ if (arrival.max_delay > arr.value.max_delay) {
+ arr.value.max_delay = arrival.max_delay;
+ arr.bwd_max = prev;
+ }
+ if (!setup_only && (arrival.min_delay < arr.value.min_delay)) {
+ arr.value.min_delay = arrival.min_delay;
+ arr.bwd_min = prev;
+ }
+ arr.path_length = std::max(arr.path_length, path_length);
+}
+
+void TimingAnalyser::set_required_time(CellPortKey target, domain_id_t domain, DelayPair required, int path_length,
+ CellPortKey prev)
+{
+ auto &req = ports.at(target).required.at(domain);
+ if (required.min_delay < req.value.min_delay) {
+ req.value.min_delay = required.min_delay;
+ req.bwd_min = prev;
+ }
+ if (!setup_only && (required.max_delay > req.value.max_delay)) {
+ req.value.max_delay = required.max_delay;
+ req.bwd_max = prev;
+ }
+ req.path_length = std::max(req.path_length, path_length);
+}
+
+void TimingAnalyser::walk_forward()
+{
+ // Assign initial arrival time to domain startpoints
+ for (domain_id_t dom_id = 0; dom_id < domain_id_t(domains.size()); ++dom_id) {
+ auto &dom = domains.at(dom_id);
+ for (auto &sp : dom.startpoints) {
+ auto &pd = ports.at(sp.first);
+ DelayPair init_arrival(0);
+ CellPortKey clock_key;
+ // TODO: clock routing delay, if analysis of that is enabled
+ if (sp.second != IdString()) {
+ // clocked startpoints have a clock-to-out time
+ for (auto &fanin : pd.cell_arcs) {
+ if (fanin.type == CellArc::CLK_TO_Q && fanin.other_port == sp.second) {
+ init_arrival = init_arrival + fanin.value.delayPair();
+ break;
+ }
+ }
+ clock_key = CellPortKey(sp.first.cell, sp.second);
+ }
+ set_arrival_time(sp.first, dom_id, init_arrival, 1, clock_key);
+ }
+ }
+ // Walk forward in topological order
+ for (auto p : topological_order) {
+ auto &pd = ports.at(p);
+ for (auto &arr : pd.arrival) {
+ if (pd.type == PORT_OUT) {
+ // Output port: propagate delay through net, adding route delay
+ NetInfo *net = port_info(p).net;
+ if (net != nullptr)
+ for (auto &usr : net->users) {
+ CellPortKey usr_key(usr);
+ auto &usr_pd = ports.at(usr_key);
+ set_arrival_time(usr_key, arr.first, arr.second.value + usr_pd.route_delay,
+ arr.second.path_length, p);
+ }
+ } else if (pd.type == PORT_IN) {
+ // Input port; propagate delay through cell, adding combinational delay
+ for (auto &fanout : pd.cell_arcs) {
+ if (fanout.type != CellArc::COMBINATIONAL)
+ continue;
+ set_arrival_time(CellPortKey(p.cell, fanout.other_port), arr.first,
+ arr.second.value + fanout.value.delayPair(), arr.second.path_length + 1, p);
+ }
+ }
+ }
+ }
+}
+
+void TimingAnalyser::walk_backward()
+{
+ // Assign initial required time to domain endpoints
+ // Note that clock frequency will be considered later in the analysis for, for now all required times are normalised
+ // to 0ns
+ for (domain_id_t dom_id = 0; dom_id < domain_id_t(domains.size()); ++dom_id) {
+ auto &dom = domains.at(dom_id);
+ for (auto &ep : dom.endpoints) {
+ auto &pd = ports.at(ep.first);
+ DelayPair init_setuphold(0);
+ CellPortKey clock_key;
+ // TODO: clock routing delay, if analysis of that is enabled
+ if (ep.second != IdString()) {
+ // Add setup/hold time, if this endpoint is clocked
+ for (auto &fanin : pd.cell_arcs) {
+ if (fanin.type == CellArc::SETUP && fanin.other_port == ep.second)
+ init_setuphold.min_delay -= fanin.value.maxDelay();
+ if (fanin.type == CellArc::HOLD && fanin.other_port == ep.second)
+ init_setuphold.max_delay -= fanin.value.maxDelay();
+ }
+ clock_key = CellPortKey(ep.first.cell, ep.second);
+ }
+ set_required_time(ep.first, dom_id, init_setuphold, 1, clock_key);
+ }
+ }
+ // Walk backwards in topological order
+ for (auto p : reversed_range(topological_order)) {
+ auto &pd = ports.at(p);
+ for (auto &req : pd.required) {
+ if (pd.type == PORT_IN) {
+ // Input port: propagate delay back through net, subtracting route delay
+ NetInfo *net = port_info(p).net;
+ if (net != nullptr && net->driver.cell != nullptr)
+ set_required_time(CellPortKey(net->driver), req.first, req.second.value - pd.route_delay,
+ req.second.path_length, p);
+ } else if (pd.type == PORT_OUT) {
+ // Output port : propagate delay back through cell, subtracting combinational delay
+ for (auto &fanin : pd.cell_arcs) {
+ if (fanin.type != CellArc::COMBINATIONAL)
+ continue;
+ set_required_time(CellPortKey(p.cell, fanin.other_port), req.first,
+ req.second.value - fanin.value.delayPair(), req.second.path_length + 1, p);
+ }
+ }
+ }
+ }
+}
+
+void TimingAnalyser::print_fmax()
+{
+ // Temporary testing code for comparison only
+ std::unordered_map<int, double> domain_fmax;
+ for (auto p : topological_order) {
+ auto &pd = ports.at(p);
+ for (auto &req : pd.required) {
+ if (pd.arrival.count(req.first)) {
+ auto &arr = pd.arrival.at(req.first);
+ double fmax = 1000.0 / ctx->getDelayNS(arr.value.maxDelay() - req.second.value.minDelay());
+ if (!domain_fmax.count(req.first) || domain_fmax.at(req.first) > fmax)
+ domain_fmax[req.first] = fmax;
+ }
+ }
+ }
+ for (auto &fm : domain_fmax) {
+ log_info("Domain %s Worst Fmax %.02f\n", ctx->nameOf(domains.at(fm.first).key.clock), fm.second);
+ }
+}
+
+void TimingAnalyser::compute_slack()
+{
+ for (auto &dp : domain_pairs) {
+ dp.worst_setup_slack = std::numeric_limits<delay_t>::max();
+ dp.worst_hold_slack = std::numeric_limits<delay_t>::max();
+ }
+ for (auto p : topological_order) {
+ auto &pd = ports.at(p);
+ for (auto &pdp : pd.domain_pairs) {
+ auto &dp = domain_pairs.at(pdp.first);
+ auto &arr = pd.arrival.at(dp.key.launch);
+ auto &req = pd.required.at(dp.key.capture);
+ pdp.second.setup_slack = dp.period.minDelay() - (arr.value.maxDelay() - req.value.minDelay());
+ if (!setup_only)
+ pdp.second.hold_slack = arr.value.minDelay() - req.value.maxDelay();
+ pdp.second.max_path_length = arr.path_length + req.path_length;
+ pd.worst_setup_slack = std::min(pd.worst_setup_slack, pdp.second.setup_slack);
+ dp.worst_setup_slack = std::min(dp.worst_setup_slack, pdp.second.setup_slack);
+ if (!setup_only) {
+ pd.worst_hold_slack = std::min(pd.worst_hold_slack, pdp.second.hold_slack);
+ dp.worst_hold_slack = std::min(dp.worst_hold_slack, pdp.second.hold_slack);
+ }
+ }
+ }
+}
+
+void TimingAnalyser::compute_criticality()
+{
+ for (auto p : topological_order) {
+ auto &pd = ports.at(p);
+ for (auto &pdp : pd.domain_pairs) {
+ auto &dp = domain_pairs.at(pdp.first);
+ float crit =
+ 1.0f - (float(pdp.second.setup_slack) - float(dp.worst_setup_slack)) / float(-dp.worst_setup_slack);
+ crit = std::min(crit, 1.0f);
+ crit = std::max(crit, 0.0f);
+ pdp.second.criticality = crit;
+ pd.worst_crit = std::max(pd.worst_crit, crit);
+ }
+ }
+}
+
+std::vector<CellPortKey> TimingAnalyser::get_failing_eps(domain_id_t domain_pair, int count)
+{
+ std::vector<CellPortKey> failing_eps;
+ delay_t last_slack = std::numeric_limits<delay_t>::min();
+ auto &dp = domain_pairs.at(domain_pair);
+ auto &cap_d = domains.at(dp.key.capture);
+ while (int(failing_eps.size()) < count) {
+ CellPortKey next;
+ delay_t next_slack = std::numeric_limits<delay_t>::max();
+ for (auto ep : cap_d.endpoints) {
+ auto &pd = ports.at(ep.first);
+ if (!pd.domain_pairs.count(domain_pair))
+ continue;
+ delay_t ep_slack = pd.domain_pairs.at(domain_pair).setup_slack;
+ if (ep_slack < next_slack && ep_slack > last_slack) {
+ next = ep.first;
+ next_slack = ep_slack;
+ }
+ }
+ if (next == CellPortKey())
+ break;
+ failing_eps.push_back(next);
+ last_slack = next_slack;
+ }
+ return failing_eps;
+}
+
+void TimingAnalyser::print_critical_path(CellPortKey endpoint, domain_id_t domain_pair)
+{
+ CellPortKey cursor = endpoint;
+ auto &dp = domain_pairs.at(domain_pair);
+ log(" endpoint %s.%s (slack %.02fns):\n", ctx->nameOf(cursor.cell), ctx->nameOf(cursor.port),
+ ctx->getDelayNS(ports.at(cursor).domain_pairs.at(domain_pair).setup_slack));
+ while (cursor != CellPortKey()) {
+ log(" %s.%s (net %s)\n", ctx->nameOf(cursor.cell), ctx->nameOf(cursor.port),
+ ctx->nameOf(get_net_or_empty(ctx->cells.at(cursor.cell).get(), cursor.port)));
+ if (!ports.at(cursor).arrival.count(dp.key.launch))
+ break;
+ cursor = ports.at(cursor).arrival.at(dp.key.launch).bwd_max;
+ }
+}
+
+namespace {
+const char *edge_name(ClockEdge edge) { return (edge == FALLING_EDGE) ? "negedge" : "posedge"; }
+} // namespace
+
+void TimingAnalyser::print_report()
+{
+ for (int i = 0; i < int(domain_pairs.size()); i++) {
+ auto &dp = domain_pairs.at(i);
+ auto &launch = domains.at(dp.key.launch);
+ auto &capture = domains.at(dp.key.capture);
+ log("Worst endpoints for %s %s -> %s %s\n", edge_name(launch.key.edge), ctx->nameOf(launch.key.clock),
+ edge_name(capture.key.edge), ctx->nameOf(capture.key.clock));
+ auto failing_eps = get_failing_eps(i, 5);
+ for (auto &ep : failing_eps)
+ print_critical_path(ep, i);
+ log_break();
+ }
+}
+
+domain_id_t TimingAnalyser::domain_id(IdString cell, IdString clock_port, ClockEdge edge)
+{
+ return domain_id(ctx->cells.at(cell)->ports.at(clock_port).net, edge);
+}
+domain_id_t TimingAnalyser::domain_id(const NetInfo *net, ClockEdge edge)
+{
+ NPNR_ASSERT(net != nullptr);
+ ClockDomainKey key{net->name, edge};
+ auto inserted = domain_to_id.emplace(key, domains.size());
+ if (inserted.second) {
+ domains.emplace_back(key);
+ }
+ return inserted.first->second;
+}
+domain_id_t TimingAnalyser::domain_pair_id(domain_id_t launch, domain_id_t capture)
+{
+ ClockDomainPairKey key{launch, capture};
+ auto inserted = pair_to_id.emplace(key, domain_pairs.size());
+ if (inserted.second) {
+ domain_pairs.emplace_back(key);
+ }
+ return inserted.first->second;
+}
+
+void TimingAnalyser::copy_domains(const CellPortKey &from, const CellPortKey &to, bool backward)
+{
+ auto &f = ports.at(from), &t = ports.at(to);
+ for (auto &dom : (backward ? f.required : f.arrival))
+ (backward ? t.required : t.arrival)[dom.first];
+}
+
+CellInfo *TimingAnalyser::cell_info(const CellPortKey &key) { return ctx->cells.at(key.cell).get(); }
+
+PortInfo &TimingAnalyser::port_info(const CellPortKey &key) { return ctx->cells.at(key.cell)->ports.at(key.port); }
+
+/** LEGACY CODE BEGIN **/
+
namespace {
struct ClockEvent
{
@@ -86,7 +627,6 @@ struct CriticalPath
};
typedef std::unordered_map<ClockPair, CriticalPath> CriticalPathMap;
-typedef std::unordered_map<IdString, NetCriticalityInfo> NetCriticalityMap;
struct Timing
{
@@ -96,7 +636,6 @@ struct Timing
delay_t min_slack;
CriticalPathMap *crit_path;
DelayFrequency *slack_histogram;
- NetCriticalityMap *net_crit;
IdString async_clock;
struct TimingData
@@ -112,10 +651,9 @@ struct Timing
};
Timing(Context *ctx, bool net_delays, bool update, CriticalPathMap *crit_path = nullptr,
- DelayFrequency *slack_histogram = nullptr, NetCriticalityMap *net_crit = nullptr)
+ DelayFrequency *slack_histogram = nullptr)
: ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->setting<float>("target_freq")),
- crit_path(crit_path), slack_histogram(slack_histogram), net_crit(net_crit),
- async_clock(ctx->id("$async$"))
+ crit_path(crit_path), slack_histogram(slack_histogram), async_clock(ctx->id("$async$"))
{
}
@@ -496,156 +1034,6 @@ struct Timing
std::reverse(cp_ports.begin(), cp_ports.end());
}
}
-
- if (net_crit) {
- NPNR_ASSERT(crit_path);
- // Go through in reverse topological order to set required times
- for (auto net : boost::adaptors::reverse(topological_order)) {
- if (!net_data.count(net))
- continue;
- auto &nd_map = net_data.at(net);
- for (auto &startdomain : nd_map) {
- auto &nd = startdomain.second;
- if (nd.false_startpoint)
- continue;
- if (startdomain.first.clock == async_clock)
- continue;
- if (nd.min_required.empty())
- nd.min_required.resize(net->users.size(), std::numeric_limits<delay_t>::max());
- delay_t net_min_required = std::numeric_limits<delay_t>::max();
- for (size_t i = 0; i < net->users.size(); i++) {
- auto &usr = net->users.at(i);
- auto net_delay = ctx->getNetinfoRouteDelay(net, usr);
- 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) {
- 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();
- }
- }
- }
- nd.min_required.at(i) = std::min(period - setup, nd.min_required.at(i));
- };
- if (portClass == TMG_REGISTER_INPUT) {
- for (int j = 0; j < port_clocks; j++) {
- TimingClockingInfo clkInfo = ctx->getPortClockingInfo(usr.cell, usr.port, j);
- 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);
- }
- }
- net_min_required = std::min(net_min_required, nd.min_required.at(i) - net_delay);
- }
- PortRef &drv = net->driver;
- if (drv.cell == nullptr)
- continue;
- for (const auto &port : drv.cell->ports) {
- if (port.second.type != PORT_IN || !port.second.net)
- continue;
- DelayQuad comb_delay;
- bool is_path = ctx->getCellDelay(drv.cell, port.first, drv.port, comb_delay);
- if (!is_path)
- continue;
- int cc;
- auto pclass = ctx->getPortTimingClass(drv.cell, port.first, cc);
- if (pclass != TMG_COMB_INPUT)
- continue;
- NetInfo *sink_net = port.second.net;
- if (net_data.count(sink_net) && net_data.at(sink_net).count(startdomain.first)) {
- auto &sink_nd = net_data.at(sink_net).at(startdomain.first);
- if (sink_nd.min_required.empty())
- sink_nd.min_required.resize(sink_net->users.size(),
- std::numeric_limits<delay_t>::max());
- for (size_t i = 0; i < sink_net->users.size(); i++) {
- auto &user = sink_net->users.at(i);
- if (user.cell == drv.cell && user.port == port.first) {
- sink_nd.min_required.at(i) = std::min(sink_nd.min_required.at(i),
- net_min_required - comb_delay.maxDelay());
- break;
- }
- }
- }
- }
- }
- }
- std::unordered_map<ClockEvent, delay_t> worst_slack;
-
- // Assign slack values
- for (auto &net_entry : net_data) {
- const NetInfo *net = net_entry.first;
- for (auto &startdomain : net_entry.second) {
- auto &nd = startdomain.second;
- if (startdomain.first.clock == async_clock)
- continue;
- if (nd.min_required.empty())
- continue;
- auto &nc = (*net_crit)[net->name];
- if (nc.slack.empty())
- nc.slack.resize(net->users.size(), std::numeric_limits<delay_t>::max());
-
- for (size_t i = 0; i < net->users.size(); i++) {
- delay_t slack = nd.min_required.at(i) -
- (nd.max_arrival + ctx->getNetinfoRouteDelay(net, net->users.at(i)));
-
- if (worst_slack.count(startdomain.first))
- worst_slack.at(startdomain.first) = std::min(worst_slack.at(startdomain.first), slack);
- else
- worst_slack[startdomain.first] = slack;
- nc.slack.at(i) = slack;
- }
- if (ctx->debug)
- log_break();
- }
- }
- // Assign criticality values
- for (auto &net_entry : net_data) {
- const NetInfo *net = net_entry.first;
- for (auto &startdomain : net_entry.second) {
- if (startdomain.first.clock == async_clock)
- continue;
- auto &nd = startdomain.second;
- if (nd.min_required.empty())
- continue;
- auto &nc = (*net_crit)[net->name];
- if (nc.slack.empty())
- continue;
- if (nc.criticality.empty())
- nc.criticality.resize(net->users.size(), 0);
- // Only consider intra-clock paths for criticality
- if (!crit_path->count(ClockPair{startdomain.first, startdomain.first}))
- continue;
- delay_t dmax = crit_path->at(ClockPair{startdomain.first, startdomain.first}).path_delay;
- for (size_t i = 0; i < net->users.size(); i++) {
- float criticality =
- 1.0f - ((float(nc.slack.at(i)) - float(worst_slack.at(startdomain.first))) / dmax);
- nc.criticality.at(i) = std::min<double>(1.0, std::max<double>(0.0, criticality));
- }
- nc.max_path_length = nd.max_path_length;
- nc.cd_worst_slack = worst_slack.at(startdomain.first);
- }
- }
- }
return min_slack;
}
@@ -999,12 +1387,4 @@ void timing_analysis(Context *ctx, bool print_histogram, bool print_fmax, bool p
}
}
-void get_criticalities(Context *ctx, NetCriticalityMap *net_crit)
-{
- CriticalPathMap crit_paths;
- net_crit->clear();
- Timing timing(ctx, true, true, &crit_paths, nullptr, net_crit);
- timing.walk_paths();
-}
-
NEXTPNR_NAMESPACE_END