Merge pull request #103 from YosysHQ/timingapi

Timing constraints API, multiple clock domains
This commit is contained in:
David Shah 2018-11-15 11:26:08 +00:00 committed by GitHub
commit 9472b6d78f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 880 additions and 179 deletions

View File

@ -53,6 +53,107 @@ 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 id;
id.index = 0;
return id;
}
TimingConstrObjectId BaseCtx::timingClockDomainObject(NetInfo *clockDomain)
{
NPNR_ASSERT(clockDomain->clkconstr != nullptr);
if (clockDomain->clkconstr->domain_tmg_id != TimingConstrObjectId()) {
return clockDomain->clkconstr->domain_tmg_id;
} else {
TimingConstraintObject obj;
TimingConstrObjectId id;
id.index = int(constraintObjects.size());
obj.id = id;
obj.type = TimingConstraintObject::CLOCK_DOMAIN;
obj.entity = clockDomain->name;
clockDomain->clkconstr->domain_tmg_id = id;
constraintObjects.push_back(obj);
return id;
}
}
TimingConstrObjectId BaseCtx::timingNetObject(NetInfo *net)
{
if (net->tmg_id != TimingConstrObjectId()) {
return net->tmg_id;
} else {
TimingConstraintObject obj;
TimingConstrObjectId id;
id.index = int(constraintObjects.size());
obj.id = id;
obj.type = TimingConstraintObject::NET;
obj.entity = net->name;
constraintObjects.push_back(obj);
net->tmg_id = id;
return id;
}
}
TimingConstrObjectId BaseCtx::timingCellObject(CellInfo *cell)
{
if (cell->tmg_id != TimingConstrObjectId()) {
return cell->tmg_id;
} else {
TimingConstraintObject obj;
TimingConstrObjectId id;
id.index = int(constraintObjects.size());
obj.id = id;
obj.type = TimingConstraintObject::CELL;
obj.entity = cell->name;
constraintObjects.push_back(obj);
cell->tmg_id = id;
return id;
}
}
TimingConstrObjectId BaseCtx::timingPortObject(CellInfo *cell, IdString port)
{
if (cell->ports.at(port).tmg_id != TimingConstrObjectId()) {
return cell->ports.at(port).tmg_id;
} else {
TimingConstraintObject obj;
TimingConstrObjectId id;
id.index = int(constraintObjects.size());
obj.id = id;
obj.type = TimingConstraintObject::CELL_PORT;
obj.entity = cell->name;
obj.port = port;
constraintObjects.push_back(obj);
cell->ports.at(port).tmg_id = id;
return id;
}
}
void BaseCtx::addConstraint(std::unique_ptr<TimingConstraint> constr)
{
for (auto fromObj : constr->from)
constrsFrom.emplace(fromObj, constr.get());
for (auto toObj : constr->to)
constrsTo.emplace(toObj, constr.get());
IdString name = constr->name;
constraints[name] = std::move(constr);
}
void BaseCtx::removeConstraint(IdString constrName)
{
TimingConstraint *constr = constraints[constrName].get();
for (auto fromObj : constr->from) {
auto fromConstrs = constrsFrom.equal_range(fromObj);
constrsFrom.erase(std::find(fromConstrs.first, fromConstrs.second, std::make_pair(fromObj, constr)));
}
for (auto toObj : constr->to) {
auto toConstrs = constrsFrom.equal_range(toObj);
constrsFrom.erase(std::find(toConstrs.first, toConstrs.second, std::make_pair(toObj, constr)));
}
constraints.erase(constrName);
}
const char *BaseCtx::nameOfBel(BelId bel) const const char *BaseCtx::nameOfBel(BelId bel) const
{ {
const Context *ctx = getCtx(); const Context *ctx = getCtx();
@ -306,4 +407,14 @@ void Context::check() const
} }
} }
void BaseCtx::addClock(IdString net, float freq)
{
log_info("constraining clock net '%s' to %.02f MHz\n", net.c_str(this), freq);
std::unique_ptr<ClockConstraint> cc(new ClockConstraint());
cc->period = getCtx()->getDelayFromNS(1000 / freq);
cc->high = getCtx()->getDelayFromNS(500 / freq);
cc->low = getCtx()->getDelayFromNS(500 / freq);
nets.at(net)->clkconstr = std::move(cc);
}
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END

View File

@ -194,6 +194,14 @@ struct Loc
bool operator!=(const Loc &other) const { return (x != other.x) || (y != other.y) || (z != other.z); } bool operator!=(const Loc &other) const { return (x != other.x) || (y != other.y) || (z != other.z); }
}; };
struct TimingConstrObjectId
{
int32_t index = -1;
bool operator==(const TimingConstrObjectId &other) const { return index == other.index; }
bool operator!=(const TimingConstrObjectId &other) const { return index != other.index; }
};
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END
namespace std { namespace std {
@ -208,6 +216,15 @@ template <> struct hash<NEXTPNR_NAMESPACE_PREFIX Loc>
return seed; return seed;
} }
}; };
template <> struct hash<NEXTPNR_NAMESPACE_PREFIX TimingConstrObjectId>
{
std::size_t operator()(const NEXTPNR_NAMESPACE_PREFIX TimingConstrObjectId &obj) const noexcept
{
return hash<int>()(obj.index);
}
};
} // namespace std } // namespace std
#include "archdefs.h" #include "archdefs.h"
@ -266,6 +283,8 @@ struct PipMap
PlaceStrength strength = STRENGTH_NONE; PlaceStrength strength = STRENGTH_NONE;
}; };
struct ClockConstraint;
struct NetInfo : ArchNetInfo struct NetInfo : ArchNetInfo
{ {
IdString name; IdString name;
@ -278,6 +297,10 @@ struct NetInfo : ArchNetInfo
// wire -> uphill_pip // wire -> uphill_pip
std::unordered_map<WireId, PipMap> wires; std::unordered_map<WireId, PipMap> wires;
std::unique_ptr<ClockConstraint> clkconstr;
TimingConstrObjectId tmg_id;
Region *region = nullptr; Region *region = nullptr;
}; };
@ -293,6 +316,7 @@ struct PortInfo
IdString name; IdString name;
NetInfo *net; NetInfo *net;
PortType type; PortType type;
TimingConstrObjectId tmg_id;
}; };
struct CellInfo : ArchCellInfo struct CellInfo : ArchCellInfo
@ -320,6 +344,7 @@ struct CellInfo : ArchCellInfo
// parent.[xyz] := 0 when (constr_parent == nullptr) // parent.[xyz] := 0 when (constr_parent == nullptr)
Region *region = nullptr; Region *region = nullptr;
TimingConstrObjectId tmg_id;
}; };
enum TimingPortClass enum TimingPortClass
@ -335,6 +360,68 @@ 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
{
IdString clock_port; // Port name of clock domain
ClockEdge edge;
DelayInfo setup, hold; // Input timing checks
DelayInfo clockToQ; // Output clock-to-Q time
};
struct ClockConstraint
{
DelayInfo high;
DelayInfo low;
DelayInfo period;
TimingConstrObjectId domain_tmg_id;
};
struct TimingConstraintObject
{
TimingConstrObjectId id;
enum
{
ANYTHING,
CLOCK_DOMAIN,
NET,
CELL,
CELL_PORT
} type;
IdString entity; // Name of clock net; net or cell
IdString port; // Name of port on a cell
};
struct TimingConstraint
{
IdString name;
enum
{
FALSE_PATH,
MIN_DELAY,
MAX_DELAY,
MULTICYCLE,
} type;
delay_t value;
std::unordered_set<TimingConstrObjectId> from;
std::unordered_set<TimingConstrObjectId> to;
};
inline bool operator==(const std::pair<const TimingConstrObjectId, TimingConstraint *> &a,
const std::pair<TimingConstrObjectId, TimingConstraint *> &b)
{
return a.first == b.first && a.second == b.second;
}
struct DeterministicRNG struct DeterministicRNG
{ {
uint64_t rngstate; uint64_t rngstate;
@ -431,6 +518,11 @@ struct BaseCtx
idstring_idx_to_str = new std::vector<const std::string *>; idstring_idx_to_str = new std::vector<const std::string *>;
IdString::initialize_add(this, "", 0); IdString::initialize_add(this, "", 0);
IdString::initialize_arch(this); IdString::initialize_arch(this);
TimingConstraintObject wildcard;
wildcard.id.index = 0;
wildcard.type = TimingConstraintObject::ANYTHING;
constraintObjects.push_back(wildcard);
} }
~BaseCtx() ~BaseCtx()
@ -524,6 +616,30 @@ struct BaseCtx
void refreshUiPip(PipId pip) { pipUiReload.insert(pip); } void refreshUiPip(PipId pip) { pipUiReload.insert(pip); }
void refreshUiGroup(GroupId group) { groupUiReload.insert(group); } void refreshUiGroup(GroupId group) { groupUiReload.insert(group); }
// --------------------------------------------------------------
// Timing Constraint API
// constraint name -> constraint
std::unordered_map<IdString, std::unique_ptr<TimingConstraint>> constraints;
// object ID -> object
std::vector<TimingConstraintObject> constraintObjects;
// object ID -> constraint
std::unordered_multimap<TimingConstrObjectId, TimingConstraint *> constrsFrom;
std::unordered_multimap<TimingConstrObjectId, TimingConstraint *> constrsTo;
TimingConstrObjectId timingWildcardObject();
TimingConstrObjectId timingClockDomainObject(NetInfo *clockDomain);
TimingConstrObjectId timingNetObject(NetInfo *net);
TimingConstrObjectId timingCellObject(CellInfo *cell);
TimingConstrObjectId timingPortObject(CellInfo *cell, IdString port);
void addConstraint(std::unique_ptr<TimingConstraint> constr);
void removeConstraint(IdString constrName);
// Intended to simplify Python API
void addClock(IdString net, float freq);
}; };
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END

View File

@ -36,8 +36,8 @@ wirelen_t get_net_metric(const Context *ctx, const NetInfo *net, MetricType type
bool driver_gb = ctx->getBelGlobalBuf(driver_cell->bel); bool driver_gb = ctx->getBelGlobalBuf(driver_cell->bel);
if (driver_gb) if (driver_gb)
return 0; return 0;
IdString clock_port; int clock_count;
bool timing_driven = ctx->timing_driven && type == MetricType::COST && ctx->getPortTimingClass(driver_cell, net->driver.port, clock_port) != TMG_IGNORE; bool timing_driven = ctx->timing_driven && type == MetricType::COST && ctx->getPortTimingClass(driver_cell, net->driver.port, clock_count) != TMG_IGNORE;
delay_t negative_slack = 0; delay_t negative_slack = 0;
delay_t worst_slack = std::numeric_limits<delay_t>::max(); delay_t worst_slack = std::numeric_limits<delay_t>::max();
Loc driver_loc = ctx->getBelLocation(driver_cell->bel); Loc driver_loc = ctx->getBelLocation(driver_cell->bel);

View File

@ -808,7 +808,7 @@ bool router1(Context *ctx, const Router1Cfg &cfg)
#endif #endif
log_info("Checksum: 0x%08x\n", ctx->checksum()); log_info("Checksum: 0x%08x\n", ctx->checksum());
timing_analysis(ctx, true /* slack_histogram */, true /* print_path */); timing_analysis(ctx, true /* slack_histogram */, true /* print_fmax */, true /* print_path */);
ctx->unlock(); ctx->unlock();
return true; return true;

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"
@ -29,17 +30,72 @@
NEXTPNR_NAMESPACE_BEGIN 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::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 TimingData struct TimingData
{ {
@ -49,23 +105,24 @@ struct Timing
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$"))
{ {
} }
delay_t walk_paths() 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 // 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 // TODO(eddieh): Handle the case where it is cyclic, e.g. combinatorial loops
std::vector<NetInfo *> topographical_order; 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 // 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; std::unordered_map<const PortInfo *, unsigned> port_fanin;
@ -84,22 +141,34 @@ 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.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 { } 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.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 // 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
for (auto i : input_ports) { for (auto i : input_ports) {
@ -120,14 +189,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 ||
@ -174,89 +244,157 @@ struct Timing
// Go forwards topographically to find the maximum arrival time and max path length for each net // Go forwards topographically to find the maximum arrival time and max path length for each net
for (auto net : topographical_order) { for (auto net : topographical_order) {
auto &nd = net_data.at(net); if (!net_data.count(net))
const auto net_arrival = nd.max_arrival; continue;
const auto net_length_plus_one = nd.max_path_length + 1; auto &nd_map = net_data.at(net);
nd.min_remaining_budget = clk_period; for (auto &startdomain : nd_map) {
for (auto &usr : net->users) { ClockEvent start_clk = startdomain.first;
IdString clockPort; auto &nd = startdomain.second;
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, clockPort); if (nd.false_startpoint)
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE) { continue;
} else { 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 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; 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 (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT || portClass == TMG_IGNORE ||
if (port.second.type != PORT_OUT || !port.second.net) portClass == TMG_CLOCK_INPUT) {
continue; // Skip
DelayInfo comb_delay; } else {
// Look up delay through this path auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay); // Iterate over all output ports on the same cell as the sink
if (!is_path) for (auto port : usr.cell->ports) {
continue; if (port.second.type != PORT_OUT || !port.second.net)
auto &data = net_data[port.second.net]; continue;
auto &arrival = data.max_arrival; DelayInfo comb_delay;
arrival = std::max(arrival, usr_arrival + comb_delay.maxDelay()); // Look up delay through this path
if (!budget_override) { // Do not increment path length if budget overriden since it doesn't bool is_path = ctx->getCellDelay(usr.cell, usr.port, port.first, comb_delay);
// require a share of the slack if (!is_path)
auto &path_length = data.max_path_length; continue;
path_length = std::max(path_length, net_length_plus_one); 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 // 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); if (!net_data.count(net))
// Ignore false startpoints continue;
if (nd.false_startpoint) continue; auto &nd_map = net_data.at(net);
const delay_t net_length_plus_one = nd.max_path_length + 1; for (auto &startdomain : nd_map) {
auto &net_min_remaining_budget = nd.min_remaining_budget; auto &nd = startdomain.second;
for (auto &usr : net->users) { // Ignore false startpoints
auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t(); if (nd.false_startpoint)
auto budget_override = ctx->getBudgetOverride(net, usr, net_delay); continue;
IdString associatedClock; const delay_t net_length_plus_one = nd.max_path_length + 1;
TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, associatedClock); auto &net_min_remaining_budget = nd.min_remaining_budget;
if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) { for (auto &usr : net->users) {
const auto net_arrival = nd.max_arrival; auto net_delay = net_delays ? ctx->getNetinfoRouteDelay(net, usr) : delay_t();
auto path_budget = clk_period - (net_arrival + net_delay); auto budget_override = ctx->getBudgetOverride(net, usr, net_delay);
if (update) { int port_clocks;
auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one; TimingPortClass portClass = ctx->getPortTimingClass(usr.cell, usr.port, port_clocks);
usr.budget = std::min(usr.budget, net_delay + budget_share); if (portClass == TMG_REGISTER_INPUT || portClass == TMG_ENDPOINT) {
net_min_remaining_budget = std::min(net_min_remaining_budget, path_budget - budget_share); 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 (path_budget < min_slack) { if (update) {
min_slack = path_budget; auto budget_share = budget_override ? 0 : path_budget / net_length_plus_one;
if (crit_path) { usr.budget = std::min(usr.budget, net_delay + budget_share);
crit_path->clear(); net_min_remaining_budget =
crit_path->push_back(&usr); std::min(net_min_remaining_budget, path_budget - budget_share);
crit_net = net; }
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);
}
} 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);
} }
} }
} }
@ -264,47 +402,55 @@ 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; if (net_data.count(port.second.net) &&
if (net_arrival > max_arrival) { net_data.at(port.second.net).count(crit_pair.first.start)) {
max_arrival = net_arrival; const auto net_arrival = net_data.at(port.second.net).at(crit_pair.first.start).max_arrival;
crit_ipin = &port.second; if (net_arrival > max_arrival) {
max_arrival = net_arrival;
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;
} }
@ -365,30 +511,106 @@ void assign_budget(Context *ctx, bool quiet)
log_info("Checksum: 0x%08x\n", ctx->checksum()); 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; 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); 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 (print_path) {
if (crit_path.empty()) { auto print_path_report = [ctx](ClockPair &clocks, PortRefVector &crit_path) {
log_info("Design contains no timing paths\n");
} else {
delay_t total = 0; delay_t total = 0;
log_break();
log_info("Critical path report:\n");
log_info("curr total\n");
auto &front = crit_path.front(); auto &front = crit_path.front();
auto &front_port = front->cell->ports.at(front->port); auto &front_port = front->cell->ports.at(front->port);
auto &front_driver = front_port.net->driver; auto &front_driver = front_port.net->driver;
IdString last_port; int port_clocks;
ctx->getPortTimingClass(front_driver.cell, front_driver.port, last_port); 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) { for (auto sink : crit_path) {
auto sink_cell = sink->cell; auto sink_cell = sink->cell;
auto &port = sink_cell->ports.at(sink->port); 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 = net->driver;
auto driver_cell = driver.cell; auto driver_cell = driver.cell;
DelayInfo comb_delay; 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(); total += comb_delay.maxDelay();
log_info("%4.1f %4.1f Source %s.%s\n", ctx->getDelayNS(comb_delay.maxDelay()), ctx->getDelayNS(total), 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)); 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; last_port = sink->port;
} }
};
for (auto &clock : clock_reports) {
log_break(); 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();
delay_t default_slack = delay_t((1.0e9 / ctx->getDelayNS(1)) / ctx->target_freq); int start_field_width = 0, end_field_width = 0;
log_info("estimated Fmax = %.2f MHz\n", 1e3 / ctx->getDelayNS(default_slack - min_slack)); 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);
}
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) { if (print_histogram && slack_histogram.size() > 0) {
unsigned num_bins = 20; unsigned num_bins = 20;

View File

@ -29,7 +29,7 @@ void assign_budget(Context *ctx, bool quiet = false);
// Perform timing analysis and print out the fmax, and optionally the // Perform timing analysis and print out the fmax, and optionally the
// critical path // critical path
void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_path = false); void timing_analysis(Context *ctx, bool slack_histogram = true, bool print_fmax = true, bool print_path = false);
NEXTPNR_NAMESPACE_END NEXTPNR_NAMESPACE_END

View File

@ -404,6 +404,10 @@ actual penalty used is a multiple of this value (i.e. a weighted version of this
Convert an `delay_t` to an actual real-world delay in nanoseconds. Convert an `delay_t` to an actual real-world delay in nanoseconds.
### DelayInfo getDelayFromNS(float v) const
Convert a real-world delay in nanoseconds to a DelayInfo with equal min/max rising/falling values.
### uint32\_t getDelayChecksum(delay\_t v) const ### uint32\_t getDelayChecksum(delay\_t v) const
Convert a `delay_t` to an integer for checksum calculations. Convert a `delay_t` to an integer for checksum calculations.
@ -461,11 +465,17 @@ Cell Delay Methods
Returns the delay for the specified path through a cell in the `&delay` argument. The method returns Returns the delay for the specified path through a cell in the `&delay` argument. The method returns
false if there is no timing relationship from `fromPort` to `toPort`. false if there is no timing relationship from `fromPort` to `toPort`.
### TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const ### TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
Return the _timing port class_ of a port. This can be a register or combinational input or output; clock input or Return the _timing port class_ of a port. This can be a register or combinational input or output; clock input or
output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockPort is set output; general startpoint or endpoint; or a port ignored for timing purposes. For register ports, clockInfoCount is set
to the associated clock port. to the number of associated _clock edges_ that can be queried by getPortClockingInfo.
### TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
Return the _clocking info_ (including port name of clock, clock polarity and setup/hold/clock-to-out times) of a
port. Where ports have more than one clock edge associated with them (such as DDR outputs), `index` can be used to obtain
information for all edges. `index` must be in [0, clockInfoCount), behaviour is undefined otherwise.
Placer Methods Placer Methods
-------------- --------------

37
docs/constraints.md Normal file
View File

@ -0,0 +1,37 @@
# Constraints
There are three types of constraints available for end users of nextpnr.
## Architecture-specific IO Cconstraints
Architectures may provide support for their native (or any other) IO constraint format.
The iCE40 architecture supports PCF constraints thus:
set_io led[0] 3
and the ECP5 architecture supports a subset of LPF constraints:
LOCATE COMP "led[0]" SITE "E16";
IOBUF PORT "led[0]" IO_TYPE=LVCMOS25;
## Absolute Placement Constraints
nextpnr provides generic support for placement constraints by setting the Bel attribute on the cell to the name of
the Bel you wish it to be placed at. For example:
(* BEL="X2/Y5/lc0" *)
## Clock Constraints
There are two ways to apply clock constraints in nextpnr. The `--clock {freq}` command line argument is used to
apply a default frequency (in MHz) to all clocks without a more specific constraint.
The Python API can apply clock constraints to specific named clocks. This is done by passing a Python file
specifying these constraints to the `--pre-pack` command line argument. Inside the file, constraints are applied by
calling the function `ctx.addClock` with the name of the clock and its frequency in MHz, for example:
ctx.addClock("csi_rx_i.dphy_clk", 96)
ctx.addClock("video_clk", 24)
ctx.addClock("uart_i.sys_clk_i", 12)

View File

@ -539,10 +539,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
return true; return true;
} }
#if 0 // FIXME #if 0 // FIXME
if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) { if (fromPort == id_WCK && (toPort == id_F0 || toPort == id_F1)) {
delay.delay = 717; delay.delay = 717;
return true; return true;
} }
#endif #endif
if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) || if ((fromPort == id_A0 && toPort == id_WADO3) || (fromPort == id_A1 && toPort == id_WDO1) ||
(fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) || (fromPort == id_B0 && toPort == id_WADO1) || (fromPort == id_B1 && toPort == id_WDO3) ||
@ -576,10 +576,10 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
} }
} }
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{ {
auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; }; auto disconnected = [cell](IdString p) { return !cell->ports.count(p) || cell->ports.at(p).net == nullptr; };
clockInfoCount = 0;
if (cell->type == id_TRELLIS_SLICE) { if (cell->type == id_TRELLIS_SLICE) {
int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0); int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0);
if (port == id_CLK || port == id_WCK) if (port == id_CLK || port == id_WCK)
@ -598,13 +598,13 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
return TMG_COMB_OUTPUT; return TMG_COMB_OUTPUT;
if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) || if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) ||
(sd1 == 1 && port == id_M1)) { (sd1 == 1 && port == id_M1)) {
clockPort = id_CLK; clockInfoCount = 1;
return TMG_REGISTER_INPUT; return TMG_REGISTER_INPUT;
} }
if (port == id_M0 || port == id_M1) if (port == id_M0 || port == id_M1)
return TMG_COMB_INPUT; return TMG_COMB_INPUT;
if (port == id_Q0 || port == id_Q1) { if (port == id_Q0 || port == id_Q1) {
clockPort = id_CLK; clockInfoCount = 1;
return TMG_REGISTER_OUTPUT; return TMG_REGISTER_OUTPUT;
} }
@ -614,7 +614,7 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 || if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 ||
port == id_WAD3 || port == id_WRE) { port == id_WAD3 || port == id_WRE) {
clockPort = id_WCK; clockInfoCount = 1;
return TMG_REGISTER_INPUT; return TMG_REGISTER_INPUT;
} }
@ -638,10 +638,8 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
for (auto c : boost::adaptors::reverse(port_name)) { for (auto c : boost::adaptors::reverse(port_name)) {
if (std::isdigit(c)) if (std::isdigit(c))
continue; continue;
if (c == 'A') if (c == 'A' || c == 'B')
clockPort = id_CLKA; clockInfoCount = 1;
else if (c == 'B')
clockPort = id_CLKB;
else else
NPNR_ASSERT_FALSE_STR("bad ram port"); NPNR_ASSERT_FALSE_STR("bad ram port");
return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT; return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;
@ -658,6 +656,60 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
} }
} }
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
TimingClockingInfo info;
info.setup.delay = 0;
info.hold.delay = 0;
info.clockToQ.delay = 0;
if (cell->type == id_TRELLIS_SLICE) {
int sd0 = int_or_default(cell->params, id("REG0_SD"), 0), sd1 = int_or_default(cell->params, id("REG1_SD"), 0);
if (port == id_WD0 || port == id_WD1 || port == id_WAD0 || port == id_WAD1 || port == id_WAD2 ||
port == id_WAD3 || port == id_WRE) {
info.edge = RISING_EDGE;
info.clock_port = id_WCK;
info.setup.delay = 100;
info.hold.delay = 0;
} else if (port == id_DI0 || port == id_DI1 || port == id_CE || port == id_LSR || (sd0 == 1 && port == id_M0) ||
(sd1 == 1 && port == id_M1)) {
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
info.clock_port = id_CLK;
info.setup.delay = 100;
info.hold.delay = 0;
} else {
info.edge = cell->sliceInfo.clkmux == id("INV") ? FALLING_EDGE : RISING_EDGE;
info.clock_port = id_CLK;
info.clockToQ.delay = 395;
}
} else if (cell->type == id_DP16KD) {
std::string port_name = port.str(this);
for (auto c : boost::adaptors::reverse(port_name)) {
if (std::isdigit(c))
continue;
if (c == 'A') {
info.clock_port = id_CLKA;
break;
} else if (c == 'B') {
info.clock_port = id_CLKB;
break;
} else
NPNR_ASSERT_FALSE_STR("bad ram port " + port.str(this));
}
info.edge = (str_or_default(cell->params, info.clock_port == id_CLKB ? id("CLKBMUX") : id("CLKAMUX"), "CLK") ==
"INV")
? FALLING_EDGE
: RISING_EDGE;
if (cell->ports.at(port).type == PORT_OUT) {
info.clockToQ.delay = 4280;
} else {
info.setup.delay = 100;
info.hold.delay = 0;
}
}
return info;
}
std::vector<std::pair<std::string, std::string>> Arch::getTilesAtLocation(int row, int col) std::vector<std::pair<std::string, std::string>> Arch::getTilesAtLocation(int row, int col)
{ {
std::vector<std::pair<std::string, std::string>> ret; std::vector<std::pair<std::string, std::string>> ret;

View File

@ -859,6 +859,12 @@ struct Arch : BaseCtx
delay_t getDelayEpsilon() const { return 20; } delay_t getDelayEpsilon() const { return 20; }
delay_t getRipupDelayPenalty() const { return 200; } delay_t getRipupDelayPenalty() const { return 200; }
float getDelayNS(delay_t v) const { return v * 0.001; } float getDelayNS(delay_t v) const { return v * 0.001; }
DelayInfo getDelayFromNS(float ns) const
{
DelayInfo del;
del.delay = delay_t(ns * 1000);
return del;
}
uint32_t getDelayChecksum(delay_t v) const { return v; } uint32_t getDelayChecksum(delay_t v) const { return v; }
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
@ -882,8 +888,10 @@ struct Arch : BaseCtx
// Get the delay through a cell from one port to another, returning false // Get the delay through a cell from one port to another, returning false
// if no path exists // if no path exists
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
// Get the port class, also setting clockPort if applicable // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const; TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
// Get the TimingClockingInfo of a port
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
// Return true if a port is a net // Return true if a port is a net
bool isGlobalNet(const NetInfo *net) const; bool isGlobalNet(const NetInfo *net) const;

View File

@ -130,6 +130,10 @@ void arch_wrap_python()
"cells"); "cells");
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls, readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
"nets"); "nets");
fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>,
pass_through<float>>::def_wrap(ctx_cls, "addClock");
WRAP_RANGE(Bel, conv_to_str<BelId>); WRAP_RANGE(Bel, conv_to_str<BelId>);
WRAP_RANGE(Wire, conv_to_str<WireId>); WRAP_RANGE(Wire, conv_to_str<WireId>);
WRAP_RANGE(AllPip, conv_to_str<PipId>); WRAP_RANGE(AllPip, conv_to_str<PipId>);

View File

@ -350,6 +350,13 @@ class Ecp5GlobalRouter
place_dcc(dcc.get()); place_dcc(dcc.get());
if (net->clkconstr) {
glbnet->clkconstr = std::unique_ptr<ClockConstraint>(new ClockConstraint());
glbnet->clkconstr->low = net->clkconstr->low;
glbnet->clkconstr->high = net->clkconstr->high;
glbnet->clkconstr->period = net->clkconstr->period;
}
ctx->cells[dcc->name] = std::move(dcc); ctx->cells[dcc->name] = std::move(dcc);
NetInfo *glbptr = glbnet.get(); NetInfo *glbptr = glbnet.get();
ctx->nets[glbnet->name] = std::move(glbnet); ctx->nets[glbnet->name] = std::move(glbnet);

View File

@ -463,11 +463,16 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
} }
// Get the port class, also setting clockPort if applicable // Get the port class, also setting clockPort if applicable
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{ {
return TMG_IGNORE; return TMG_IGNORE;
} }
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
NPNR_ASSERT_FALSE("no clocking info for generic");
}
bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; } bool Arch::isValidBelForCell(CellInfo *cell, BelId bel) const { return true; }
bool Arch::isBelLocationValid(BelId bel) const { return true; } bool Arch::isBelLocationValid(BelId bel) const { return true; }

View File

@ -211,6 +211,14 @@ struct Arch : BaseCtx
delay_t getDelayEpsilon() const { return 0.01; } delay_t getDelayEpsilon() const { return 0.01; }
delay_t getRipupDelayPenalty() const { return 1.0; } delay_t getRipupDelayPenalty() const { return 1.0; }
float getDelayNS(delay_t v) const { return v; } float getDelayNS(delay_t v) const { return v; }
DelayInfo getDelayFromNS(float ns) const
{
DelayInfo del;
del.delay = ns;
return del;
}
uint32_t getDelayChecksum(delay_t v) const { return 0; } uint32_t getDelayChecksum(delay_t v) const { return 0; }
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
@ -225,8 +233,10 @@ struct Arch : BaseCtx
DecalXY getGroupDecal(GroupId group) const; DecalXY getGroupDecal(GroupId group) const;
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
// Get the port class, also setting clockPort if applicable // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const; TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
// Get the TimingClockingInfo of a port
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
bool isValidBelForCell(CellInfo *cell, BelId bel) const; bool isValidBelForCell(CellInfo *cell, BelId bel) const;
bool isBelLocationValid(BelId bel) const; bool isBelLocationValid(BelId bel) const;

View File

@ -856,8 +856,9 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort
} }
// Get the port class, also setting clockPort to associated clock if applicable // Get the port class, also setting clockPort to associated clock if applicable
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockPort) const TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{ {
clockInfoCount = 0;
if (cell->type == id_ICESTORM_LC) { if (cell->type == id_ICESTORM_LC) {
if (port == id_CLK) if (port == id_CLK)
return TMG_CLOCK_INPUT; return TMG_CLOCK_INPUT;
@ -870,18 +871,15 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
if (cell->lcInfo.inputCount == 0) if (cell->lcInfo.inputCount == 0)
return TMG_IGNORE; return TMG_IGNORE;
if (cell->lcInfo.dffEnable) { if (cell->lcInfo.dffEnable) {
clockPort = id_CLK; clockInfoCount = 1;
return TMG_REGISTER_OUTPUT; return TMG_REGISTER_OUTPUT;
} } else
else
return TMG_COMB_OUTPUT; return TMG_COMB_OUTPUT;
} } else {
else {
if (cell->lcInfo.dffEnable) { if (cell->lcInfo.dffEnable) {
clockPort = id_CLK; clockInfoCount = 1;
return TMG_REGISTER_INPUT; return TMG_REGISTER_INPUT;
} } else
else
return TMG_COMB_INPUT; return TMG_COMB_INPUT;
} }
} else if (cell->type == id_ICESTORM_RAM) { } else if (cell->type == id_ICESTORM_RAM) {
@ -889,23 +887,22 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
if (port == id_RCLK || port == id_WCLK) if (port == id_RCLK || port == id_WCLK)
return TMG_CLOCK_INPUT; return TMG_CLOCK_INPUT;
if (port.str(this)[0] == 'R') clockInfoCount = 1;
clockPort = id_RCLK;
else
clockPort = id_WCLK;
if (cell->ports.at(port).type == PORT_OUT) if (cell->ports.at(port).type == PORT_OUT)
return TMG_REGISTER_OUTPUT; return TMG_REGISTER_OUTPUT;
else else
return TMG_REGISTER_INPUT; return TMG_REGISTER_INPUT;
} else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) { } else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) {
clockPort = id_CLK; if (port == id_CLK || port == id_CLOCK)
if (port == id_CLK)
return TMG_CLOCK_INPUT; return TMG_CLOCK_INPUT;
else if (cell->ports.at(port).type == PORT_OUT) else {
return TMG_REGISTER_OUTPUT; clockInfoCount = 1;
else if (cell->ports.at(port).type == PORT_OUT)
return TMG_REGISTER_INPUT; return TMG_REGISTER_OUTPUT;
else
return TMG_REGISTER_INPUT;
}
} else if (cell->type == id_SB_IO) { } else if (cell->type == id_SB_IO) {
if (port == id_D_IN_0 || port == id_D_IN_1) if (port == id_D_IN_0 || port == id_D_IN_1)
return TMG_STARTPOINT; return TMG_STARTPOINT;
@ -934,6 +931,51 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, Id
log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this)); log_error("no timing info for port '%s' of cell type '%s'\n", port.c_str(this), cell->type.c_str(this));
} }
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
TimingClockingInfo info;
if (cell->type == id_ICESTORM_LC) {
info.clock_port = id_CLK;
info.edge = cell->lcInfo.negClk ? FALLING_EDGE : RISING_EDGE;
if (port == id_O) {
bool has_clktoq = getCellDelay(cell, id_CLK, id_O, info.clockToQ);
NPNR_ASSERT(has_clktoq);
} else {
info.setup.delay = 100;
info.hold.delay = 0;
}
} else if (cell->type == id_ICESTORM_RAM) {
if (port.str(this)[0] == 'R') {
info.clock_port = id_RCLK;
info.edge = bool_or_default(cell->params, id("NEG_CLK_R")) ? FALLING_EDGE : RISING_EDGE;
} else {
info.clock_port = id_WCLK;
info.edge = bool_or_default(cell->params, id("NEG_CLK_W")) ? FALLING_EDGE : RISING_EDGE;
}
if (cell->ports.at(port).type == PORT_OUT) {
bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ);
NPNR_ASSERT(has_clktoq);
} else {
info.setup.delay = 100;
info.hold.delay = 0;
}
} else if (cell->type == id_ICESTORM_DSP || cell->type == id_ICESTORM_SPRAM) {
info.clock_port = cell->type == id_ICESTORM_SPRAM ? id_CLOCK : id_CLK;
info.edge = RISING_EDGE;
if (cell->ports.at(port).type == PORT_OUT) {
bool has_clktoq = getCellDelay(cell, info.clock_port, port, info.clockToQ);
if (!has_clktoq)
info.clockToQ.delay = 100;
} else {
info.setup.delay = 100;
info.hold.delay = 0;
}
} else {
NPNR_ASSERT_FALSE("unhandled cell type in getPortClockingInfo");
}
return info;
}
bool Arch::isGlobalNet(const NetInfo *net) const bool Arch::isGlobalNet(const NetInfo *net) const
{ {
if (net == nullptr) if (net == nullptr)

View File

@ -796,6 +796,12 @@ struct Arch : BaseCtx
delay_t getDelayEpsilon() const { return 20; } delay_t getDelayEpsilon() const { return 20; }
delay_t getRipupDelayPenalty() const { return 200; } delay_t getRipupDelayPenalty() const { return 200; }
float getDelayNS(delay_t v) const { return v * 0.001; } float getDelayNS(delay_t v) const { return v * 0.001; }
DelayInfo getDelayFromNS(float ns) const
{
DelayInfo del;
del.delay = delay_t(ns * 1000);
return del;
}
uint32_t getDelayChecksum(delay_t v) const { return v; } uint32_t getDelayChecksum(delay_t v) const { return v; }
bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const; bool getBudgetOverride(const NetInfo *net_info, const PortRef &sink, delay_t &budget) const;
@ -819,8 +825,10 @@ struct Arch : BaseCtx
// Get the delay through a cell from one port to another, returning false // Get the delay through a cell from one port to another, returning false
// if no path exists // if no path exists
bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const; bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayInfo &delay) const;
// Get the port class, also setting clockDomain if applicable // Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, IdString &clockDomain) const; TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const;
// Get the TimingClockingInfo of a port
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const;
// Return true if a port is a net // Return true if a port is a net
bool isGlobalNet(const NetInfo *net) const; bool isGlobalNet(const NetInfo *net) const;

View File

@ -140,6 +140,10 @@ void arch_wrap_python()
"cells"); "cells");
readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls, readonly_wrapper<Context, decltype(&Context::nets), &Context::nets, wrap_context<NetMap &>>::def_wrap(ctx_cls,
"nets"); "nets");
fn_wrapper_2a_v<Context, decltype(&Context::addClock), &Context::addClock, conv_from_str<IdString>,
pass_through<float>>::def_wrap(ctx_cls, "addClock");
WRAP_RANGE(Bel, conv_to_str<BelId>); WRAP_RANGE(Bel, conv_to_str<BelId>);
WRAP_RANGE(Wire, conv_to_str<WireId>); WRAP_RANGE(Wire, conv_to_str<WireId>);
WRAP_RANGE(AllPip, conv_to_str<PipId>); WRAP_RANGE(AllPip, conv_to_str<PipId>);

View File

@ -462,7 +462,8 @@ static bool is_logic_port(BaseCtx *ctx, const PortRef &port)
static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic) static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen, bool is_logic)
{ {
log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "", is_logic ? " [logic]" : ""); log_info("promoting %s%s%s%s\n", net->name.c_str(ctx), is_reset ? " [reset]" : "", is_cen ? " [cen]" : "",
is_logic ? " [logic]" : "");
std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk")); std::string glb_name = net->name.str(ctx) + std::string("_$glb_") + (is_reset ? "sr" : (is_cen ? "ce" : "clk"));
std::unique_ptr<CellInfo> gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name); std::unique_ptr<CellInfo> gb = create_ice_cell(ctx, ctx->id("SB_GB"), "$gbuf_" + glb_name);
@ -489,6 +490,14 @@ static void insert_global(Context *ctx, NetInfo *net, bool is_reset, bool is_cen
} }
} }
net->users = keep_users; net->users = keep_users;
if (net->clkconstr) {
glbnet->clkconstr = std::unique_ptr<ClockConstraint>(new ClockConstraint());
glbnet->clkconstr->low = net->clkconstr->low;
glbnet->clkconstr->high = net->clkconstr->high;
glbnet->clkconstr->period = net->clkconstr->period;
}
ctx->nets[glbnet->name] = std::move(glbnet); ctx->nets[glbnet->name] = std::move(glbnet);
ctx->cells[gb->name] = std::move(gb); ctx->cells[gb->name] = std::move(gb);
} }