Working on multi-clock analysis

Signed-off-by: David Shah <dave@ds0.me>
This commit is contained in:
David Shah 2018-11-02 16:56:53 +00:00
parent 122771cac3
commit 9687f7d1da
4 changed files with 273 additions and 188 deletions

View File

@ -51,13 +51,15 @@ void IdString::initialize_add(const BaseCtx *ctx, const char *s, int idx)
ctx->idstring_idx_to_str->push_back(&insert_rc.first->first); ctx->idstring_idx_to_str->push_back(&insert_rc.first->first);
} }
TimingConstrObjectId BaseCtx::timingWildcardObject() { TimingConstrObjectId BaseCtx::timingWildcardObject()
{
TimingConstrObjectId id; TimingConstrObjectId id;
id.index = 0; id.index = 0;
return id; return id;
} }
TimingConstrObjectId BaseCtx::timingClockDomainObject(NetInfo *clockDomain) { TimingConstrObjectId BaseCtx::timingClockDomainObject(NetInfo *clockDomain)
{
NPNR_ASSERT(clockDomain->clkconstr != nullptr); NPNR_ASSERT(clockDomain->clkconstr != nullptr);
if (clockDomain->clkconstr->domain_tmg_id != TimingConstrObjectId()) { if (clockDomain->clkconstr->domain_tmg_id != TimingConstrObjectId()) {
return clockDomain->clkconstr->domain_tmg_id; return clockDomain->clkconstr->domain_tmg_id;
@ -74,7 +76,8 @@ TimingConstrObjectId BaseCtx::timingClockDomainObject(NetInfo *clockDomain) {
} }
} }
TimingConstrObjectId BaseCtx::timingNetObject(NetInfo *net) { TimingConstrObjectId BaseCtx::timingNetObject(NetInfo *net)
{
if (net->tmg_id != TimingConstrObjectId()) { if (net->tmg_id != TimingConstrObjectId()) {
return net->tmg_id; return net->tmg_id;
} else { } else {
@ -90,7 +93,8 @@ TimingConstrObjectId BaseCtx::timingNetObject(NetInfo *net) {
} }
} }
TimingConstrObjectId BaseCtx::timingCellObject(CellInfo *cell) { TimingConstrObjectId BaseCtx::timingCellObject(CellInfo *cell)
{
if (cell->tmg_id != TimingConstrObjectId()) { if (cell->tmg_id != TimingConstrObjectId()) {
return cell->tmg_id; return cell->tmg_id;
} else { } else {
@ -106,7 +110,8 @@ TimingConstrObjectId BaseCtx::timingCellObject(CellInfo *cell) {
} }
} }
TimingConstrObjectId BaseCtx::timingPortObject(CellInfo *cell, IdString port) { TimingConstrObjectId BaseCtx::timingPortObject(CellInfo *cell, IdString port)
{
if (cell->ports.at(port).tmg_id != TimingConstrObjectId()) { if (cell->ports.at(port).tmg_id != TimingConstrObjectId()) {
return cell->ports.at(port).tmg_id; return cell->ports.at(port).tmg_id;
} else { } else {
@ -123,7 +128,8 @@ TimingConstrObjectId BaseCtx::timingPortObject(CellInfo *cell, IdString port) {
} }
} }
void BaseCtx::addConstraint(std::unique_ptr<TimingConstraint> constr) { void BaseCtx::addConstraint(std::unique_ptr<TimingConstraint> constr)
{
for (auto fromObj : constr->from) for (auto fromObj : constr->from)
constrsFrom.emplace(fromObj, constr.get()); constrsFrom.emplace(fromObj, constr.get());
for (auto toObj : constr->to) for (auto toObj : constr->to)
@ -132,7 +138,8 @@ void BaseCtx::addConstraint(std::unique_ptr<TimingConstraint> constr) {
constraints[name] = std::move(constr); constraints[name] = std::move(constr);
} }
void BaseCtx::removeConstraint(IdString constrName) { void BaseCtx::removeConstraint(IdString constrName)
{
TimingConstraint *constr = constraints[constrName].get(); TimingConstraint *constr = constraints[constrName].get();
for (auto fromObj : constr->from) { for (auto fromObj : constr->from) {
auto fromConstrs = constrsFrom.equal_range(fromObj); auto fromConstrs = constrsFrom.equal_range(fromObj);

View File

@ -360,14 +360,16 @@ enum TimingPortClass
TMG_IGNORE, // Asynchronous to all clocks, "don't care", and should be ignored (false path) for analysis TMG_IGNORE, // Asynchronous to all clocks, "don't care", and should be ignored (false path) for analysis
}; };
enum ClockEdge
{
RISING_EDGE,
FALLING_EDGE
};
struct TimingClockingInfo struct TimingClockingInfo
{ {
IdString clock_port; // Port name of clock domain IdString clock_port; // Port name of clock domain
enum ClockEdge edge;
{
RISING,
FALLING
} edge;
DelayInfo setup, hold; // Input timing checks DelayInfo setup, hold; // Input timing checks
DelayInfo clockToQ; // Output clock-to-Q time DelayInfo clockToQ; // Output clock-to-Q time
}; };

View File

@ -22,6 +22,7 @@
#include <algorithm> #include <algorithm>
#include <boost/range/adaptor/reversed.hpp> #include <boost/range/adaptor/reversed.hpp>
#include <deque> #include <deque>
#include <map>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include "log.h" #include "log.h"
@ -30,80 +31,83 @@
NEXTPNR_NAMESPACE_BEGIN NEXTPNR_NAMESPACE_BEGIN
namespace { namespace {
struct ClockEvent { struct ClockEvent
IdString clock; {
enum { IdString clock;
POSEDGE, ClockEdge edge;
NEGEDGE };
} edge;
};
struct ClockPair { struct ClockPair
ClockEvent start, end; {
}; ClockEvent start, end;
} };
} // namespace
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END
namespace std { namespace std {
template<> template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>
struct hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent> { {
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockEvent &obj) const noexcept { 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)); std::size_t seed = 0;
boost::hash_combine(seed, hash<int>()(int(obj.edge))); boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX IdString>()(obj.clock));
return seed; boost::hash_combine(seed, hash<int>()(int(obj.edge)));
} return seed;
}; }
};
template<> template <> struct hash<NEXTPNR_NAMESPACE_PREFIX ClockPair>
struct hash<NEXTPNR_NAMESPACE_PREFIX ClockPair> { {
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX ClockPair &obj) const noexcept { 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)); 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; boost::hash_combine(seed, hash<NEXTPNR_NAMESPACE_PREFIX ClockEvent>()(obj.start));
} return seed;
}; }
};
} } // namespace std
NEXTPNR_NAMESPACE_BEGIN NEXTPNR_NAMESPACE_BEGIN
typedef std::vector<const PortRef *> PortRefVector; typedef std::vector<const PortRef *> PortRefVector;
typedef std::map<int, unsigned> DelayFrequency; 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 struct Timing
{ {
Context *ctx; Context *ctx;
bool net_delays; bool net_delays;
bool update; bool update;
delay_t min_slack; delay_t min_slack;
PortRefVector *crit_path; CriticalPathMap *crit_path;
DelayFrequency *slack_histogram; DelayFrequency *slack_histogram;
IdString async_clock;
struct ClockDomain
{
IdString net;
enum {
RISING,
FALLING
} edge;
};
struct TimingData struct TimingData
{ {
TimingData() : max_arrival(), max_path_length(), min_remaining_budget() {} TimingData() : max_arrival(), max_path_length(), min_remaining_budget() {}
TimingData(ClockPair dest, delay_t max_arrival) : max_path_length(), min_remaining_budget() {} TimingData(delay_t max_arrival) : max_arrival(max_arrival), max_path_length(), min_remaining_budget() {}
std::unordedelay_t max_arrival; delay_t max_arrival;
unsigned max_path_length = 0; unsigned max_path_length = 0;
delay_t min_remaining_budget; delay_t min_remaining_budget;
bool false_startpoint = false; 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) DelayFrequency *slack_histogram = nullptr)
: ctx(ctx), net_delays(net_delays), update(update), min_slack(1.0e12 / ctx->target_freq), : 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$"))
{ {
} }
@ -133,21 +137,26 @@ struct Timing
} }
for (auto o : output_ports) { for (auto o : output_ports) {
IdString clockPort; int clocks = 0;
TimingPortClass portClass = ctx->getPortTimingClass(cell.second.get(), o->name, clockPort); 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 // If output port is influenced by a clock (e.g. FF output) then add it to the ordering as a timing
// start-point // start-point
if (portClass == TMG_REGISTER_OUTPUT) { if (portClass == TMG_REGISTER_OUTPUT) {
DelayInfo clkToQ;
ctx->getCellDelay(cell.second.get(), clockPort, o->name, clkToQ);
topographical_order.emplace_back(o->net); topographical_order.emplace_back(o->net);
net_data[o->net][ClockEvent{IdString(), ClockEvent::POSEDGE}] = 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 { } else {
if (portClass == TMG_STARTPOINT || portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE) { if (portClass == TMG_STARTPOINT || portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE) {
topographical_order.emplace_back(o->net); topographical_order.emplace_back(o->net);
TimingData td; TimingData td;
td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE); td.false_startpoint = (portClass == TMG_GEN_CLOCK || portClass == TMG_IGNORE);
net_data[o->net][ClockEvent{IdString(), ClockEvent::POSEDGE}] = td; net_data[o->net][ClockEvent{async_clock, RISING_EDGE}] = td;
} }
// Otherwise, for all driven input ports on this cell, if a timing arc exists between the input and // 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 // the current output port, increment fanin counter
@ -169,14 +178,15 @@ struct Timing
queue.pop_front(); queue.pop_front();
for (auto &usr : net->users) { for (auto &usr : net->users) {
IdString clockPort; int user_clocks;
TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort); TimingPortClass usrClass = ctx->getPortTimingClass(usr.cell, usr.port, user_clocks);
if (usrClass == TMG_IGNORE || usrClass == TMG_CLOCK_INPUT) if (usrClass == TMG_IGNORE || usrClass == TMG_CLOCK_INPUT)
continue; continue;
for (auto &port : usr.cell->ports) { for (auto &port : usr.cell->ports) {
if (port.second.type != PORT_OUT || !port.second.net) if (port.second.type != PORT_OUT || !port.second.net)
continue; 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) // Skip if this is a clocked output (but allow non-clocked ones)
if (portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT || portClass == TMG_IGNORE || if (portClass == TMG_REGISTER_OUTPUT || portClass == TMG_STARTPOINT || portClass == TMG_IGNORE ||
@ -231,13 +241,15 @@ struct Timing
const auto net_length_plus_one = nd.max_path_length + 1; const auto net_length_plus_one = nd.max_path_length + 1;
nd.min_remaining_budget = clk_period; nd.min_remaining_budget = clk_period;
for (auto &usr : net->users) { for (auto &usr : net->users) {
IdString clockPort; int port_clocks;
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort); TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
auto usr_arrival = net_arrival + net_delay;
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) { if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) {
// Skip
} else { } else {
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); 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 // Iterate over all output ports on the same cell as the sink
for (auto port : usr.cell->ports) { for (auto port : usr.cell->ports) {
if (port.second.type != PORT_OUT || !port.second.net) if (port.second.type != PORT_OUT || !port.second.net)
@ -259,59 +271,93 @@ struct Timing
} }
} }
} }
} }
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 // Now go backwards topographically to determine the minimum path slack, and to distribute all path slack evenly
// between all nets on the path // between all nets on the path
for (auto net : boost::adaptors::reverse(topographical_order)) { for (auto net : boost::adaptors::reverse(topographical_order)) {
auto &nd = net_data.at(net); auto &nd_map = net_data.at(net);
// Ignore false startpoints for (auto &startdomain : nd_map) {
if (nd.false_startpoint) auto &nd = startdomain.second;
continue; // Ignore false startpoints
const delay_t net_length_plus_one = nd.max_path_length + 1; if (nd.false_startpoint)
auto &net_min_remaining_budget = nd.min_remaining_budget; continue;
for (auto &usr : net->users) { const delay_t net_length_plus_one = nd.max_path_length + 1;
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); auto &net_min_remaining_budget = nd.min_remaining_budget;
auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); for (auto &usr : net->users) {
IdString associatedClock; auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, associatedClock); auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) { int port_clocks;
const auto net_arrival = nd.max_arrival; TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
auto path_budget = clk_period - (net_arrival + net_delay); if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) {
if (update) { auto process_endpoint = [&](IdString clksig, ClockEdge edge, delay_t setup) {
auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; const auto net_arrival = nd.max_arrival;
usr.budget = std::min(usr.budget, net_delay + budget_share); const auto endpoint_arrival = net_arrival + net_delay + setup;
net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share); auto path_budget = clk_period - endpoint_arrival;
} delay_t period;
if (path_budget < min_slack) { if (edge == startdomain.first.edge) {
min_slack = path_budget; period = clk_period;
if (crit_path) { } else {
crit_path->clear(); period = clk_period / 2;
crit_path->push_back(&usr); }
crit_net = net;
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 = clk_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 ? RISING_EDGE : clkInfo.edge, clkInfo.setup.maxDelay());
}
} else {
process_endpoint(async_clock, RISING_EDGE, 0);
}
} 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).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);
} }
} }
} }
@ -319,47 +365,52 @@ struct Timing
if (crit_path) { if (crit_path) {
// Walk backwards from the most critical net // Walk backwards from the most critical net
while (crit_net) { for (auto crit_pair : crit_nets) {
const PortInfo *crit_ipin = nullptr; NetInfo *crit_net = crit_pair.second.second;
delay_t max_arrival = std::numeric_limits<delay_t>::min(); 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 // Look at all input ports on its driving cell
for (const auto &port : crit_net->driver.cell->ports) { for (const auto &port : crit_net->driver.cell->ports) {
if (port.second.type != PORT_IN || !port.second.net) if (port.second.type != PORT_IN || !port.second.net)
continue; continue;
DelayInfo comb_delay; DelayInfo comb_delay;
bool is_path = bool is_path =
ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay); ctx->getCellDelay(crit_net->driver.cell, port.first, crit_net->driver.port, comb_delay);
if (!is_path) if (!is_path)
continue; continue;
// If input port is influenced by a clock, skip // If input port is influenced by a clock, skip
IdString portClock; int port_clocks;
TimingPortClass portClass = ctx->getPortTimingClass(crit_net->driver.cell, port.first, portClock); TimingPortClass portClass =
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT || portClass == TMG_ENDPOINT || ctx->getPortTimingClass(crit_net->driver.cell, port.first, port_clocks);
portClass == TMG_IGNORE) if (portClass == TMG_REGISTER_INPUT || portClass == TMG_CLOCK_INPUT ||
continue; portClass == TMG_ENDPOINT || portClass == TMG_IGNORE)
continue;
// And find the fanin net with the latest arrival time // And find the fanin net with the latest arrival time
const auto net_arrival = net_data.at(port.second.net).max_arrival; const auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival;
if (net_arrival > max_arrival) { if (net_arrival > max_arrival) {
max_arrival = net_arrival; max_arrival = net_arrival;
crit_ipin = &port.second; crit_ipin = &port.second;
}
} }
}
if (!crit_ipin) 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);
break; 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; return min_slack;
} }
@ -422,56 +473,83 @@ void assign_budget(Context *ctx, bool quiet)
void timing_analysis(Context *ctx, bool print_histogram, bool print_path) void timing_analysis(Context *ctx, bool print_histogram, bool print_path)
{ {
PortRefVector crit_path; CriticalPathMap crit_paths;
DelayFrequency slack_histogram; 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 ? &crit_paths : nullptr,
print_histogram ? &slack_histogram : nullptr); print_histogram ? &slack_histogram : nullptr);
auto min_slack = timing.walk_paths(); auto min_slack = timing.walk_paths();
if (print_path) { if (print_path) {
if (crit_path.empty()) { std::map<IdString, std::pair<ClockPair, CriticalPath>> clock_reports;
log_info("Design contains no timing paths\n"); 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;
delay_t slack = path.second.path_period - path.second.path_delay;
if (!clock_reports.count(a.clock) ||
slack < (clock_reports.at(a.clock).second.path_period - clock_reports.at(a.clock).second.path_delay)) {
clock_reports[a.clock] = path;
}
}
if (clock_reports.empty()) {
log_warning("No clocks found in design");
} else { } else {
delay_t total = 0; delay_t total = 0;
log_break(); log_break();
log_info("Critical path report:\n"); for (auto &clock : clock_reports) {
log_info("curr total\n"); log_info("Critical path report for clock '%s':\n", clock.first.c_str(ctx));
log_info("curr total\n");
auto &crit_path = clock.second.second.ports;
auto &front = crit_path.front();
auto &front_port = front->cell->ports.at(front->port);
auto &front_driver = front_port.net->driver;
auto &front = crit_path.front(); int port_clocks;
auto &front_port = front->cell->ports.at(front->port); ctx->getPortTimingClass(front_driver.cell, front_driver.port, port_clocks);
auto &front_driver = front_port.net->driver; 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 == clock.first &&
clockInfo.edge == clock.second.first.start.edge) {
IdString last_port = clockInfo.clock_port;
IdString last_port; for (auto sink : crit_path) {
ctx->getPortTimingClass(front_driver.cell, front_driver.port, last_port); auto sink_cell = sink->cell;
for (auto sink : crit_path) { auto &port = sink_cell->ports.at(sink->port);
auto sink_cell = sink->cell; auto net = port.net;
auto &port = sink_cell->ports.at(sink->port); auto &driver = net->driver;
auto net = port.net; auto driver_cell = driver.cell;
auto &driver = net->driver; DelayInfo comb_delay;
auto driver_cell = driver.cell; ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay);
DelayInfo comb_delay; total += comb_delay.maxDelay();
ctx->getCellDelay(sink_cell, last_port, driver.port, comb_delay); log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()),
total += comb_delay.maxDelay(); ctx->getDelayNS(total), driver_cell->name.c_str(ctx), driver.port.c_str(ctx));
log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()), ctx->getDelayNS(total), auto net_delay = ctx->getNetinfoRouteDelay(net, *sink);
driver_cell->name.c_str(ctx), driver.port.c_str(ctx)); total += net_delay;
auto net_delay = ctx->getNetinfoRouteDelay(net, *sink); auto driver_loc = ctx->getBelLocation(driver_cell->bel);
total += net_delay; auto sink_loc = ctx->getBelLocation(sink_cell->bel);
auto driver_loc = ctx->getBelLocation(driver_cell->bel); log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n",
auto sink_loc = ctx->getBelLocation(sink_cell->bel); ctx->getDelayNS(net_delay), ctx->getDelayNS(total), net->name.c_str(ctx),
log_info("%4.1f %4.1f Net %s budget %f ns (%d,%d) -> (%d,%d)\n", ctx->getDelayNS(net_delay), ctx->getDelayNS(sink->budget), driver_loc.x, driver_loc.y, sink_loc.x, sink_loc.y);
ctx->getDelayNS(total), net->name.c_str(ctx), ctx->getDelayNS(sink->budget), driver_loc.x, log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx));
driver_loc.y, sink_loc.x, sink_loc.y); last_port = sink->port;
log_info(" Sink %s.%s\n", sink_cell->name.c_str(ctx), sink->port.c_str(ctx)); }
last_port = sink->port; }
}
log_break();
double Fmax;
if (clock.second.first.start.edge == clock.second.first.end.edge)
Fmax = 1000 / ctx->getDelayNS(clock.second.second.path_delay);
else
Fmax = 500 / ctx->getDelayNS(clock.second.second.path_delay);
log_info("Max frequency for clock '%s': %.02f MHz\n", clock.first.c_str(ctx), Fmax);
log_break();
} }
log_break();
} }
} }
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));
if (print_histogram && slack_histogram.size() > 0) { if (print_histogram && slack_histogram.size() > 0) {
unsigned num_bins = 20; unsigned num_bins = 20;
unsigned bar_width = 60; unsigned bar_width = 60;

View File

@ -936,7 +936,7 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port
TimingClockingInfo info; TimingClockingInfo info;
if (cell->type == id_ICESTORM_LC) { if (cell->type == id_ICESTORM_LC) {
info.clock_port = id_CLK; info.clock_port = id_CLK;
info.edge = cell->lcInfo.negClk ? TimingClockingInfo::FALLING : TimingClockingInfo::RISING; info.edge = cell->lcInfo.negClk ? FALLING_EDGE : RISING_EDGE;
if (port == id_O) { if (port == id_O) {
bool has_clktoq = getCellDelay(cell, id_CLK, id_O, info.clockToQ); bool has_clktoq = getCellDelay(cell, id_CLK, id_O, info.clockToQ);
NPNR_ASSERT(has_clktoq); NPNR_ASSERT(has_clktoq);
@ -947,12 +947,10 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port
} else if (cell->type == id_ICESTORM_RAM) { } else if (cell->type == id_ICESTORM_RAM) {
if (port.str(this)[0] == 'R') { if (port.str(this)[0] == 'R') {
info.clock_port = id_RCLK; info.clock_port = id_RCLK;
info.edge = bool_or_default(cell->params, id("NEG_CLK_R")) ? TimingClockingInfo::FALLING info.edge = bool_or_default(cell->params, id("NEG_CLK_R")) ? FALLING_EDGE : RISING_EDGE;
: TimingClockingInfo::RISING;
} else { } else {
info.clock_port = id_WCLK; info.clock_port = id_WCLK;
info.edge = bool_or_default(cell->params, id("NEG_CLK_W")) ? TimingClockingInfo::FALLING info.edge = bool_or_default(cell->params, id("NEG_CLK_W")) ? FALLING_EDGE : RISING_EDGE;
: TimingClockingInfo::RISING;
} }
if (cell->ports.at(port).type == PORT_OUT) { if (cell->ports.at(port).type == PORT_OUT) {
bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ); bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ);
@ -963,7 +961,7 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port
} }
} else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) { } else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) {
info.clock_port = id_CLK; info.clock_port = id_CLK;
info.edge = TimingClockingInfo::RISING; info.edge = RISING_EDGE;
if (cell->ports.at(port).type == PORT_OUT) { if (cell->ports.at(port).type == PORT_OUT) {
bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ); bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ);
if (!has_clktoq) if (!has_clktoq)