Working on multi-clock analysis
Signed-off-by: David Shah <dave@ds0.me>
This commit is contained in:
parent
122771cac3
commit
9687f7d1da
@ -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);
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
418
common/timing.cc
418
common/timing.cc
@ -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;
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user